<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

OpenHarmony內核編程實戰

觸覺智能 ? 2024-03-27 08:31 ? 次閱讀

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

▍任務編寫程序,讓開發板在串口調試工具中輸出”Hello,OpenHarmony“。

▍操作

在源碼的根目錄中有名為”applications“的文件,他存放著應用程序樣例,下面是他的目錄結構:

5f464dbe-ebd1-11ee-9118-92fbcf53809c.png

我們要編寫的程序樣例就在源碼根目錄下的:applications/sample/wifi-iot/app/下面將具體演示如何編寫程序樣例。

1.新建樣例目錄applications/sample/wifi-iot/app/hello_demo

2.新建源文件和gn文件applications/sample/wifi-iot/app/hello_demo/hello.capplications/sample/wifi-iot/app/hello_demo/BUILD.gn

5f5cb612-ebd1-11ee-9118-92fbcf53809c.png

3.編寫源文件hello.c

#include
#include"ohos_init.h"

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

SYS_RUN(hello);

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

5f643acc-ebd1-11ee-9118-92fbcf53809c.png筆者的代碼版本是OpenHarmony3.2Release版,不同版本的源碼可能庫所存放的路徑不同,那么怎么去找到對應的庫呢,對于不熟悉源碼結構的伙伴們學習起來很不友好。對于在純Windows環境開發的伙伴們,筆者推薦使用everything這款工具,它可以快速查找主機中的文件,比在資源管理器的搜索快上不少。5f7148ca-ebd1-11ee-9118-92fbcf53809c.pngeverything似乎不能找到我WSL中的Ubuntu中的文件,因此對于Windows + Linux環境下的伙伴們,這款工具又不那么適用。那就可以根據Linux的查詢指令來定位文件所在目錄,下面提供查詢案例防止有不熟悉Linux的伙伴們。我們使用locate指令來查找文件。首先安裝locate

sudoaptinstallmlocate

更新mlocate.db

sudoupdatedb

查詢文件目錄

locateohos_init.h

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

5f86a120-ebd1-11ee-9118-92fbcf53809c.png4.編寫gn文件

static_library("sayHello"){
sources = [
"hello.c"
]
include_dirs = [
"http://commonlibrary/utils_lite/include"
]
}

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

5.編寫app下的gn文件在app的目錄下也有一個gn文件,我們只需要去修改他即可5f94997e-ebd1-11ee-9118-92fbcf53809c.png

這表示我們的程序將會執行hello_demo樣例中的sayHello模塊

6.編譯,燒錄,串口調試這一步就屬于基礎操作了,不做過多贅述,7.觀察控制臺的輸出5fa4e31a-ebd1-11ee-9118-92fbcf53809c.png

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

內核

▍內核介紹

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

我們先來看一張圖,這是OpenHarmony官網發布的技術架構圖5fb00a6a-ebd1-11ee-9118-92fbcf53809c.png

我們可以看到最底層叫做內核層,有Linux,LiteOS等。內核在整個架構,或者操作系統中起到一個核心作用,他負責管理計算機系統內的資源和硬件設備,提供給頂層的應用層一個統一規范的接口,從而使得整個系統能夠完成應用與硬件的交互。

具體點來說,內核可以做以下相關的工作:1.進程管理2.內存管理3.文件資源管理4.網絡通信管理5.設備驅動管理當然不局限于這些,這里只是給出具體的例子供伙伴們理解,如果實在難以理解,那么筆者再舉一個例子,進程??赡苣銢]聽過進程,但你一定打開過任務管理器。5fc04650-ebd1-11ee-9118-92fbcf53809c.png

這些都是進程,一個進程又由多個線程組成。那么CPU,內存,硬盤,網絡這些硬件層面資源是怎么合理分配到我們軟件的各個進程中呢?這就是內核幫助我們完成的事情,我們并不關心我們設備上的應用在哪里執行,如何分配資源,內核會完成這些事情。我們日常與軟件交互,而內核會幫助我們完成軟件和硬件的交互。

▍OpenHarmony內核明白了什么是內核后,我們來看看OpenHarmony的內核是怎么樣設計的吧。OpenHarmony采用的是多內核設計 有基于Linux內核的標準系統,有基于LiteOS-A的小型系統,也有基于LiteOS-M的輕量系統。他們分別適配不同的設備,比如說智能手表就是輕量級別的,智能汽車就是標準級別的等等。本篇并不介紹標準系統和小型系統,輕量系統更加適合初學者。▍LiteOS-M內核

下面是一張LiteOS-M的架構圖

5fd28a18-ebd1-11ee-9118-92fbcf53809c.png

下面重點介紹KAL抽象層 和 基礎內核的操作

KAL抽象層

相信大家還是會有疑惑,什么是KAL抽象層?Kernel Abstraction Layer在剛剛的內核中我們提到了,內核主要完成的是軟件與硬件的交互,他會給應用層提供統一的規范接口,而KAL抽象層正是內核對應用層提供的接口集合。應用程序可以通過KAL抽象層完成對硬件的控制交互。抽象層是因為他隱藏了與硬件接口具體的交互邏輯,開發人員只需要關心如何操作硬件,而無需關心硬件底層的細節,大大提高了可移植性和維護性。以筆者的角度去看,KAL簡單來說就是一堆接口,幫助你去操控硬件。CMSIS與POSIX就是具有統一規范的一些接口。通過他們我們就可以去控制一些基礎的內核,線程,軟件定時器,互斥鎖,信號量等等。概念就先簡單介紹這么多,感興趣的伙伴們可以上官網查看更多的關于OpenHarmony內核的信息。下面筆者會帶著大家編碼操作,從實際去體會內核編程。

內核編程

▍線程管理

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

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

任務

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

操作

回憶第一個hello.c的例子我們要編寫的程序樣例就在源碼根目錄下的:applications/sample/wifi-iot/app/下面將具體演示如何編寫程序樣例。1.新建樣例目錄applications/sample/wifi-iot/app/thread_demo2.新建源文件和gn文件applications/sample/wifi-iot/app/thread_demo/singleThread.capplications/sample/wifi-iot/app/thread_demo/BUILD.gn

5fe07f74-ebd1-11ee-9118-92fbcf53809c.png

3.編寫源碼注意:我們需要使用到cmsis_os2.h這個庫,請伙伴們按照筆者介紹的方法把includePath修改好。問題一:怎么創建線程?

typedefstruct{
/**Threadname*/
constchar*name;
/**Threadattributebits*/
uint32_tattr_bits;
/**Memoryforthethreadcontrolblock*/
void*cb_mem;
/**Sizeofthememoryforthethreadcontrolblock*/
uint32_tcb_size;
/**Memoryforthethreadstack*/
void*stack_mem;
/**Sizeofthethreadstack*/
uint32_tstack_size;
/**Threadpriority*/
osPriority_tpriority;
/**TrustZonemoduleofthethread*/
TZ_ModuleId_ttz_module;
/**Reserved*/
uint32_treserved;
}osThreadAttr_t;

這是線程的結構體,它具有以下屬性:

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

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

osThreadId_tosThreadNew(osThreadFunc_tfunc,void*argument,constosThreadAttr_t*attr);

這是創建線程的接口函數,他有三個參數,一個返回值,我們來逐個解析func:是線程的回調函數,你創建的這個線程會執行這段函數的內容。arguments:線程回調函數的參數。attr:線程的屬性,也就是我們之前創建的線程返回值:線程的id 如果id不為空則說明成功。問題三:怎么終止線程呢?

osStatus_tosThreadTerminate(osThreadId_tthread_id);

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

typedefenum{
/**Operationcompletedsuccessfully*/
osOK= 0,
/**Unspecifiederror*/
osError=-1,
/**Timeout*/
osErrorTimeout=-2,
/**Resourceerror*/
osErrorResource=-3,
/**Incorrectparameter*/
osErrorParameter=-4,
/**Insufficientmemory*/
osErrorNoMemory=-5,
/**Serviceinterruption*/
osErrorISR=-6,
/**Reserved.Itisusedtopreventthecompilerfromoptimizingenumerations.*/
osStatusReserved=0x7FFFFFFF
}osStatus_t;

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

#include
#include"ohos_init.h"
//CMSIS
#include"cmsis_os2.h"
//POSIX
#include

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

voidthreadTest(void){
//創建線程
osThreadAttr_tattr;
attr.name="mainThread";
//線程
attr.cb_mem=NULL;
attr.cb_size=0U;
attr.stack_mem=NULL;
attr.stack_size=1024;
attr.priority=osPriorityNormal;

//將線程啟動
osThreadId_ttid=osThreadNew((osThreadFunc_t)printThread,NULL,&attr);
if(tid==NULL){
printf("[ThreadTest]FailedtocreateprintThread!\r\n");
}

//休眠5秒
osDelay(500);
//終止線程
osStatus_tstatus=osThreadTerminate(tid);
printf("[ThreadTest]printThreadstop,status=%d.\r\n",status);

}

APP_FEATURE_INIT(threadTest);

4.編寫gn文件

static_library("thread_demo"){
sources=[
"singleThread.c"
]
include_dirs=[
"http://commonlibrary/utils_lite/include",
"http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
]
}

5.編寫app下的gn文件5fe8d3fe-ebd1-11ee-9118-92fbcf53809c.png

注意的是,這次的寫法與上次不同,是因為筆者的樣例文件名和靜態模塊的名字是一樣的就可以簡寫。

執行效果

5ffaf610-ebd1-11ee-9118-92fbcf53809c.png▍多線程的封裝

在處理業務的時候,我們一般是多線程的背景,下面筆者將創建線程函數封裝起來,方便大家創建多線程

osThreadId_tnewThread(char*name,osThreadFunc_tfunc,void*arg){
//定義線程和屬性
osThreadAttr_tattr={
name,0,NULL,0,NULL,1024,osPriorityNormal,0,0
};
//創建線程
osThreadId_ttid=osThreadNew(func,arg,&attr);
if(tid==NULL){
printf("[newThread]osThreadNew(%s)failed.\r\n",name);
}
returntid;
}

線程部分先體會到這里,想要探索更過線程相關的API,筆者這里提供了API網站,供大家參考學習。

CMSIS_OS2 Thread API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_threadMgmt

軟件定時器

下面我們介紹軟件定時器,老樣子我們先來介紹以下軟件定時器。軟件定時器是一種在軟件層面上實現的計時器機制,用于在特定的時間間隔內執行特定的任務或觸發特定的事件。它不依賴于硬件定時器,而是通過軟件編程的方式實現。舉一個例子,手機應用。

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

任務

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

操作

1.新建樣例目錄applications/sample/wifi-iot/app/thread_demo2.新建源文件和gn文件applications/sample/wifi-iot/app/thread_demo/singleThread.capplications/sample/wifi-iot/app/thread_demo/BUILD.gn3.編寫源碼創建軟件定時器

osTimerId_tosTimerNew(osTimerFunc_tfunc,osTimerType_ttype,void*argument,constosTimerAttr_t*attr);

func: 軟件定時器的回調函數type:軟件定時器的種類argument:軟件定時器回調函數的參數attr:軟件定時器的屬性返回值:返回軟件定時器的id, id為空則說明軟件定時器失敗

typedefenum{
/**One-shottimer*/
osTimerOnce=0,
/**Repeatingtimer*/
osTimerPeriodic=1
}osTimerType_t;

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

osStatus_tosTimerStart(osTimerId_ttimer_id,uint32_tticks);

timer_id:軟件定時器的參數,指定要啟動哪個軟件定時器ticks:等待多少個ticks執行回調函數,在Hi3861中 100個ticks為1秒返回值:軟件定時器的狀態碼,在線程部分已經展示給大家了全部的狀態碼停止定時器

osStatus_tosTimerStop(osTimerId_ttimer_id);

這個函數很簡單,只需要傳軟件定時器的id,即可停止軟件計時器,并且返回他的狀態碼刪除定時器

osStatus_tosTimerDelete(osTimerId_ttimer_id);

刪除和停止類似,就不多說明了。下面是源代碼

#include
#include"ohos_init.h"
//CMSIS
#include"cmsis_os2.h"
//POSIX
#include

//為操作軟件的時間
staticinttimes=0;

//軟件定時器回調函數
voidtimerFunction(void){
times++;
printf("[TimerTest]TimerisRunning,times=%d.\r\n",times);
}

//主函數
voidtimerMain(void){
//創建軟件定時器
osTimerId_ttid=osTimerNew(timerFunction,osTimerPeriodic,NULL,NULL);
if(tid==NULL){
printf("[TimerTest]Failedtocreateatimer!\r\n");
return;
}else{
printf("[TimerTest]Createatimersuccess!\r\n");
}
//啟動軟件定時器,每1秒執行一次回調函數
osStatus_tstatus=osTimerStart(tid,100);

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

voidTimerTest(void){
//創建測試線程
osThreadAttr_tattr;
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_ttid=osThreadNew((osThreadFunc_t)timerMain,NULL,&attr);
if(tid==NULL){
printf("[TimerTest]FailedtocreatedtimerMain!\r\n");
}
}

APP_FEATURE_INIT(TimerTest);

4.編寫gn文件

static_library("timer_demo"){
sources=[
"timer.c"
]
include_dirs=[
"http://commonlibrary/utils_lite/include",
"http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
]
}

5.編寫app下的gn文件603c0b14-ebd1-11ee-9118-92fbcf53809c.png

執行效果

603c0b14-ebd1-11ee-9118-92fbcf53809c.png軟件定時器的API相對較少,這里還是提供所有的軟件定時器APICMSIS_OS2 Timer API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_timer▍互斥鎖線程的狀態
在介紹互斥鎖之前,我們有必要去了解一下線程的狀態,或者說線程的生命周期。避免伙伴們因為不夠熟悉線程而對這個互斥鎖的概念感到困難。首先介紹一下線程的幾個狀態,他們分別有:

  • 創建

創建線程,在OpenHarmony的源碼中,線程的屬性被封裝成了一個名為”osThreadAttr_t“的結構體

typedefstruct{
constchar*name;
uint32_tattr_bits;
void*cb_mem;
uint32_tcb_size;
void*stack_mem;
uint32_tstack_size;
osPriority_tpriority;
TZ_ModuleId_ttz_module;
uint32_treserved;
}osThreadAttr_t;

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

當我們創建一個線程的時候,系統就會為該線程分配所需要的資源,將線程加入到系統的線程調度隊列中,此時線程已經處在就緒狀態了。

  • 就緒

線程一旦被創建,就會進入就緒狀態,他表示我們完成的線程的創建(線程相關屬性的初始化),但是并未運行,線程正在等待操作系統調度程序,將其調度運行起來。

  • 運行

之前我們介紹了一個關于線程的API,他可以將就緒狀態的線程加入到活躍線程組

osThreadId_tosThreadNew(osThreadFunc_tfunc,void*argument,constosThreadAttr_t*attr);

此時的線程將會占用部分cpu資源執行他的程序,直到程序結束,或者線程被阻塞,cpu被搶占等。

  • 阻塞

線程阻塞,可以理解為線程無法繼續往下執行,但時線程執行的程序不會直接退出,他會進入到等待的狀態,直到相關資源被釋放,線程可以繼續執行。

  • 終止

上篇我們介紹了線程終止的API

sStatus_tstatus=osThreadTerminate(osThreadId_ttid);

此時線程完成了自己的代碼邏輯,我們方可主動調用該API徹底刪除這個線程。當然如果具體細分,其實不止這五個狀態,但是了解了這五個狀態就足夠了。下面我們來聊聊什么是互斥鎖。互斥鎖簡介

互斥鎖的應用場景時處理多線程,資源訪問的問題。這里還是給大家舉一個例子:動物園賣票。

在我們的程序設計中,往往會有共有資源,每個線程都可以進來訪問這些資源。這里的共有資源就是我們的門票,一個售票口就是一個線程,當有人來窗口買票,我們的門票就會減少一張。當然一個動物園的流量時巨大的,我們不能只設立一個售票口,這樣的效率是很低的。京東淘寶搶購秒殺也一樣,我們必須設立多個窗口,在同一時刻為多個人處理業務。多線程解決了效率問題但也帶來了安全隱患。我們假設一個這樣的場景,動物園僅剩一張門票,但是有兩個在不同的窗口同時付了錢,當售票員為他們拿票的時候就會發現,少了一張票,兩個人都付了錢都想要那張票,就陷入了一個死局,無奈動物園只能讓他們都進去。但是在程序中體現的可能就是一個bug,動物園的門票剩余數為:-1。

if(count>0){
count--;
}

售票的邏輯很簡單,只要票數大于零,還有票能賣,那就在此基礎上減掉一。問題就在于,兩個線程同時在count = 1的時候通過了if判斷,都執行的count--;那么就會出現了count = -1,也就是票數為負 的bug結果。

互斥鎖的出現就很好地解決了一個問題,他能夠阻止上面兩個線程同時訪問資源的同步行為,也就是說當一個線程進入這個if語句后,別的線程都不能進入。形象起來說,就像對這段代碼加了鎖,只有有鑰匙的線程才能夠訪問它。6061b058-ebd1-11ee-9118-92fbcf53809c.png

互斥鎖API

通過對幾個API的介紹,讓大家知道怎么為一段代碼加上互斥鎖

1.創建互斥鎖與線程的定義一樣,互斥鎖也封裝成了一個結構體

typedefstruct{
/**Mutexname*/
constchar*name;
/**Reservedattributebits*/
uint32_tattr_bits;
/**Memoryforthemutexcontrolblock*/
void*cb_mem;
/**Sizeofthememoryforthemutexcontrolblock*/
uint32_tcb_size;
}osMutexAttr_t;

  • name:互斥鎖的名稱
  • attr_bits:保留的屬性位
  • cb_mem:互斥鎖控制塊的內存
  • cb_size:互斥鎖控制塊內存的大小

2.獲取互斥鎖的id

osMutexId_tosMutexNew(constosMutexAttr_t*attr);

將我們上面定義的互斥鎖屬性傳入函數,返回互斥鎖的id3.線程獲取互斥鎖

osStatus_tosMutexAcquire(osMutexId_tmutex_id,uint32_ttimeout);

傳入互斥鎖id,設置我們的延遲時間,當線程獲取到我們的互斥鎖時,返回的狀態是osOK。

下面是全部的狀態碼

typedefenum{
/**Operationcompletedsuccessfully*/
osOK= 0,
/**Unspecifiederror*/
osError=-1,
/**Timeout*/
osErrorTimeout=-2,
/**Resourceerror*/
osErrorResource=-3,
/**Incorrectparameter*/
osErrorParameter=-4,
/**Insufficientmemory*/
osErrorNoMemory=-5,
/**Serviceinterruption*/
osErrorISR=-6,
/**Reserved.Itisusedtopreventthecompilerfromoptimizingenumerations.*/
osStatusReserved=0x7FFFFFFF
}osStatus_t;

4.釋放鎖

osStatus_tosMutexRelease(osMutexId_tmutex_id);

當我們的線程執行完業務后需要把鎖釋放出來,讓別的線程獲取鎖,執行業務。當然這個過程是線程之間的競爭,一個線程可能一直得不到鎖,一個線程也可能剛釋放鎖又獲得鎖,我們可以添加休眠操作,提高鎖在各個線程間的分配。

其他API請參考:Mutex API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_mutex操作1.新建樣例目錄applications/sample/wifi-iot/app/mutex_demo2.新建源文件和gn文件applications/sample/wifi-iot/app/mutex_demo/mutex.capplications/sample/wifi-iot/app/mutex_demo/BUILD.gn

606aadb6-ebd1-11ee-9118-92fbcf53809c.png

3.源碼編寫因為已經介紹了主要的API,這里就直接給伙伴們上源碼了。

#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"

//模擬動物園門票數
staticintcount=100;

//售票業務線程
voidoutThread(void*args){
//獲取互斥鎖
osMutexId_t*mid=(osMutexId_t*)args;
//每個線程都在不停地買票
while(1){
//獲取鎖,進入業務流程
if(osMutexAcquire(*mid,100)==osOK){
if(count>0){
count--;
//設置提示信息
printf("[MutexTest]Thread%sgetavalue,thelessis%d.\r\n",osThreadGetName(osThreadGetId()),count);
}else{
//告知這些線程已經沒有門票賣了,線程結束
printf("[MutexTest]Thevalueisout!\r\n");
osThreadTerminate(osThreadGetId());
}
}
//釋放鎖
osMutexRelease(*mid);

osDelay(5);
}
}
//創建線程封裝
osThreadId_tcreateThreads(char*name,osThreadFunc_tfunc,void*args){
osThreadAttr_tattr={
name,0,NULL,0,NULL,1024,osPriorityNormal,0,0
};
osThreadId_ttid=osThreadNew(func,args,&attr);
returntid;
}

//主函數實現多線程的創建,執行買票業務
voidmutexMain(void){
//創建互斥鎖
osMutexAttr_tattr={0};

//獲取互斥鎖的id
osMutexId_tmid=osMutexNew(&attr);

if(mid==NULL){
printf("[MutexTest]Failedtocreateamutex!\r\n");
}

//創建多線程
osThreadId_ttid1=createThreads("Thread_1",(osThreadFunc_t)outThread,&mid);
osThreadId_ttid2=createThreads("Thread_2",(osThreadFunc_t)outThread,&mid);
osThreadId_ttid3=createThreads("Thread_3",(osThreadFunc_t)outThread,&mid);

osDelay(1000);

}

//測試線程
voidMainTest(void){
osThreadId_ttid=createThreads("MainTest",(osThreadFunc_t)mutexMain,NULL);
}

APP_FEATURE_INIT(MainTest);

4.編寫gn文件

static_library("mutex_demo"){
sources = [
"mutex.c"
]
include_dirs = [
"http://commonlibrary/utils_lite/include",
"http://device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
]
}

5.編寫app目錄下的gn文件607d4714-ebd1-11ee-9118-92fbcf53809c.png

結果展示

6090d1da-ebd1-11ee-9118-92fbcf53809c.png可能有的伙伴們看到這里不太清晰,會覺得這段代碼真的上鎖了嗎

if(osMutexAcquire(*mid,100)==osOK){
if(count>0){
count--;
printf("[MutexTest]Thread%sgetavalue,thelessis%d.\r\n",osThreadGetName(osThreadGetId()),count);
}else{
printf("[MutexTest]Thevalueisout!\r\n");
osThreadTerminate(osThreadGetId());
}
}

那么我們可以不使用互斥鎖再次執行這段代碼60aa5c36-ebd1-11ee-9118-92fbcf53809c.png

結果展示如下:

60bda28c-ebd1-11ee-9118-92fbcf53809c.png

注:這里筆者還另外多加了3個線程,一共六個線程,可以看出來控制臺的輸出很混亂,當一個線程在執行輸出指令時,另一個線程也插了進來執行輸出指令所造成的,再看票數,也是出現了明顯的問題。因此互斥鎖在處理多線程問題時,起到了非常重要的作用??赡苡谢锇楹闷?,怎么沒有負數票的出現,筆者作為學習者,代碼能力也有限,可能寫出來的案例并不是非常精確,僅供參考。▍信號量

對大部分初學者而言,這又是一個新名詞,什么是信號量?其實他跟我們上篇介紹的互斥鎖很像?;コ怄i是在多線程中允許一個線程訪問資源,信號量是在多線程中允許多個線程訪問資源。

60cd7ac2-ebd1-11ee-9118-92fbcf53809c.png初學者一定會感到困惑,為了解決多線程訪問資源的風險我們限制只能有一個線程在某一時刻訪問資源,現在這個信號量怎么有允許多個線程訪問資源呢。筆者剛開始也比較困惑,結合一些案例理解后,也是明白了這樣的設計初衷。實際上,信號量,互斥鎖本就是兩種不同的多形成同步運行機制,在特定的應用場景下,有特定的需求,而信號量,互斥鎖可以滿足不同的需求,具體是什么需求呢,舉個例子給大家。賣票,我們的確需要互斥鎖解決多線程可能帶來的錯誤,那么如果是驗票呢,為了提高效率,我們開設多個入口同時驗票且不會發生沖突,信號量就做到了限制線程數量訪問資源的作用。如果我們不限制并發的數量,我們的程序占用資源可能會非常大,甚至崩潰,就像檢票的入口沒有被明確入口數量一樣,門口的人們會亂成一片。

信號量API

1.創建信號量

osSemaphoreId_tosSemaphoreNew(uint32_tmax_count,uint32_tinitial_count,constosSemaphoreAttr_t*attr);

參數解釋:最大容量量,初始容納量,信號量屬性最大容納量說明了,我們的資源最大能被多少線程訪問初始容納量說明了,我們當前實際能有多少線程訪問資源,因為一個信號對應一個線程的許可。返回值:信號量的id2.獲取信號量

osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout);

參數解釋:信號量的id,等待時長返回值:狀態碼 (介紹很多遍了,就不說明了)我們往往會在timoeout處設置為 oswaitForever

#defineosWaitForever0xFFFFFFFFU

這樣我們的線程就會一直等,直到有信號量空出來被他獲取,才執行后續的代碼。3.釋放信號量

osStatus_tosSemaphoreRelease(osSemaphoreId_tsemaphore_id);

很簡單,傳入信號量的id,就可以釋放一個信號量出來。其他的API請參考:Semaphore API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_sem

任務

有4個售票窗,2個檢票口,每次會有4個人來買票,然后去檢票,用互斥鎖控制購票,信號量控制檢票。

操作

1.新建樣例目錄applications/sample/wifi-iot/app/semaphore_demo2.新建源文件和gn文件applications/sample/wifi-iot/app/semaphore_demo/semaphore.capplications/sample/wifi-iot/app/semaphore_demo/BUILD.gn

60d5972a-ebd1-11ee-9118-92fbcf53809c.png

3.源碼編寫

直接上源碼了

#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"

//售票口4
#defineOUT_NUM4
//檢票口2
#defineIN_NUM2

//信號量
osSemaphoreId_tsid;

//待檢票人數
staticintpeople=0;

//售票業務
voidoutThread(void*args){
//獲取互斥鎖
osMutexId_t*mid=(osMutexId_t*)args;

while(1){
if(osMutexAcquire(*mid,100)==osOK){
//賣一張票,帶檢票的人數就會加一位
people++;
printf("[SEMAPHORETEST]out,people:%d.\r\n",people);
}
osMutexRelease(*mid);
osDelay(50);
}
}

//檢票業務
voidinThread(void*args){
//獲取信號量
osSemaphoreAcquire(sid,osWaitForever);
while(1){
if(people>0){
people--;
printf("[SEMAPHORETEST]in,people:%d.\r\n",people);
}
osSemaphoreRelease(sid);
osDelay(100);
}
}

//創建線程封裝
osThreadId_tcreateThreads(char*name,osThreadFunc_tfunc,void*args){
osThreadAttr_tattr={
name,0,NULL,0,NULL,1024*2,osPriorityNormal,0,0
};
osThreadId_ttid=osThreadNew(func,args,&attr);
returntid;
}

//主線程
voidSemaphoreMain(void){
//創建信號量
sid=osSemaphoreNew(IN_NUM,IN_NUM,NULL);

//創建互斥鎖
osMutexAttr_tattr={0};

//獲取互斥鎖的id
osMutexId_tmid=osMutexNew(&attr);

//創建售票線程
for(inti=0;icreateThreads("",(osThreadFunc_t)outThread,&mid);
}

//創建檢票線程
for(inti=0;icreateThreads("",(osThreadFunc_t)inThread,NULL);
}
}

//測試函數
voidMainTest(){
createThreads("MainTest",(osThreadFunc_t)SemaphoreMain,NULL);
}

APP_FEATURE_INIT(MainTest);

4.編寫gn文件

static_library("semaphore_demo"){
sources = [
"semaphore.c"
]
include_dirs = [
"http://utils/native/lite/include",
]
}

5.編寫app目錄下的gn文件60e27ca6-ebd1-11ee-9118-92fbcf53809c.png

結果展示

60fdaee0-ebd1-11ee-9118-92fbcf53809c.png大家可以加長檢票業務的休眠時間,我們的檢票口是兩個,in的業務一定是兩個一起執行的??傊盘柫亢突コ怄i是多線程管理中的重點,大家一定要好好體會他們的作用和區別。▍消息隊列本篇的最后我們來介紹消息隊列。隊列相信大部分朋友都不陌生,是一種基本且常用的數據結構,這里筆者就不介紹隊列的相關信息了。那么什么是消息隊列呢?有什么應用場景呢。
6115bc92-ebd1-11ee-9118-92fbcf53809c.png消息隊列也是多線程,高并發中的處理方式,大家可以理解為“同步入隊,異步出隊”。老樣子從一個案例解釋,網購秒殺。在網購秒殺時,會有上萬甚至上百萬的流量涌入服務器,下單即可看作一個請求,向服務器請求獲取某個商品。服務器處理,生成買家的訂單號。當然強大服務器的也無法在同一時刻支持如此多的請求,并且商品的數量也不足被所有人購買,這個時候,我們的消息隊列就會同步接受大家的請求,所有的請求都會被壓進一個隊列中,服務器從隊列中依次獲取消息,確保不會因為資源被占用而導致系統崩潰。6120f0bc-ebd1-11ee-9118-92fbcf53809c.png

#### 消息隊列API

1.創建消息隊列

osMessageQueueId_tosMessageQueueNew(uint32_tmsg_count,uint32_tmsg_size,constosMessageQueueAttr_t*attr);

參數說明:

  • msg_count:消息隊列中的消息數量。
  • msg_size:消息隊列中每個消息的大小,通常我們的消息會用一個結構體來自定義消息的內容
  • attr:指向消息隊列屬性。

該函數的返回值是osMessageQueueId_t類型,表示消息隊列的ID。如果創建消息隊列失敗,函數將返回NULL。2.向消息隊列加入消息

osStatus_tosMessageQueuePut(osMessageQueueId_tmq_id,constvoid*msg_ptr,uint8_tmsg_prio,uint32_ttimeout);

參數說明:

  • mq_id:消息隊列的ID,通過調用osMessageQueueNew函數獲得。
  • msg_ptr:指向要放入消息隊列的消息緩沖區的指針,也就是我們將結構體的指針轉遞給函數
  • msg_prio:消息的優先級。
  • timeout:延時,使用osWaitForever,線程就會一直等待直到隊列中有空余的位置。

該函數的返回值是osStatus_t類型,表示函數執行的結果。3.從消息隊列中接受消息

osStatus_tosMessageQueueGet(osMessageQueueId_tmq_id,void*msg_ptr,uint8_t*msg_prio,uint32_ttimeout);

參數說明:

  • mq_id:消息隊列的ID,通過調用osMessageQueueNew函數獲得。
  • msg_ptr:指向存儲從消息隊列中獲取的消息的緩沖區的指針。
  • msg_prio:指向存儲從消息隊列中獲取的消息的優先級的緩沖區的指針。
  • timeout:延時,使用osWaitForever,線程就會一直等待直到隊列中有消息了。

該函數的返回值是osStatus_t類型,表示函數執行的結果。4.刪除消息隊列

osStatus_tosMessageQueueDelete(osMessageQueueId_tmq_id);

參數說明:

  • mq_id:消息隊列的ID,通過調用osMessageQueueNew函數獲得。

該函數的返回值是osStatus_t類型,表示函數執行的結果。其他的API請參考:MessageQueue API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_msgQueue

任務

模擬搶購秒殺,假設我們有10個線程,15個大小的消息隊列,5件商品。

操作

1.新建樣例目錄applications/sample/wifi-iot/app/queue_demo2.新建源文件和gn文件applications/sample/wifi-iot/app/queue_demo/queue.capplications/sample/wifi-iot/app/queue_demo/BUILD.gn

612b170e-ebd1-11ee-9118-92fbcf53809c.png

3.編寫源碼

直接上源碼

#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"

//定義消息隊列的大小
#defineQUEUE_SIZE15

//定義請求數量
#defineREQ_SIZE10

//定義消息的結構
typedefstruct{
osThreadId_ttid;
}message_queue;

//創建消息隊列id
osMessageQueueId_tqid;

//模擬發送業務
voidsendThread(void){
//定義一個消息結構
message_queuesentry;
sentry.tid=osThreadGetId();

osDelay(100);

//消息入隊
osMessageQueuePut(qid,(constvoid*)&sentry,0,osWaitForever);

//設置提示信息
printf("[MESSAGEQUEUETEST]%dsendamessage.\r\n",sentry.tid);
}

//模擬處理業務
voidreceiverThread(void){
//定義一個消息結構
message_queuerentry;
intless=5;
while(less>0){
osMessageQueueGet(qid,(void*)&rentry,NULL,osWaitForever);
less--;
printf("[MESSAGEQUEUETEST]%dgetaproduct,less=%d.\r\n",rentry.tid,less);
osDelay(5);
}
printf("[MESSAGEQUEUETEST]over!\r\n");
}

//創建線程封裝
osThreadId_tcreateThreads(char*name,osThreadFunc_tfunc,void*args){
osThreadAttr_tattr={
name,0,NULL,0,NULL,1024*2,osPriorityNormal,0,0
};
osThreadId_ttid=osThreadNew(func,args,&attr);
returntid;
}

//主線程
voidMessageQueueMain(void){
//創建一個消息隊列
qid=osMessageQueueNew(QUEUE_SIZE,sizeof(message_queue),NULL);

//創建發送線程
for(inti=0;icreateThreads("",(osThreadFunc_t)sendThread,NULL);
}
osDelay(5);

//創建接收線程
createThreads("",(osThreadFunc_t)receiverThread,NULL);

osDelay(500);

//刪除消息隊列
osMessageQueueDelete(qid);
}

//測試函數
voidMainTest(){
createThreads("MainTest",(osThreadFunc_t)MessageQueueMain,NULL);
}

APP_FEATURE_INIT(MainTest);

4.編寫gn

static_library("queue_demo"){
sources=[
"queue.c",
]
include_dirs=[
"http://utils/native/lite/include",
]
}

5.編寫app目錄下的gn

61322300-ebd1-11ee-9118-92fbcf53809c.png

結果展示

61467f6c-ebd1-11ee-9118-92fbcf53809c.png因為線程創建是循環創建的,先創建的線程就優先發送了請求,可以看的出來,前五個線程搶到了商品。如果線程可以同時發送請求,爭搶入隊的時機,模擬將會更加準確一些,這里只是簡單的模擬。

結束語

至此內核的基礎內容就給伙伴們介紹完了,內核作為一個系統的底層起到了相當重要的作用,大家要好好體會,希望能幫助到學習OpenHarmony的伙伴們。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內核
    +關注

    關注

    3

    文章

    1312

    瀏覽量

    39896
  • 開發板
    +關注

    關注

    25

    文章

    4526

    瀏覽量

    94556
  • OpenHarmony
    +關注

    關注

    24

    文章

    3400

    瀏覽量

    15212
收藏 人收藏

    評論

    相關推薦

    [HarmonyOS][鴻蒙專欄開篇]快速入門OpenHarmony的LiteOS微內核

    ://openharmony.gitee.com]倉庫本專欄主要分析`OpenHarmony`內核的相關代碼:Cortex-A系列處理器內核:[kernel_liteos_a](htt
    發表于 09-14 19:40

    OpenHarmony LiteOS-A內核介紹

    簡介OpenHarmony LiteOS-A內核是基于Huawei LiteOS內核演進發展的新一代內核,Huawei LiteOS是面向IoT領域構建的輕量級物聯網操作系統。在IoT
    發表于 08-19 10:46

    OpenHarmony支持的系統類型及內核

    本文介紹OpenHarmony所不同的系統類型以及支持的內核。系統類型OpenHarmony支持以下幾種系統類型:輕量系統(迷你系統)給定的有限MCU類處理器例如Arm Cortex-M
    發表于 04-19 11:29

    OpenHarmony內核學習[1]--單獨編譯OpenHarmony標準系統內核

    內核是操作系統的核心,學習掌握OpenHarmony內核對于開發人員至關重要。筆者整理學習OpenHarmony標準系統內核筆記如下:閱讀本
    發表于 02-03 10:46

    OpenHarmony瘦設備內核移植實戰(一)

    一、背景 在各行各業存在很多不同的智能設備,每個設備都使用芯片去實現不同的業務場景需求。本文將以常用的STM32F407ZG芯片為例,介紹OpenHarmony瘦設備內核移植方法,希望能對熱愛
    發表于 04-24 16:04

    Java并發編程實戰

    Java并發編程實戰
    發表于 03-19 11:24 ?7次下載

    openharmony內核分析

    OpenHarmony內核的源代碼分為 kernel_liteos_a 和 kernel_liteos_m 這2個代碼倉庫,其中kernel_liteos_a主要針對Cortex-A系列處理器,而kernel_liteos_m則主要針對Cortex-M系列處理器,兩者目錄
    的頭像 發表于 06-24 09:39 ?2695次閱讀

    淺談OpenHarmony內核以及OpenHarmony開發語言

    、java、Python、C++ 等等。 OpenHarmony內核有兩個,一個是liteos_A另一個是lite_M。 要說這倆有啥區別呢?按照我個人的理解大概就是liteos_A 是ARM架構
    的頭像 發表于 06-24 09:47 ?3520次閱讀

    OpenHarmony內核是什么

    OpenHarmony源碼里面包含兩個內核代碼,分別是liteos_A和lite_M。
    的頭像 發表于 06-24 10:08 ?2618次閱讀

    OpenHarmony內核任務間IPC原理

    身為深開鴻 OS 內核開發師,我們常年深耕于 OpenHarmony內核開發,希望通過分享一些工作上的經驗,幫助大家掌握開源知識。
    的頭像 發表于 07-12 16:45 ?1403次閱讀

    議程搶鮮看|OpenHarmony技術峰會——OS內核及視窗分論壇

    點擊藍字 ╳ 關注我們 開源項目 OpenHarmony 是每個人的 OpenHarmony 原文標題:議程搶鮮看|OpenHarmony技術峰會——OS內核及視窗分論壇 文章出處:【
    的頭像 發表于 02-23 18:50 ?459次閱讀

    OpenHarmony瘦設備內核移植實戰(一)

    背景 在各行各業存在很多不同的智能設備,每個設備都使用芯片去實現不同的業務場景需求。本文將以常用的STM32F407ZG芯片為例,介紹OpenHarmony瘦設備內核移植方法,希望能對熱愛
    的頭像 發表于 05-11 20:16 ?668次閱讀
    <b class='flag-5'>OpenHarmony</b>瘦設備<b class='flag-5'>內核</b>移植<b class='flag-5'>實戰</b>(一)

    Python編程實戰(源代碼)

    [源代碼]Python編程實戰 妙趣橫生的項目之旅
    發表于 06-06 17:49 ?1次下載

    Liteos-a內核工作隊列的實現原理分析及經驗總結——芯??萍糚PG芯片CS1262接入OpenHarmony實戰

    摘要OpenHarmony系統中使用了liteos-m、liteos-a、linux三種內核,工作隊列是linux內核引入的一種異步處理機制。本文對liteos-a內核下工作隊列的實現
    的頭像 發表于 04-26 09:26 ?1408次閱讀
    Liteos-a<b class='flag-5'>內核</b>工作隊列的實現原理分析及經驗總結——芯??萍糚PG芯片CS1262接入<b class='flag-5'>OpenHarmony</b><b class='flag-5'>實戰</b>

    OpenHarmony輕量系統書籍推薦《OpenHarmony輕量設備開發理論與實戰

    、OpenHarmony內核編程接口、控制I/O設備、感知環境狀態、OLED顯示屏的驅動和控制、控制Wi-Fi、網絡編程以及MQTT編程。案
    的頭像 發表于 07-20 12:43 ?702次閱讀
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>