自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

OpenHarmony智能開發(fā)套件—內(nèi)核編程(上)

系統(tǒng) OpenHarmony
本篇主要介紹了一些基礎(chǔ)內(nèi)核編程相關(guān)的內(nèi)容,希望能夠幫助到學(xué)習(xí)OpenHarmony的伙伴們。

想了解更多關(guān)于開源的內(nèi)容,請訪問:

51CTO 開源基礎(chǔ)軟件社區(qū)

https://ost.51cto.com

前言

本篇具體介紹OpenHarmony在智能開發(fā)套件Hi3861上的內(nèi)核編程學(xué)習(xí)。

編程入門[Hello,OpenHarmony]

在正式開始之前,對于剛接觸OpenHarmony的伙伴們,面對大篇幅的源碼可能無從下手,不知道怎么去編碼寫程序,下面用一個簡單的例子帶伙伴們?nèi)腴T。

任務(wù)

編寫程序,讓開發(fā)板在串口調(diào)試工具中輸出”Hello,OpenHarmony“。

操作

在源碼的根目錄中有名為”applications“的文件,他存放著應(yīng)用程序樣例,下面是他的目錄結(jié)構(gòu):

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

我們要編寫的程序樣例就在源碼根目錄下的:applications/sample/wifi-iot/app/。

下面將具體演示如何編寫程序樣例。

  1. 新建樣例目錄
    applications/sample/wifi-iot/app/hello_demo
  2. 新建源文件和gn文件
    applications/sample/wifi-iot/app/hello_demo/hello.c
    applications/sample/wifi-iot/app/hello_demo/BUILD.gn

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

  1. 編寫源文件

hello.c:

#include <stdio.h>
#include "ohos_init.h"

void hello(void){
    printf("Hello,OpenHarmony!");
}

SYS_RUN(hello);

第一次操作的伙伴們可能會在引入”ohos_init.h“庫時報錯,面對這個問題我們只需要修改我們的include path即可,一般我們直接在目錄下的 .vscode/c_cpp_properties.json文件中直接修改includePath

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

筆者的代碼版本是OpenHarmony3.2Release版,不同版本的源碼可能庫所存放的路徑不同,那么怎么去找到對應(yīng)的庫呢,對于不熟悉源碼結(jié)構(gòu)的伙伴們學(xué)習(xí)起來很不友好。

對于在純Windows環(huán)境開發(fā)的伙伴們,筆者推薦使用everything這款工具,它可以快速查找主機(jī)中的文件,比在資源管理器的搜索快上不少。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

everything官網(wǎng)

everything似乎不能找到我WSL中的Ubuntu中的文件,因此對于Windows + Linux環(huán)境下的伙伴們,這款工具又不那么適用。那就可以根據(jù)Linux的查詢指令來定位文件所在目錄,下面提供查詢案例防止有不熟悉Linux的伙伴們。我們使用locate指令來查找文件。

首先安裝locate。

sudo apt install mlocate

更新mlocate.db。

sudo updatedb

查詢文件目錄。

locate ohos_init.h

找到我們源碼根目錄下 include路徑下的ohos_init.h文件。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

  1. 編寫gn文件。
static_library("sayHello"){
    sources = [
        "hello.c"
    ]
    include_dirs = [
        "http://commonlibrary/utils_lite/include"
    ]
}

static_library表示我們編寫的靜態(tài)模塊,名為"sayHello", sources表示我們要編譯的源碼,include_dirs表示我們引入的庫,這里的雙斜杠就代表我們的源碼根目錄,”/commonlibrary/utils_lite/include“就是我們ohos_init.h的所在目錄。

  1. 編寫app下的gn文件。

在app的目錄下也有一個gn文件,我們只需要去修改他即可。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

這表示我們的程序?qū)?zhí)行hello_demo樣例中的sayHello模塊。

  1. 編譯,燒錄,串口調(diào)試。

這一步就屬于基礎(chǔ)操作了,不做過多贅述,不會的伙伴們可以看我之前發(fā)布的[環(huán)境搭建篇],里面也詳細(xì)介紹了操作流程。

環(huán)境搭建篇

  1. 觀察控制臺的輸出。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

至此編碼完成了編碼入門,下面就具體介紹OpenHarmony的內(nèi)核編程。

內(nèi)核

內(nèi)核介紹

什么是內(nèi)核?或者說內(nèi)核在一個操作系統(tǒng)中起到一個什么樣的作用?相信初次接觸這個詞的伙伴們也會有同樣的疑問。不過不用擔(dān)心,筆者會盡可能地通俗地介紹內(nèi)核的相關(guān)知識,以便大家能夠更好地去體會內(nèi)核編程。

我們先來看一張圖,這是OpenHarmony官網(wǎng)發(fā)布的技術(shù)架構(gòu)圖。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

我們可以看到最底層叫做內(nèi)核層,有Linux,LiteOS等。內(nèi)核在整個架構(gòu),或者操作系統(tǒng)中起到一個核心作用,他負(fù)責(zé)管理計算機(jī)系統(tǒng)內(nèi)的資源和硬件設(shè)備,提供給頂層的應(yīng)用層一個統(tǒng)一規(guī)范的接口,從而使得整個系統(tǒng)能夠完成應(yīng)用與硬件的交互。

具體點(diǎn)來說,內(nèi)核可以做以下相關(guān)的工作:

  1. 進(jìn)程管理
  2. 內(nèi)存管理
  3. 文件資源管理
  4. 網(wǎng)絡(luò)通信管理
  5. 設(shè)備驅(qū)動管理

當(dāng)然不局限于這些,這里只是給出具體的例子供伙伴們理解,如果實(shí)在難以理解,那么筆者再舉一個例子,進(jìn)程??赡苣銢]聽過進(jìn)程,但你一定打開過任務(wù)管理器。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

這些都是進(jìn)程,一個進(jìn)程又由多個線程組成。那么CPU,內(nèi)存,硬盤,網(wǎng)絡(luò)這些硬件層面資源是怎么合理分配到我們軟件的各個進(jìn)程中呢?這就是內(nèi)核幫助我們完成的事情,我們并不關(guān)心我們設(shè)備上的應(yīng)用在哪里執(zhí)行,如何分配資源,內(nèi)核會完成這些事情。我們?nèi)粘Ec軟件交互,而內(nèi)核會幫助我們完成軟件和硬件的交互。

OpenHarmony內(nèi)核

明白了什么是內(nèi)核后,我們來看看OpenHarmony的內(nèi)核是怎么樣設(shè)計的吧。

OpenHarmony采用的是多內(nèi)核設(shè)計 有基于Linux內(nèi)核的標(biāo)準(zhǔn)系統(tǒng),有基于LiteOS-A的小型系統(tǒng),也有基于LiteOS-M的輕量系統(tǒng)。他們分別適配不同的設(shè)備,比如說智能手表就是輕量級別的,智能汽車就是標(biāo)準(zhǔn)級別的等等。本篇并不介紹標(biāo)準(zhǔn)系統(tǒng)和小型系統(tǒng),輕量系統(tǒng)更加適合初學(xué)者。

LiteOS-M內(nèi)核

下面是一張LiteOS-M的架構(gòu)圖。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

下面重點(diǎn)介紹KAL抽象層 和 基礎(chǔ)內(nèi)核的操作。

KAL抽象層

相信大家還是會有疑惑,什么是KAL抽象層?

Kernel Abstraction Layer。

在剛剛的內(nèi)核中我們提到了,內(nèi)核主要完成的是軟件與硬件的交互,他會給應(yīng)用層提供統(tǒng)一的規(guī)范接口,而KAL抽象層正是內(nèi)核對應(yīng)用層提供的接口集合。應(yīng)用程序可以通過KAL抽象層完成對硬件的控制交互。

抽象層是因?yàn)樗[藏了與硬件接口具體的交互邏輯,開發(fā)人員只需要關(guān)心如何操作硬件,而無需關(guān)心硬件底層的細(xì)節(jié),大大提高了可移植性和維護(hù)性。

以筆者的角度去看,KAL簡單來說就是一堆接口,幫助你去操控硬件。CMSIS與POSIX就是具有統(tǒng)一規(guī)范的一些接口。通過他們我們就可以去控制一些基礎(chǔ)的內(nèi)核,線程,軟件定時器,互斥鎖,信號量等等。概念就先簡單介紹這么多,感興趣的伙伴們可以上官網(wǎng)查看更多的關(guān)于OpenHarmony內(nèi)核的信息。下面筆者會帶著大家編碼操作,從實(shí)際去體會內(nèi)核編程。

內(nèi)核編程

線程管理

在管理線程前,我們需要了解線程,線程是調(diào)度的基本單位,具有獨(dú)立的??臻g和寄存器上下文,相比與進(jìn)程,他是輕量的。舉一個實(shí)際的例子,動物園賣票。

對于動物園賣票這件事本身而言是一個進(jìn)程,而每一個買票的人可以看作一個線程,在多個售票口處,我們并發(fā)執(zhí)行,并行計算,共同消費(fèi)動物園的門票,像享受共同的內(nèi)存資源空間一樣。為什么要線程管理呢?你我都希望買到票,但是票有限,我們都不希望看到售票廳一篇混亂,因此對線程進(jìn)行管理是非常重要的一件事情。

任務(wù)

創(chuàng)建一個線程,每間隔0.1秒,輸出“Hello,OpenHarmony”,1秒后終止線程。

操作

回憶第一個hello.c的例子。

我們要編寫的程序樣例就在源碼根目錄下的:applications/sample/wifi-iot/app/。

下面將具體演示如何編寫程序樣例。

  1. 新建樣例目錄
    applications/sample/wifi-iot/app/thread_demo。
  2. 新建源文件和gn文件
    applications/sample/wifi-iot/app/thread_demo/singleThread.c
    applications/sample/wifi-iot/app/thread_demo/BUILD.gn

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

  1. 編寫源碼

注意:我們需要使用到cmsis_os2.h這個庫,請伙伴們按照筆者介紹的方法把includePath修改好。

問題一:怎么創(chuàng)建線程?

typedef struct {
  /** Thread name */
  const char                   *name;
  /** Thread attribute bits */
  uint32_t                 attr_bits;
  /** Memory for the thread control block */
  void                      *cb_mem;
  /** Size of the memory for the thread control block */
  uint32_t                   cb_size;
  /** Memory for the thread stack */
  void                   *stack_mem;
  /** Size of the thread stack */
  uint32_t                stack_size;
  /** Thread priority */
  osPriority_t              priority;
  /** TrustZone module of the thread */
  TZ_ModuleId_t            tz_module;
  /** Reserved */
  uint32_t                  reserved;
} osThreadAttr_t;

這是線程的結(jié)構(gòu)體,它具有以下屬性:

  • name:線程的名稱。
  • attr_bits:線程屬性位。
  • cb_mem:線程控制塊的內(nèi)存地址。
  • cb_size:線程控制塊的內(nèi)存大小。
  • stack_mem:線程棧的內(nèi)存地址。
  • stack_size:線程棧的大小。
  • priority:線程的優(yōu)先級。
  • tz_module:線程所屬的TrustZone模塊。
  • reserved:保留字段。

問題二:怎么把線程啟動起來呢?

osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr);

這是創(chuàng)建線程的接口函數(shù),他有三個參數(shù),一個返回值,我們來逐個解析。

func: 是線程的回調(diào)函數(shù),你創(chuàng)建的這個線程會執(zhí)行這段函數(shù)的內(nèi)容。

arguments:線程回調(diào)函數(shù)的參數(shù)。

attr:線程的屬性,也就是我們之前創(chuàng)建的線程

返回值:線程的id 如果id不為空則說明成功。

問題三:怎么終止線程呢?

osStatus_t osThreadTerminate (osThreadId_t thread_id);

顯然我們只要傳入線程的id就會讓該線程終止,返回值是一個狀態(tài)碼,下面給出全部的狀態(tài)碼。

typedef enum {
  /** Operation completed successfully */
  osOK                      =  0,
  /** Unspecified error */
  osError                   = -1,
  /** Timeout */
  osErrorTimeout            = -2,
  /** Resource error */
  osErrorResource           = -3,
  /** Incorrect parameter */
  osErrorParameter          = -4,
  /** Insufficient memory */
  osErrorNoMemory           = -5,
  /** Service interruption */
  osErrorISR                = -6,
  /** Reserved. It is used to prevent the compiler from optimizing enumerations. */
  osStatusReserved          = 0x7FFFFFFF
} osStatus_t;

回調(diào)函數(shù)怎么寫?當(dāng)然是結(jié)合我們的任務(wù),每間隔0.1秒,輸出“Hello,OpenHarmony”,1秒后終止。講到這里,代碼的整體邏輯是不是就清晰了很多,直接上完整代碼。

#include <stdio.h>
#include "ohos_init.h"
// CMSIS
#include "cmsis_os2.h"
// POSIX
#include <unistd.h>

// 線程回調(diào)函數(shù)
void printThread(void *args){
    (void)args;
    while(1){
        printf("Hello,OpenHarmony!\r\n");
        // 休眠0.1秒
        osDelay(10);
    }
}

void threadTest(void){
    // 創(chuàng)建線程
    osThreadAttr_t attr;
    attr.name = "mainThread";
    // 線程
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 1024;
    attr.priority = osPriorityNormal;

    // 將線程啟動
    osThreadId_t tid = osThreadNew((osThreadFunc_t)printThread, NULL, &attr);
    if(tid == NULL){
        printf("[Thread Test] Failed to create printThread!\r\n");
    }

    // 休眠5秒
    osDelay(500);
    // 終止線程
    osStatus_t status = osThreadTerminate(tid);
    printf("[Thread Test] printThread stop, status = %d.\r\n", status);

}

APP_FEATURE_INIT(threadTest);
  1. 編寫gn文件。
static_library("thread_demo"){
    sources = [
        "singleThread.c"
    ]
    include_dirs = [
        "http://commonlibrary/utils_lite/include",
        "http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
    ]
}
  1. 編寫app下的gn文件。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

注意的是,這次的寫法與上次不同,是因?yàn)楣P者的樣例文件名和靜態(tài)模塊的名字是一樣的就可以簡寫。

執(zhí)行效果

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

多線程的封裝

在處理業(yè)務(wù)的時候,我們一般是多線程的背景,下面筆者將創(chuàng)建線程函數(shù)封裝起來,方便大家創(chuàng)建多線程。

osThreadId_t newThread(char *name, osThreadFunc_t func, void *arg){
    // 定義線程和屬性
    osThreadAttr_t attr = {
        name, 0, NULL, 0, NULL, 1024, osPriorityNormal, 0, 0
    };
    // 創(chuàng)建線程
    osThreadId_t tid = osThreadNew(func, arg, &attr);
    if(tid == NULL){
        printf("[newThread] osThreadNew(%s) failed.\r\n", name);
    }
    return tid;
}

線程部分先體會到這里,想要探索更過線程相關(guān)的API,筆者這里提供了API網(wǎng)站,供大家參考學(xué)習(xí)。

CMSIS_OS2 Thread API

軟件定時器

下面我們介紹軟件定時器,老樣子我們先來介紹以下軟件定時器。軟件定時器是一種在軟件層面上實(shí)現(xiàn)的計時器機(jī)制,用于在特定的時間間隔內(nèi)執(zhí)行特定的任務(wù)或觸發(fā)特定的事件。它不依賴于硬件定時器,而是通過軟件編程的方式實(shí)現(xiàn)。舉一個例子,手機(jī)應(yīng)用。

當(dāng)你使用手機(jī)上的某個應(yīng)用時,你可能會注意到,如果你在一段時間內(nèi)沒有進(jìn)行任何操作,應(yīng)用程序會自動斷開連接并要求你重新登錄。這是為了保護(hù)你的賬號安全并釋放服務(wù)器資源。類似的設(shè)定都是有軟件定時器實(shí)現(xiàn)的,下面進(jìn)行實(shí)際操作,讓大家體會一下軟件定時器。

任務(wù)

創(chuàng)建一個軟件定時器,用來模擬上述手機(jī)應(yīng)用的例子。為了方便理解,假設(shè)從此刻開始,我們不對手機(jī)做任何操作,也就是說,我們的回調(diào)函數(shù)只需要單純的計算應(yīng)用不被操作的時常即可。

操作
  1. 新建樣例目錄
    applications/sample/wifi-iot/app/thread_demo。
  2. 新建源文件和gn文件
    applications/sample/wifi-iot/app/thread_demo/singleThread.c。
    applications/sample/wifi-iot/app/thread_demo/BUILD.gn。
  3. 編寫源碼

創(chuàng)建軟件定時器。

osTimerId_t osTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr);
  • func: 軟件定時器的回調(diào)函數(shù)。
  • type:軟件定時器的種類。
  • argument:軟件定時器回調(diào)函數(shù)的參數(shù)。
  • attr:軟件定時器的屬性。

返回值:返回軟件定時器的id, id為空則說明軟件定時器失敗。

typedef enum {
  /** One-shot timer */
  osTimerOnce               = 0,
  /** Repeating timer */
  osTimerPeriodic           = 1
} osTimerType_t;

軟件定時器的種類有兩個,分為一次性定時器和周期性定時器,一次性在執(zhí)行完回調(diào)函數(shù)后就會停止計數(shù),而周期性定時器會重復(fù)觸發(fā),每次觸發(fā)重新計時。根據(jù)不同的需求我們可以選擇使用不同的軟件定時器。

啟動軟件定時器。

osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks);
  • timer_id:軟件定時器的參數(shù),指定要啟動哪個軟件定時器。
  • ticks:等待多少個ticks執(zhí)行回調(diào)函數(shù),在Hi3861中 100個ticks為1秒。
  • 返回值:軟件定時器的狀態(tài)碼,在線程部分已經(jīng)展示給大家了全部的狀態(tài)碼。

停止定時器。

osStatus_t osTimerStop (osTimerId_t timer_id);

這個函數(shù)很簡單,只需要傳軟件定時器的id,即可停止軟件計時器,并且返回他的狀態(tài)碼。

刪除定時器。

osStatus_t osTimerDelete (osTimerId_t timer_id);

刪除和停止類似,就不多說明了。

下面是源代碼。

#include <stdio.h>
#include "ohos_init.h"
// CMSIS
#include "cmsis_os2.h"
// POSIX
#include <unistd.h>

// 為操作軟件的時間
static int times = 0;

// 軟件定時器回調(diào)函數(shù)
void timerFunction(void){
    times++;
    printf("[Timer Test] Timer is Running, times = %d.\r\n", times);
}

// 主函數(shù)
void timerMain(void){
    // 創(chuàng)建軟件定時器
    osTimerId_t tid = osTimerNew(timerFunction, osTimerPeriodic, NULL, NULL);
    if(tid == NULL){
        printf("[Timer Test] Failed to create a timer!\r\n");
        return;
    } else {
        printf("[Timer Test] Create a timer success!\r\n");
    }
    // 啟動軟件定時器,每1秒執(zhí)行一次回調(diào)函數(shù)
    osStatus_t status = osTimerStart(tid, 100);

    // 當(dāng)超過三個周期位操作軟件時,關(guān)閉軟件
    while(times <= 3){
        osDelay(100);
    }
    // 停止軟件定時器
    status = osTimerStop(tid);
    // 刪除軟件定時器
    status = osTimerDelete(tid);
    printf("[Timer Test] Time Out!\r\n");
}

void TimerTest(void){
    // 創(chuàng)建測試線程
    osThreadAttr_t attr;
    attr.name = "timerMain";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 0U;
    attr.priority = osPriorityNormal;
    
    // 啟動測試線程
    osThreadId_t tid = osThreadNew((osThreadFunc_t)timerMain, NULL, &attr);
    if(tid == NULL){
        printf("[Timer Test] Failed to created timerMain!\r\n");
    }
}

APP_FEATURE_INIT(TimerTest);
  1. 編寫gn文件。
static_library("timer_demo"){
    sources = [
        "timer.c"
    ]
    include_dirs = [
        "http://commonlibrary/utils_lite/include",
        "http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
    ]
}
  1. 編寫app下的gn文件。

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

執(zhí)行效果

OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]-開源基礎(chǔ)軟件社區(qū)

軟件定時器的API相對較少,這里還是提供所有的軟件定時器API。

CMSIS_OS2 Timer API

結(jié)束語

本篇主要介紹了一些基礎(chǔ)內(nèi)核編程相關(guān)的內(nèi)容,希望能夠幫助到學(xué)習(xí)OpenHarmony的伙伴們,考慮到篇幅問題,剩余的基礎(chǔ)內(nèi)核編程將在OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]中介紹。

想了解更多關(guān)于開源的內(nèi)容,請訪問:

51CTO 開源基礎(chǔ)軟件社區(qū)

https://ost.51cto.com

責(zé)任編輯:jianghua 來源: 51CTO 開源基礎(chǔ)軟件社區(qū)
相關(guān)推薦

2023-05-17 15:07:42

智能開發(fā)鴻蒙

2023-05-26 16:01:32

驅(qū)動開發(fā)鴻蒙

2023-05-12 14:52:11

鴻蒙操作系統(tǒng)

2023-05-30 14:58:05

智能開發(fā)鴻蒙

2021-12-06 15:05:41

鴻蒙HarmonyOS應(yīng)用

2021-11-12 15:58:11

鴻蒙HarmonyOS應(yīng)用

2022-03-01 15:54:38

智能開發(fā)鴻蒙創(chuàng)造性TV

2022-03-03 19:21:50

Harmony鴻蒙操作系統(tǒng)

2022-04-01 15:26:06

Harmony操作系統(tǒng)鴻蒙

2009-02-27 09:07:09

Linux開發(fā)套件100美元

2023-10-27 06:33:14

鴻蒙開發(fā)套件

2010-01-22 09:40:36

Kindle平臺亞馬遜

2011-10-25 09:48:07

NFC諾基亞Symbian

2020-10-30 17:57:11

鴻蒙HiSpark

2013-08-07 09:45:35

Windows phoWP應(yīng)用開發(fā)套件Stu

2018-02-27 16:55:38

微軟量子開發(fā)

2023-10-06 11:09:23

微軟C#

2022-05-30 15:38:02

開箱照片編譯環(huán)境搭建

2021-01-26 14:27:21

鴻蒙HarmonyOS應(yīng)用

2023-02-28 15:49:09

鴻蒙應(yīng)用開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號