在正式開始之前,對于剛接觸OpenHarmony的伙伴們,面對大篇幅的源碼可能無從下手,不知道怎么去編碼寫程序,下面用一個簡單的例子帶伙伴們?nèi)腴T。
▍任務編寫程序,讓開發(fā)板在串口調(diào)試工具中輸出”Hello,OpenHarmony“。
▍操作
在源碼的根目錄中有名為”applications“的文件,他存放著應用程序樣例,下面是他的目錄結構:
我們要編寫的程序樣例就在源碼根目錄下的: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
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
筆者的代碼版本是OpenHarmony3.2Release版,不同版本的源碼可能庫所存放的路徑不同,那么怎么去找到對應的庫呢,對于不熟悉源碼結構的伙伴們學習起來很不友好。對于在純Windows環(huán)境開發(fā)的伙伴們,筆者推薦使用everything這款工具,它可以快速查找主機中的文件,比在資源管理器的搜索快上不少。everything似乎不能找到我WSL中的Ubuntu中的文件,因此對于Windows + Linux環(huán)境下的伙伴們,這款工具又不那么適用。那就可以根據(jù)Linux的查詢指令來定位文件所在目錄,下面提供查詢案例防止有不熟悉Linux的伙伴們。我們使用locate指令來查找文件。首先安裝locate
sudoaptinstallmlocate
更新mlocate.db
sudoupdatedb
查詢文件目錄
locateohos_init.h
找到我們源碼根目錄下 include路徑下的ohos_init.h文件
4.編寫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的所在目錄
5.編寫app下的gn文件在app的目錄下也有一個gn文件,我們只需要去修改他即可
這表示我們的程序將會執(zhí)行hello_demo樣例中的sayHello模塊
6.編譯,燒錄,串口調(diào)試這一步就屬于基礎操作了,不做過多贅述,7.觀察控制臺的輸出
至此編碼完成了編碼入門,下面就具體介紹OpenHarmony的內(nèi)核編程。
內(nèi)核
▍內(nèi)核介紹
什么是內(nèi)核?或者說內(nèi)核在一個操作系統(tǒng)中起到一個什么樣的作用?相信初次接觸這個詞的伙伴們也會有同樣的疑問。不過不用擔心,筆者會盡可能地通俗地介紹內(nèi)核的相關知識,以便大家能夠更好地去體會內(nèi)核編程。
我們先來看一張圖,這是OpenHarmony官網(wǎng)發(fā)布的技術架構圖
我們可以看到最底層叫做內(nèi)核層,有Linux,LiteOS等。內(nèi)核在整個架構,或者操作系統(tǒng)中起到一個核心作用,他負責管理計算機系統(tǒng)內(nèi)的資源和硬件設備,提供給頂層的應用層一個統(tǒng)一規(guī)范的接口,從而使得整個系統(tǒng)能夠完成應用與硬件的交互。
具體點來說,內(nèi)核可以做以下相關的工作:1.進程管理2.內(nèi)存管理3.文件資源管理4.網(wǎng)絡通信管理5.設備驅動管理當然不局限于這些,這里只是給出具體的例子供伙伴們理解,如果實在難以理解,那么筆者再舉一個例子,進程。可能你沒聽過進程,但你一定打開過任務管理器。
這些都是進程,一個進程又由多個線程組成。那么CPU,內(nèi)存,硬盤,網(wǎng)絡這些硬件層面資源是怎么合理分配到我們軟件的各個進程中呢?這就是內(nèi)核幫助我們完成的事情,我們并不關心我們設備上的應用在哪里執(zhí)行,如何分配資源,內(nèi)核會完成這些事情。我們?nèi)粘Ec軟件交互,而內(nèi)核會幫助我們完成軟件和硬件的交互。
▍OpenHarmony內(nèi)核明白了什么是內(nèi)核后,我們來看看OpenHarmony的內(nèi)核是怎么樣設計的吧。OpenHarmony采用的是多內(nèi)核設計 有基于Linux內(nèi)核的標準系統(tǒng),有基于LiteOS-A的小型系統(tǒng),也有基于LiteOS-M的輕量系統(tǒng)。他們分別適配不同的設備,比如說智能手表就是輕量級別的,智能汽車就是標準級別的等等。本篇并不介紹標準系統(tǒng)和小型系統(tǒng),輕量系統(tǒng)更加適合初學者。▍LiteOS-M內(nèi)核
下面是一張LiteOS-M的架構圖
下面重點介紹KAL抽象層 和 基礎內(nèi)核的操作
KAL抽象層
相信大家還是會有疑惑,什么是KAL抽象層?Kernel Abstraction Layer在剛剛的內(nèi)核中我們提到了,內(nèi)核主要完成的是軟件與硬件的交互,他會給應用層提供統(tǒng)一的規(guī)范接口,而KAL抽象層正是內(nèi)核對應用層提供的接口集合。應用程序可以通過KAL抽象層完成對硬件的控制交互。抽象層是因為他隱藏了與硬件接口具體的交互邏輯,開發(fā)人員只需要關心如何操作硬件,而無需關心硬件底層的細節(jié),大大提高了可移植性和維護性。以筆者的角度去看,KAL簡單來說就是一堆接口,幫助你去操控硬件。CMSIS與POSIX就是具有統(tǒng)一規(guī)范的一些接口。通過他們我們就可以去控制一些基礎的內(nèi)核,線程,軟件定時器,互斥鎖,信號量等等。概念就先簡單介紹這么多,感興趣的伙伴們可以上官網(wǎng)查看更多的關于OpenHarmony內(nèi)核的信息。下面筆者會帶著大家編碼操作,從實際去體會內(nèi)核編程。
內(nèi)核編程
▍線程管理
在管理線程前,我們需要了解線程,線程是調(diào)度的基本單位,具有獨立的棧空間和寄存器上下文,相比與進程,他是輕量的。舉一個實際的例子,動物園賣票。
對于動物園賣票這件事本身而言是一個進程,而每一個買票的人可以看作一個線程,在多個售票口處,我們并發(fā)執(zhí)行,并行計算,共同消費動物園的門票,像享受共同的內(nèi)存資源空間一樣。為什么要線程管理呢?你我都希望買到票,但是票有限,我們都不希望看到售票廳一篇混亂,因此對線程進行管理是非常重要的一件事情。
任務
創(chuàng)建一個線程,每間隔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
3.編寫源碼注意:我們需要使用到cmsis_os2.h這個庫,請伙伴們按照筆者介紹的方法把includePath修改好。問題一:怎么創(chuàng)建線程?
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:線程控制塊的內(nèi)存地址。
- cb_size:線程控制塊的內(nèi)存大小。
- stack_mem:線程棧的內(nèi)存地址。
- stack_size:線程棧的大小。
- priority:線程的優(yōu)先級。
- tz_module:線程所屬的TrustZone模塊。
- reserved:保留字段。
問題二:怎么把線程啟動起來呢?
osThreadId_tosThreadNew(osThreadFunc_tfunc,void*argument,constosThreadAttr_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_tosThreadTerminate(osThreadId_tthread_id);
顯然我們只要傳入線程的id就會讓該線程終止,返回值是一個狀態(tài)碼,下面給出全部的狀態(tài)碼
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;
回調(diào)函數(shù)怎么寫?當然是結合我們的任務,每間隔0.1秒,輸出“Hello,OpenHarmony”,1秒后終止。講到這里,代碼的整體邏輯是不是就清晰了很多,直接上完整代碼。
#include
#include"ohos_init.h"
//CMSIS
#include"cmsis_os2.h"
//POSIX
#include
//線程回調(diào)函數(shù)
voidprintThread(void*args){
(void)args;
while(1){
printf("Hello,OpenHarmony!\r\n");
//休眠0.1秒
osDelay(10);
}
}
voidthreadTest(void){
//創(chuàng)建線程
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文件
注意的是,這次的寫法與上次不同,是因為筆者的樣例文件名和靜態(tài)模塊的名字是一樣的就可以簡寫。
執(zhí)行效果
▍多線程的封裝
在處理業(yè)務的時候,我們一般是多線程的背景,下面筆者將創(chuàng)建線程函數(shù)封裝起來,方便大家創(chuàng)建多線程
osThreadId_tnewThread(char*name,osThreadFunc_tfunc,void*arg){
//定義線程和屬性
osThreadAttr_tattr={
name,0,NULL,0,NULL,1024,osPriorityNormal,0,0
};
//創(chuàng)建線程
osThreadId_ttid=osThreadNew(func,arg,&attr);
if(tid==NULL){
printf("[newThread]osThreadNew(%s)failed.\r\n",name);
}
returntid;
}
線程部分先體會到這里,想要探索更過線程相關的API,筆者這里提供了API網(wǎng)站,供大家參考學習。
CMSIS_OS2 Thread API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_threadMgmt
軟件定時器
下面我們介紹軟件定時器,老樣子我們先來介紹以下軟件定時器。軟件定時器是一種在軟件層面上實現(xiàn)的計時器機制,用于在特定的時間間隔內(nèi)執(zhí)行特定的任務或觸發(fā)特定的事件。它不依賴于硬件定時器,而是通過軟件編程的方式實現(xiàn)。舉一個例子,手機應用。
當你使用手機上的某個應用時,你可能會注意到,如果你在一段時間內(nèi)沒有進行任何操作,應用程序會自動斷開連接并要求你重新登錄。這是為了保護你的賬號安全并釋放服務器資源。類似的設定都是有軟件定時器實現(xiàn)的,下面進行實際操作,讓大家體會一下軟件定時器。
任務
創(chuàng)建一個軟件定時器,用來模擬上述手機應用的例子。為了方便理解,假設從此刻開始,我們不對手機做任何操作,也就是說,我們的回調(diào)函數(shù)只需要單純的計算應用不被操作的時常即可。
操作
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.編寫源碼創(chuàng)建軟件定時器
osTimerId_tosTimerNew(osTimerFunc_tfunc,osTimerType_ttype,void*argument,constosTimerAttr_t*attr);
func: 軟件定時器的回調(diào)函數(shù)type:軟件定時器的種類argument:軟件定時器回調(diào)函數(shù)的參數(shù)attr:軟件定時器的屬性返回值:返回軟件定時器的id, id為空則說明軟件定時器失敗
typedefenum{
/**One-shottimer*/
osTimerOnce=0,
/**Repeatingtimer*/
osTimerPeriodic=1
}osTimerType_t;
軟件定時器的種類有兩個,分為一次性定時器和周期性定時器,一次性在執(zhí)行完回調(diào)函數(shù)后就會停止計數(shù),而周期性定時器會重復觸發(fā),每次觸發(fā)重新計時。根據(jù)不同的需求我們可以選擇使用不同的軟件定時器。啟動軟件定時器
osStatus_tosTimerStart(osTimerId_ttimer_id,uint32_tticks);
timer_id:軟件定時器的參數(shù),指定要啟動哪個軟件定時器ticks:等待多少個ticks執(zhí)行回調(diào)函數(shù),在Hi3861中 100個ticks為1秒返回值:軟件定時器的狀態(tài)碼,在線程部分已經(jīng)展示給大家了全部的狀態(tài)碼停止定時器
osStatus_tosTimerStop(osTimerId_ttimer_id);
這個函數(shù)很簡單,只需要傳軟件定時器的id,即可停止軟件計時器,并且返回他的狀態(tài)碼刪除定時器
osStatus_tosTimerDelete(osTimerId_ttimer_id);
刪除和停止類似,就不多說明了。下面是源代碼
#include
#include"ohos_init.h"
//CMSIS
#include"cmsis_os2.h"
//POSIX
#include
//為操作軟件的時間
staticinttimes=0;
//軟件定時器回調(diào)函數(shù)
voidtimerFunction(void){
times++;
printf("[TimerTest]TimerisRunning,times=%d.\r\n",times);
}
//主函數(shù)
voidtimerMain(void){
//創(chuàng)建軟件定時器
osTimerId_ttid=osTimerNew(timerFunction,osTimerPeriodic,NULL,NULL);
if(tid==NULL){
printf("[TimerTest]Failedtocreateatimer!\r\n");
return;
}else{
printf("[TimerTest]Createatimersuccess!\r\n");
}
//啟動軟件定時器,每1秒執(zhí)行一次回調(diào)函數(shù)
osStatus_tstatus=osTimerStart(tid,100);
//當超過三個周期位操作軟件時,關閉軟件
while(times<=?3){
osDelay(100);
}
//停止軟件定時器
status=osTimerStop(tid);
//刪除軟件定時器
status=osTimerDelete(tid);
printf("[TimerTest]TimeOut!\r\n");
}
voidTimerTest(void){
//創(chuàng)建測試線程
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文件
執(zhí)行效果
軟件定時器的API相對較少,這里還是提供所有的軟件定時器APICMSIS_OS2 Timer API:https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html#mig_timer▍互斥鎖線程的狀態(tài)
在介紹互斥鎖之前,我們有必要去了解一下線程的狀態(tài),或者說線程的生命周期。避免伙伴們因為不夠熟悉線程而對這個互斥鎖的概念感到困難。首先介紹一下線程的幾個狀態(tài),他們分別有:
- 創(chuàng)建
創(chuàng)建線程,在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:線程控制塊的內(nèi)存地址。
- cb_size:線程控制塊的內(nèi)存大小。
- stack_mem:線程棧的內(nèi)存地址。
- stack_size:線程棧的大小。
- priority:線程的優(yōu)先級。
- tz_module:線程所屬的TrustZone模塊。
- reserved:保留字段。
當我們創(chuàng)建一個線程的時候,系統(tǒng)就會為該線程分配所需要的資源,將線程加入到系統(tǒng)的線程調(diào)度隊列中,此時線程已經(jīng)處在就緒狀態(tài)了。
- 就緒
線程一旦被創(chuàng)建,就會進入就緒狀態(tài),他表示我們完成的線程的創(chuàng)建(線程相關屬性的初始化),但是并未運行,線程正在等待操作系統(tǒng)調(diào)度程序,將其調(diào)度運行起來。
- 運行
之前我們介紹了一個關于線程的API,他可以將就緒狀態(tài)的線程加入到活躍線程組
osThreadId_tosThreadNew(osThreadFunc_tfunc,void*argument,constosThreadAttr_t*attr);
此時的線程將會占用部分cpu資源執(zhí)行他的程序,直到程序結束,或者線程被阻塞,cpu被搶占等。
- 阻塞
線程阻塞,可以理解為線程無法繼續(xù)往下執(zhí)行,但時線程執(zhí)行的程序不會直接退出,他會進入到等待的狀態(tài),直到相關資源被釋放,線程可以繼續(xù)執(zhí)行。
- 終止
上篇我們介紹了線程終止的API
sStatus_tstatus=osThreadTerminate(osThreadId_ttid);
此時線程完成了自己的代碼邏輯,我們方可主動調(diào)用該API徹底刪除這個線程。當然如果具體細分,其實不止這五個狀態(tài),但是了解了這五個狀態(tài)就足夠了。下面我們來聊聊什么是互斥鎖。互斥鎖簡介
互斥鎖的應用場景時處理多線程,資源訪問的問題。這里還是給大家舉一個例子:動物園賣票。
在我們的程序設計中,往往會有共有資源,每個線程都可以進來訪問這些資源。這里的共有資源就是我們的門票,一個售票口就是一個線程,當有人來窗口買票,我們的門票就會減少一張。當然一個動物園的流量時巨大的,我們不能只設立一個售票口,這樣的效率是很低的。京東淘寶搶購秒殺也一樣,我們必須設立多個窗口,在同一時刻為多個人處理業(yè)務。多線程解決了效率問題但也帶來了安全隱患。我們假設一個這樣的場景,動物園僅剩一張門票,但是有兩個在不同的窗口同時付了錢,當售票員為他們拿票的時候就會發(fā)現(xiàn),少了一張票,兩個人都付了錢都想要那張票,就陷入了一個死局,無奈動物園只能讓他們都進去。但是在程序中體現(xiàn)的可能就是一個bug,動物園的門票剩余數(shù)為:-1。
if(count>0){
count--;
}
售票的邏輯很簡單,只要票數(shù)大于零,還有票能賣,那就在此基礎上減掉一。問題就在于,兩個線程同時在count = 1的時候通過了if判斷,都執(zhí)行的count--;那么就會出現(xiàn)了count = -1,也就是票數(shù)為負 的bug結果。
互斥鎖的出現(xiàn)就很好地解決了一個問題,他能夠阻止上面兩個線程同時訪問資源的同步行為,也就是說當一個線程進入這個if語句后,別的線程都不能進入。形象起來說,就像對這段代碼加了鎖,只有有鑰匙的線程才能夠訪問它。
互斥鎖API
通過對幾個API的介紹,讓大家知道怎么為一段代碼加上互斥鎖
1.創(chuàng)建互斥鎖與線程的定義一樣,互斥鎖也封裝成了一個結構體
typedefstruct{
/**Mutexname*/
constchar*name;
/**Reservedattributebits*/
uint32_tattr_bits;
/**Memoryforthemutexcontrolblock*/
void*cb_mem;
/**Sizeofthememoryforthemutexcontrolblock*/
uint32_tcb_size;
}osMutexAttr_t;
- name:互斥鎖的名稱
- attr_bits:保留的屬性位
- cb_mem:互斥鎖控制塊的內(nèi)存
- cb_size:互斥鎖控制塊內(nèi)存的大小
2.獲取互斥鎖的id
osMutexId_tosMutexNew(constosMutexAttr_t*attr);
將我們上面定義的互斥鎖屬性傳入函數(shù),返回互斥鎖的id3.線程獲取互斥鎖
osStatus_tosMutexAcquire(osMutexId_tmutex_id,uint32_ttimeout);
傳入互斥鎖id,設置我們的延遲時間,當線程獲取到我們的互斥鎖時,返回的狀態(tài)是osOK。
下面是全部的狀態(tài)碼
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);
當我們的線程執(zhí)行完業(yè)務后需要把鎖釋放出來,讓別的線程獲取鎖,執(zhí)行業(yè)務。當然這個過程是線程之間的競爭,一個線程可能一直得不到鎖,一個線程也可能剛釋放鎖又獲得鎖,我們可以添加休眠操作,提高鎖在各個線程間的分配。
其他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
3.源碼編寫因為已經(jīng)介紹了主要的API,這里就直接給伙伴們上源碼了。
#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"
//模擬動物園門票數(shù)
staticintcount=100;
//售票業(yè)務線程
voidoutThread(void*args){
//獲取互斥鎖
osMutexId_t*mid=(osMutexId_t*)args;
//每個線程都在不停地買票
while(1){
//獲取鎖,進入業(yè)務流程
if(osMutexAcquire(*mid,100)==osOK){
if(count>0){
count--;
//設置提示信息
printf("[MutexTest]Thread%sgetavalue,thelessis%d.\r\n",osThreadGetName(osThreadGetId()),count);
}else{
//告知這些線程已經(jīng)沒有門票賣了,線程結束
printf("[MutexTest]Thevalueisout!\r\n");
osThreadTerminate(osThreadGetId());
}
}
//釋放鎖
osMutexRelease(*mid);
osDelay(5);
}
}
//創(chuàng)建線程封裝
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;
}
//主函數(shù)實現(xiàn)多線程的創(chuàng)建,執(zhí)行買票業(yè)務
voidmutexMain(void){
//創(chuàng)建互斥鎖
osMutexAttr_tattr={0};
//獲取互斥鎖的id
osMutexId_tmid=osMutexNew(&attr);
if(mid==NULL){
printf("[MutexTest]Failedtocreateamutex!\r\n");
}
//創(chuàng)建多線程
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文件
結果展示
可能有的伙伴們看到這里不太清晰,會覺得這段代碼真的上鎖了嗎
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());
}
}
那么我們可以不使用互斥鎖再次執(zhí)行這段代碼
結果展示如下:
注:這里筆者還另外多加了3個線程,一共六個線程,可以看出來控制臺的輸出很混亂,當一個線程在執(zhí)行輸出指令時,另一個線程也插了進來執(zhí)行輸出指令所造成的,再看票數(shù),也是出現(xiàn)了明顯的問題。因此互斥鎖在處理多線程問題時,起到了非常重要的作用。可能有伙伴好奇,怎么沒有負數(shù)票的出現(xiàn),筆者作為學習者,代碼能力也有限,可能寫出來的案例并不是非常精確,僅供參考。▍信號量
對大部分初學者而言,這又是一個新名詞,什么是信號量?其實他跟我們上篇介紹的互斥鎖很像。互斥鎖是在多線程中允許一個線程訪問資源,信號量是在多線程中允許多個線程訪問資源。
初學者一定會感到困惑,為了解決多線程訪問資源的風險我們限制只能有一個線程在某一時刻訪問資源,現(xiàn)在這個信號量怎么有允許多個線程訪問資源呢。筆者剛開始也比較困惑,結合一些案例理解后,也是明白了這樣的設計初衷。實際上,信號量,互斥鎖本就是兩種不同的多形成同步運行機制,在特定的應用場景下,有特定的需求,而信號量,互斥鎖可以滿足不同的需求,具體是什么需求呢,舉個例子給大家。賣票,我們的確需要互斥鎖解決多線程可能帶來的錯誤,那么如果是驗票呢,為了提高效率,我們開設多個入口同時驗票且不會發(fā)生沖突,信號量就做到了限制線程數(shù)量訪問資源的作用。如果我們不限制并發(fā)的數(shù)量,我們的程序占用資源可能會非常大,甚至崩潰,就像檢票的入口沒有被明確入口數(shù)量一樣,門口的人們會亂成一片。
信號量API
1.創(chuàng)建信號量
osSemaphoreId_tosSemaphoreNew(uint32_tmax_count,uint32_tinitial_count,constosSemaphoreAttr_t*attr);
參數(shù)解釋:最大容量量,初始容納量,信號量屬性最大容納量說明了,我們的資源最大能被多少線程訪問初始容納量說明了,我們當前實際能有多少線程訪問資源,因為一個信號對應一個線程的許可。返回值:信號量的id2.獲取信號量
osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout);
參數(shù)解釋:信號量的id,等待時長返回值:狀態(tài)碼 (介紹很多遍了,就不說明了)我們往往會在timoeout處設置為 oswaitForever
#defineosWaitForever0xFFFFFFFFU
這樣我們的線程就會一直等,直到有信號量空出來被他獲取,才執(zhí)行后續(xù)的代碼。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
3.源碼編寫
直接上源碼了
#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"
//售票口4
#defineOUT_NUM4
//檢票口2
#defineIN_NUM2
//信號量
osSemaphoreId_tsid;
//待檢票人數(shù)
staticintpeople=0;
//售票業(yè)務
voidoutThread(void*args){
//獲取互斥鎖
osMutexId_t*mid=(osMutexId_t*)args;
while(1){
if(osMutexAcquire(*mid,100)==osOK){
//賣一張票,帶檢票的人數(shù)就會加一位
people++;
printf("[SEMAPHORETEST]out,people:%d.\r\n",people);
}
osMutexRelease(*mid);
osDelay(50);
}
}
//檢票業(yè)務
voidinThread(void*args){
//獲取信號量
osSemaphoreAcquire(sid,osWaitForever);
while(1){
if(people>0){
people--;
printf("[SEMAPHORETEST]in,people:%d.\r\n",people);
}
osSemaphoreRelease(sid);
osDelay(100);
}
}
//創(chuàng)建線程封裝
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){
//創(chuàng)建信號量
sid=osSemaphoreNew(IN_NUM,IN_NUM,NULL);
//創(chuàng)建互斥鎖
osMutexAttr_tattr={0};
//獲取互斥鎖的id
osMutexId_tmid=osMutexNew(&attr);
//創(chuàng)建售票線程
for(inti=0;icreateThreads("",(osThreadFunc_t)outThread,&mid);
}
//創(chuàng)建檢票線程
for(inti=0;icreateThreads("",(osThreadFunc_t)inThread,NULL);
}
}
//測試函數(shù)
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文件
結果展示
大家可以加長檢票業(yè)務的休眠時間,我們的檢票口是兩個,in的業(yè)務一定是兩個一起執(zhí)行的。總之信號量和互斥鎖是多線程管理中的重點,大家一定要好好體會他們的作用和區(qū)別。▍消息隊列本篇的最后我們來介紹消息隊列。隊列相信大部分朋友都不陌生,是一種基本且常用的數(shù)據(jù)結構,這里筆者就不介紹隊列的相關信息了。那么什么是消息隊列呢?有什么應用場景呢。
消息隊列也是多線程,高并發(fā)中的處理方式,大家可以理解為“同步入隊,異步出隊”。老樣子從一個案例解釋,網(wǎng)購秒殺。在網(wǎng)購秒殺時,會有上萬甚至上百萬的流量涌入服務器,下單即可看作一個請求,向服務器請求獲取某個商品。服務器處理,生成買家的訂單號。當然強大服務器的也無法在同一時刻支持如此多的請求,并且商品的數(shù)量也不足被所有人購買,這個時候,我們的消息隊列就會同步接受大家的請求,所有的請求都會被壓進一個隊列中,服務器從隊列中依次獲取消息,確保不會因為資源被占用而導致系統(tǒng)崩潰。
#### 消息隊列API
1.創(chuàng)建消息隊列
osMessageQueueId_tosMessageQueueNew(uint32_tmsg_count,uint32_tmsg_size,constosMessageQueueAttr_t*attr);
參數(shù)說明:
- msg_count:消息隊列中的消息數(shù)量。
- msg_size:消息隊列中每個消息的大小,通常我們的消息會用一個結構體來自定義消息的內(nèi)容
- attr:指向消息隊列屬性。
該函數(shù)的返回值是osMessageQueueId_t類型,表示消息隊列的ID。如果創(chuàng)建消息隊列失敗,函數(shù)將返回NULL。2.向消息隊列加入消息
osStatus_tosMessageQueuePut(osMessageQueueId_tmq_id,constvoid*msg_ptr,uint8_tmsg_prio,uint32_ttimeout);
參數(shù)說明:
- mq_id:消息隊列的ID,通過調(diào)用osMessageQueueNew函數(shù)獲得。
- msg_ptr:指向要放入消息隊列的消息緩沖區(qū)的指針,也就是我們將結構體的指針轉遞給函數(shù)
- msg_prio:消息的優(yōu)先級。
- timeout:延時,使用osWaitForever,線程就會一直等待直到隊列中有空余的位置。
該函數(shù)的返回值是osStatus_t類型,表示函數(shù)執(zhí)行的結果。3.從消息隊列中接受消息
osStatus_tosMessageQueueGet(osMessageQueueId_tmq_id,void*msg_ptr,uint8_t*msg_prio,uint32_ttimeout);
參數(shù)說明:
- mq_id:消息隊列的ID,通過調(diào)用osMessageQueueNew函數(shù)獲得。
- msg_ptr:指向存儲從消息隊列中獲取的消息的緩沖區(qū)的指針。
- msg_prio:指向存儲從消息隊列中獲取的消息的優(yōu)先級的緩沖區(qū)的指針。
- timeout:延時,使用osWaitForever,線程就會一直等待直到隊列中有消息了。
該函數(shù)的返回值是osStatus_t類型,表示函數(shù)執(zhí)行的結果。4.刪除消息隊列
osStatus_tosMessageQueueDelete(osMessageQueueId_tmq_id);
參數(shù)說明:
- mq_id:消息隊列的ID,通過調(diào)用osMessageQueueNew函數(shù)獲得。
該函數(shù)的返回值是osStatus_t類型,表示函數(shù)執(zhí)行的結果。其他的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
3.編寫源碼
直接上源碼
#include
#include
#include"ohos_init.h"
#include"cmsis_os2.h"
//定義消息隊列的大小
#defineQUEUE_SIZE15
//定義請求數(shù)量
#defineREQ_SIZE10
//定義消息的結構
typedefstruct{
osThreadId_ttid;
}message_queue;
//創(chuàng)建消息隊列id
osMessageQueueId_tqid;
//模擬發(fā)送業(yè)務
voidsendThread(void){
//定義一個消息結構
message_queuesentry;
sentry.tid=osThreadGetId();
osDelay(100);
//消息入隊
osMessageQueuePut(qid,(constvoid*)&sentry,0,osWaitForever);
//設置提示信息
printf("[MESSAGEQUEUETEST]%dsendamessage.\r\n",sentry.tid);
}
//模擬處理業(yè)務
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");
}
//創(chuàng)建線程封裝
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){
//創(chuàng)建一個消息隊列
qid=osMessageQueueNew(QUEUE_SIZE,sizeof(message_queue),NULL);
//創(chuàng)建發(fā)送線程
for(inti=0;icreateThreads("",(osThreadFunc_t)sendThread,NULL);
}
osDelay(5);
//創(chuàng)建接收線程
createThreads("",(osThreadFunc_t)receiverThread,NULL);
osDelay(500);
//刪除消息隊列
osMessageQueueDelete(qid);
}
//測試函數(shù)
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
結果展示
因為線程創(chuàng)建是循環(huán)創(chuàng)建的,先創(chuàng)建的線程就優(yōu)先發(fā)送了請求,可以看的出來,前五個線程搶到了商品。如果線程可以同時發(fā)送請求,爭搶入隊的時機,模擬將會更加準確一些,這里只是簡單的模擬。
結束語
至此內(nèi)核的基礎內(nèi)容就給伙伴們介紹完了,內(nèi)核作為一個系統(tǒng)的底層起到了相當重要的作用,大家要好好體會,希望能幫助到學習OpenHarmony的伙伴們。
-
內(nèi)核
+關注
關注
3文章
1382瀏覽量
40422 -
開發(fā)板
+關注
關注
25文章
5121瀏覽量
98191 -
OpenHarmony
+關注
關注
25文章
3744瀏覽量
16577
發(fā)布評論請先 登錄
相關推薦
評論