OpenHarmony智能開發(fā)套件—內(nèi)核編程(上)
前言
本篇具體介紹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):
我們要編寫的程序樣例就在源碼根目錄下的:applications/sample/wifi-iot/app/。
下面將具體演示如何編寫程序樣例。
- 新建樣例目錄
applications/sample/wifi-iot/app/hello_demo - 新建源文件和gn文件
applications/sample/wifi-iot/app/hello_demo/hello.c
applications/sample/wifi-iot/app/hello_demo/BUILD.gn
- 編寫源文件
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
筆者的代碼版本是OpenHarmony3.2Release版,不同版本的源碼可能庫所存放的路徑不同,那么怎么去找到對應(yīng)的庫呢,對于不熟悉源碼結(jié)構(gòu)的伙伴們學(xué)習(xí)起來很不友好。
對于在純Windows環(huán)境開發(fā)的伙伴們,筆者推薦使用everything這款工具,它可以快速查找主機(jī)中的文件,比在資源管理器的搜索快上不少。
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文件。
- 編寫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的所在目錄。
- 編寫app下的gn文件。
在app的目錄下也有一個gn文件,我們只需要去修改他即可。
這表示我們的程序?qū)?zhí)行hello_demo樣例中的sayHello模塊。
- 編譯,燒錄,串口調(diào)試。
這一步就屬于基礎(chǔ)操作了,不做過多贅述,不會的伙伴們可以看我之前發(fā)布的[環(huán)境搭建篇],里面也詳細(xì)介紹了操作流程。
- 觀察控制臺的輸出。
至此編碼完成了編碼入門,下面就具體介紹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)圖。
我們可以看到最底層叫做內(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)的工作:
- 進(jìn)程管理
- 內(nèi)存管理
- 文件資源管理
- 網(wǎng)絡(luò)通信管理
- 設(shè)備驅(qū)動管理
當(dāng)然不局限于這些,這里只是給出具體的例子供伙伴們理解,如果實(shí)在難以理解,那么筆者再舉一個例子,進(jìn)程??赡苣銢]聽過進(jìn)程,但你一定打開過任務(wù)管理器。
這些都是進(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)圖。
下面重點(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/。
下面將具體演示如何編寫程序樣例。
- 新建樣例目錄
applications/sample/wifi-iot/app/thread_demo。 - 新建源文件和gn文件
applications/sample/wifi-iot/app/thread_demo/singleThread.c
applications/sample/wifi-iot/app/thread_demo/BUILD.gn
- 編寫源碼
注意:我們需要使用到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);
- 編寫gn文件。
static_library("thread_demo"){
sources = [
"singleThread.c"
]
include_dirs = [
"http://commonlibrary/utils_lite/include",
"http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
]
}
- 編寫app下的gn文件。
注意的是,這次的寫法與上次不同,是因?yàn)楣P者的樣例文件名和靜態(tài)模塊的名字是一樣的就可以簡寫。
執(zhí)行效果
多線程的封裝
在處理業(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í)。
軟件定時器
下面我們介紹軟件定時器,老樣子我們先來介紹以下軟件定時器。軟件定時器是一種在軟件層面上實(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)用不被操作的時常即可。
操作
- 新建樣例目錄
applications/sample/wifi-iot/app/thread_demo。 - 新建源文件和gn文件
applications/sample/wifi-iot/app/thread_demo/singleThread.c。
applications/sample/wifi-iot/app/thread_demo/BUILD.gn。 - 編寫源碼
創(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);
- 編寫gn文件。
static_library("timer_demo"){
sources = [
"timer.c"
]
include_dirs = [
"http://commonlibrary/utils_lite/include",
"http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
]
}
- 編寫app下的gn文件。
執(zhí)行效果
軟件定時器的API相對較少,這里還是提供所有的軟件定時器API。
結(jié)束語
本篇主要介紹了一些基礎(chǔ)內(nèi)核編程相關(guān)的內(nèi)容,希望能夠幫助到學(xué)習(xí)OpenHarmony的伙伴們,考慮到篇幅問題,剩余的基礎(chǔ)內(nèi)核編程將在OpenHarmony智能開發(fā)套件[內(nèi)核編程·上]中介紹。