嵌入式系統(tǒng)不只是ARM+Linux,不是只有安卓,凡是電子產(chǎn)品都可稱為嵌入式系統(tǒng)。物聯(lián)網(wǎng)行業(yè)的興起,也提升了FreeRTOS市場占有率。本文就是介紹FreeRTOS基礎(chǔ)及其應用,只是個人整理,可能存在問題,其目的只是簡要介紹系統(tǒng)的基礎(chǔ),只能作為入門資料。
目錄
一、 為什么要學習RTOS
二、 操作系統(tǒng)基礎(chǔ)
三、 初識 FreeRTOS
四、 任務
五、 隊列
六、 軟件定時器
七、 信號量
八、 事件
九、 任務通知
十、 內(nèi)存管理
十一、 通用接口
一、 為什么要學習 RTOS
進入嵌入式這個領(lǐng)域,入門首先接觸的是單片機編程,尤其是C51 單片機來,基礎(chǔ)的單片機編程通常都是指裸機編程,即不加入任何 RTOS(Real Time Operating System 實時操作系統(tǒng))。常用的有國外的FreeRTOS、μC/OS、RTX 和國內(nèi)的 RT-thread、Huawei LiteOS 和 AliOS-Things 等,其中開源且免費的 FreeRTOS 的市場占有率較高。
1.1 前后臺系統(tǒng)
在裸機系統(tǒng)中,所有的操作都是在一個無限的大循環(huán)里面實現(xiàn),支持中斷檢測。外部中斷緊急事件在中斷里面標記或者響應,中斷服務稱為前臺,main 函數(shù)里面的while(1)無限循環(huán)稱為后臺,按順序處理業(yè)務功能,以及中斷標記的可執(zhí)行的事件。小型的電子產(chǎn)品用的都是裸機系統(tǒng),而且也能夠滿足需求。
1.2 多任務系統(tǒng)
多任務系統(tǒng)的事件響應也是在中斷中完成的,但是事件的處理是在任務中完成的。如果事件對應的任務的優(yōu)先級足夠高,中斷對應的事件會立刻執(zhí)行。相比前后臺系統(tǒng),多任務系統(tǒng)的實時性又被提高了。
在多任務系統(tǒng)中,根據(jù)程序的功能,把這個程序主體分割成一個個獨立的,無限循環(huán)且不能返回的子程序,稱之為任務。每個任務都是獨立的,互不干擾的,且具備自身的優(yōu)先級,它由操作系統(tǒng)調(diào)度管理。加入操作系統(tǒng)后,開發(fā)人員不需要關(guān)注每個功能模塊之間的沖突,重心放在子程序的實現(xiàn)。缺點是整個系統(tǒng)隨之帶來的額外RAM開銷,但對目前的單片機的來影響不大。
1.3 學習RTOS的意義
學習 RTOS,一是項目需要,隨著產(chǎn)品要實現(xiàn)的功能越來越多,單純的裸機系統(tǒng)已經(jīng)不能完美地解決問題,反而會使編程變得更加復雜,如果想降低編程的難度,就必須引入 RTOS實現(xiàn)多任務管理。二是技能需要,掌握操作系統(tǒng),和基于RTOS的編程,實現(xiàn)更好的職業(yè)規(guī)劃,對個人發(fā)展尤其是錢途是必不可少的。
以前一直覺得學操作系統(tǒng)就必須是linux,實際每個系統(tǒng)都有其應用場景,對于物聯(lián)網(wǎng)行業(yè),殺雞焉用牛刀,小而美,且應用廣泛的FreeRTOS 是首選。有一個操作系統(tǒng)的基礎(chǔ),即使后續(xù)基于其他系統(tǒng)開發(fā)軟件,也可觸類旁通,對新技術(shù)快速入門。目前接觸的幾款芯片都是基于FreeRTOS。
如何學習RTOS?最簡單的就是在別人移植好的系統(tǒng)之上,看看 RTOS 里面的 API 使用說明,然后調(diào)用這些 API 實現(xiàn)自己想要的功能即可。完全不用關(guān)心底層的移植,這是最簡單快速的入門方法。這種學習方式,如果是做產(chǎn)品,可以快速的實現(xiàn)功能,弊端是當程序出現(xiàn)問題的時候,如果對RTOS不夠了解,會導致調(diào)試困難,無從下手。
各種RTOS內(nèi)核實現(xiàn)方式都差不多,我們只需要深入學習其中一款就行。萬變不離其宗,正如掌握了C51基礎(chǔ),后續(xù)換其他型號或者更高級的ARM單片機,在原理和方法上,都是有借鑒意義,可以比較快的熟悉并掌握新單片機的使用。
二、 操作系統(tǒng)基礎(chǔ) 2.1 鏈表
鏈表作為 C 語言中一種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),在平時寫程序的時候用的并不多,但在操作系統(tǒng)里面使用的非常多。FreeRTOS 中存在著大量的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)鏈表和鏈表項的操作(list 和 list item)。FreeRTOS 中與鏈表相關(guān)的操作均在 list.h 和 list.c 這兩個文件中實現(xiàn)。
鏈表比數(shù)組,最大優(yōu)勢是占用的內(nèi)存空間可以隨著需求擴大或縮小,動態(tài)調(diào)整。實際FreeRTOS中各種任務的記錄都是依靠鏈表動態(tài)管理,具體的可以參考源碼的任務控制塊tskTCB。任務切換狀態(tài),就是將對應的鏈表進行操作,鏈表操作涉及創(chuàng)建和插入、刪除和查找。
2.2 隊列
隊列是一種只允許在表的前端(front)進行刪除操作,而在表的后端(rear)進行插入操作。隊尾放入數(shù)據(jù),對頭擠出。先進先出,稱為FIFO
2.3 任務
在裸機系統(tǒng)中,系統(tǒng)的主體就是 main 函數(shù)里面順序執(zhí)行的無限循環(huán),這個無限循環(huán)里面 CPU 按照順序完成各種事情。在多任務系統(tǒng)中,根據(jù)功能的不同,把整個系統(tǒng)分割成一個個獨立的且無法返回的函數(shù),這個函數(shù)我們稱為任務。系統(tǒng)中的每一任務都有多種運行狀態(tài)。系統(tǒng)初始化完成后,創(chuàng)建的任務就可以在系統(tǒng)中競爭一定的資源,由內(nèi)核進行調(diào)度。
就緒(Ready):該任務在就緒列表中,就緒的任務已經(jīng)具備執(zhí)行的能力,只等待調(diào)度器進行調(diào)度,新創(chuàng)建的任務會初始化為就緒態(tài)。
運行(Running):該狀態(tài)表明任務正在執(zhí)行,此時它占用處理器,調(diào)度器選擇運行的永遠是處于最高優(yōu)先級的就緒態(tài)任務。
阻塞(Blocked):任務當前正在等待某個事件,比如信號量或外部中斷。
掛起態(tài)(Suspended):處于掛起態(tài)的任務對調(diào)度器而言是不可見的。
掛起態(tài)與阻塞態(tài)的區(qū)別,當任務有較長的時間不允許運行的時候,我們可以掛起任務,這樣子調(diào)度器就不會管這個任務的任何信息,直到調(diào)用恢復任務的 接口;而任務處于阻塞態(tài)的時候,系統(tǒng)還需要判斷阻塞態(tài)的任務是否超時,是否可以解除阻塞。
各任務運行時使用消息、信號量等方式進行通信,不能是全局變量。任務通常會運行在一個死循環(huán)中,不會退出,如果不再需要,可以調(diào)用刪除任務。
2.4 臨界區(qū)
臨界區(qū)就是一段在執(zhí)行的時候不能被中斷的代碼段。在多任務操作系統(tǒng)里面,對全局變量的操作不能被打斷,不能執(zhí)行到一半就被其他任務再次操作。一般被打斷,原因就是系統(tǒng)調(diào)度或外部中斷。對臨界區(qū)的保護控制,歸根到底就是對系統(tǒng)中斷的使能控制。在使用臨界區(qū)時,關(guān)閉中斷響應,對部分優(yōu)先級的中斷進行屏蔽,因此臨界區(qū)不允許運行時間過長。為了對臨界區(qū)進行控制,就需要使用信號量通信,實現(xiàn)同步或互斥操作。
三、 初識 FreeRTOS 3.1 FreeRTOS源碼
FreeRTOS 由美國的 Richard Barry 于 2003 年發(fā)布, 2018 年被亞馬遜收購,改名為 AWS FreeRTOS,版本號升級為 V10,支持MIT開源協(xié)議,亞馬遜收購 FreeRTOS 也是為了進入物聯(lián)網(wǎng)和人工智能,新版本增加了物聯(lián)網(wǎng)行業(yè)的網(wǎng)絡(luò)協(xié)議等功能。
FreeRTOS 是開源免費的,可從官網(wǎng) www.freertos.org 下載源碼和說明手冊。例如展銳的UIS8910使用的是V10。以FreeRTOSv10.4.1為例,包含 Demo 例程,Source內(nèi)核的源碼,License許可文件。
3.1.1 Source 文件夾
FreeRTOS/ Source 文件夾下的文件:
包括FreeRTOS 的通用的頭文件include和 C 文件,包括任務、隊列、定時器等,適用于各種編譯器和處理器,是通用的。
需要特殊處理適配的在portblle文件夾,其下內(nèi)容與編譯器和處理器相關(guān), FreeRTOS 要想運行在一個單片機上面,它們就必須關(guān)聯(lián)在一起,通常由匯編和 C 聯(lián)合編寫。通常難度比較高,不過一般芯片原廠提供移植好的接口文件。這里不介紹移植的方法,因為自己也不明白。
Portblle/MemMang 文件夾下存放的是跟內(nèi)存管理相關(guān)的,總共有五個 heap 文件,有5種內(nèi)存動態(tài)分配方式,一般物聯(lián)網(wǎng)產(chǎn)品選用 heap4.c 。
3.1.2 Demo 文件夾
里面包含了 FreeRTOS 官方為各個單片機移植好的工程代碼,F(xiàn)reeRTOS 為了推廣自己,會給針對不同半導體廠商的評估板實現(xiàn)基礎(chǔ)功能范例, Demo下就是參考范例。
3.1.3 FreeRTOSConfig.h配置
FreeRTOSConfig.h頭文件對FreeRTOS 所需的功能的宏均做了定義,需要根據(jù)應用情況配置合適的參數(shù),其作用類似MTK功能機平臺的主mak文件,部分定義如下:
1.# defineconfigUSE_PREEMPTION 1
2.# defineconfigUSE_IDLE_HOOK 0
3.# defineconfigUSE_TICK_HOOK 0
4.# defineconfigCPU_CLOCK_HZ ( SystemCoreClock )
5.# defineconfigTICK_RATE_HZ ( ( TickType_t ) 1000 )
例如系統(tǒng)時鐘tick等參數(shù)在就這個文件配置,具體作用可以看注釋。一般情況下使用SDK不需要改動,特殊情況下咨詢原廠再調(diào)整。
3.2 FreeRTOS 編碼規(guī)范
接觸一個新平臺或者SDK,明白它的編碼規(guī)范,文件作用,可以提高源碼閱讀效率,快速熟悉其內(nèi)部實現(xiàn)。
3.2.1 數(shù)據(jù)類型
FreeRTOS針對不同的處理器,對標準C的數(shù)據(jù)類型進行了重定義。
1.# defineportCHAR char
2.# defineportFLOAT float
3.# defineportDOUBLE double
4.# defineportLONG long
5.# defineportSHORT short
6.# defineportSTACK_TYPE uint32_t
7.# defineportBASE_TYPE long
應用編碼中,推薦使用的是下面這種風格。
1.typedefintint32_t;
2.typedefshort int16_t;
3.typedefcharint8_t;
4.typedefunsignedintuint32_t;
5.typedefunsignedshort uint16_t;
6.typedefunsignedcharuint8_t;
3.2.2 變量名
FreeRTOS 中,定義變量的時候往往會把變量的類型當作前綴,好處看到就知道其類型。
char 型變量的前綴是 c
short 型變量的前綴是 s
long 型變量的前綴是 l
復雜的結(jié)構(gòu)體,句柄等定義的變量名的前綴是 x
變量是無符號型的再加前綴 u,是指針變量則加前綴 p
3.2.3 函數(shù)名
函數(shù)名包含了函數(shù)返回值的類型、函數(shù)所在的文件名和函數(shù)的功能,如果是私有的函數(shù)則會加一個 prv(private)的前綴。
例如vTaskPrioritySet函數(shù)的返回值為 void 型,在 task.c 這個文件中定義。
3.2.4 宏
宏內(nèi)容是由大寫字母表示,前綴是小寫字母,表示該宏在哪個頭文件定義,如:
1.# definetaskYIELD portYIELD
表示該宏是在task.h。
3.2.5 個人解讀
1、編碼不缺編碼規(guī)范,但是實際使用中很難完全依照標準執(zhí)行,即使freeRTOS源碼也是如此。
2、關(guān)于函數(shù)或者宏定義中帶文件名的作用,使用Source Insight 編輯代碼,該前綴的意義不大。
3、規(guī)則是活的,只要所有人都按一個規(guī)則執(zhí)行,它就是標準。
3.3 FreeRTOS應用開發(fā)
關(guān)于freeRTOS的應用開發(fā),主要是任務的創(chuàng)建和調(diào)度,任務間的通信與同步,涉及隊列、信號量等操作系統(tǒng)通用接口。結(jié)合應用需求,涉及定時器、延時、中斷控制等接口。
特別說明,有些功能的實現(xiàn)方式有多種形式,只針對常用方式進行說明,例如task的創(chuàng)建,只說明動態(tài)創(chuàng)建方式,因為很少使用靜態(tài)方式。
四、 任務 4.1 創(chuàng)建任務
xTaskCreate使用動態(tài)內(nèi)存的方式創(chuàng)建一個任務。
1.ret = xTaskCreate((TaskFunction_t) master_task_main, /* 任務入口函數(shù) */( 1)
2.“MASTER”, /* 任務名字 */( 2)
3.64* 1024, /* 任務棧大小 */( 3)
4.NULL, , /* 任務入口函數(shù)參數(shù) */( 4)
5.TASK_PRIORITY_NORMAL, /* 任務的優(yōu)先級 */( 5)
6.&task_master_handler); /* 任務控制塊指針 */( 6)
創(chuàng)建任務就是軟件運行時的一個while(1)的入口,一般閱讀其他代碼,找到這個函數(shù),再跟蹤到任務入口函數(shù),學習基于freeRTOS系統(tǒng)的代碼,首先就是找到main和這個接口。
(1):任務入口函數(shù),即任務函數(shù)的名稱,需要我們自己定義并且實現(xiàn)。
(2):任務名字,字符串形式,最大長度由FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會被自動截掉,只是方便調(diào)試。
(3):任務堆棧大小,單位為字, 4 個字節(jié),這個要注意,否則系統(tǒng)內(nèi)存緊缺。
(4):任務入口函數(shù)形參,不用的時候配置為 0 或者NULL 即可。
(5) :任務的優(yōu)先級,在 FreeRTOS 中,數(shù)值越大優(yōu)先級越高,0代表最低優(yōu)先級?;谄銼DK開發(fā),可將自定義的所有業(yè)務功能task設(shè)為同一個優(yōu)先級,按時間片輪詢調(diào)度。
(6):任務控制塊指針,使用動態(tài)內(nèi)存的時候,任務創(chuàng)建函數(shù)xTaskCreate會返回一個指針指向任務控制塊,也可以設(shè)為NULL,因為任務句柄后期可以不使用。
4.2 開啟調(diào)度
當任務創(chuàng)建成功后處于就緒狀態(tài)(Ready),在就緒態(tài)的任務可以參與操作系統(tǒng)的調(diào)度。操作系統(tǒng)任務調(diào)度器只啟動一次,之后就不會再次執(zhí)行了,F(xiàn)reeRTOS 中啟動任務調(diào)度器的函數(shù)是 vTaskStartScheduler,并且啟動任務調(diào)度器的時候就不會返回,從此任務管理都由FreeRTOS 管理,此時才是真正進入實時操作系統(tǒng)中的第一步。
vTaskStartScheduler開啟調(diào)度時,順便會創(chuàng)建空閑任務和定時器任務。
FreeRTOS 為了任務啟動和任務切換使用了三個異常:SVC、PendSV 和SysTick。
SVC(系統(tǒng)服務調(diào)用,亦簡稱系統(tǒng)調(diào)用)用于任務啟動。
PendSV(可掛起系統(tǒng)調(diào)用)用于完成任務切換,它是可以像普通的中斷一樣被掛起的,它的最大特性是如果當前有優(yōu)先級比它高的中斷在運行,PendSV會延遲執(zhí)行,直到高優(yōu)先級中斷執(zhí)行完畢,這樣產(chǎn)生的PendSV 中斷就不會打斷其他中斷的運行。
SysTick 用于產(chǎn)生系統(tǒng)節(jié)拍時鐘,提供一個時間片,如果多個任務共享同一個優(yōu)先級,則每次 SysTick 中斷,下一個任務將獲得一個時間片。
FreeRTOS 中的任務是搶占式調(diào)度機制,高優(yōu)先級的任務可打斷低優(yōu)先級任務,低優(yōu)先級任務必須在高優(yōu)先級任務阻塞或結(jié)束后才能得到調(diào)度。相同優(yōu)先級的任務采用時間片輪轉(zhuǎn)方式進行調(diào)度(也就是分時調(diào)度),時間片輪轉(zhuǎn)調(diào)度僅在當前系統(tǒng)中無更高優(yōu)先級就緒任務存在的情況下才有效。
4.3 啟動方式
FreeRTOS有兩種啟動方式,效果一樣,看個人喜好。
第一種:main 函數(shù)中將硬件初始化, RTOS 系統(tǒng)初始化,所有任務的創(chuàng)建完成,最后一步開啟調(diào)度。目前看到的幾個芯片SDK都是這種方式。
第二種:main 函數(shù)中將硬件和 RTOS 系統(tǒng)先初始化好,只創(chuàng)建一個任務后就啟動調(diào)度器,然后在這個任務里面創(chuàng)建其它應用任務,當所有任務都創(chuàng)建成功后,啟動任務再把自己刪除。
4.4 任務創(chuàng)建源碼分析
xTaskCreate創(chuàng)建任務。
1.BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
2.constchar* constpcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
3.constconfigSTACK_DEPTH_TYPE usStackDepth,
4.void* constpvParameters,
5.UBaseType_t uxPriority,
6.TaskHandle_t * constpxCreatedTask )
7. {
8.TCB_t * pxNewTCB;
9.BaseType_t xReturn;
10.
11./* If the stack grows down then allocate the stack then the TCB so the stack
12. * does not grow into the TCB. Likewise if the stack grows up then allocate
13. * the TCB then the stack. */
14.# if( portSTACK_GROWTH 》 0 )
15.{
16./**/
17.}
18.# else/* portSTACK_GROWTH */
19.{
20.StackType_t * pxStack;
21.
22./* Allocate space for the stack used by the task being created. */
23.pxStack = pvPortMalloc( ( ( ( size_t) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc have at least the alignment required by the MCU‘s stack and this allocation is the stack. */
24.
25.if( pxStack != NULL)
26.{
27./* Allocate space for the TCB. */
28.pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc have at least the alignment required by the MCU’s stack, and the first member of TCB_t is always a pointer to the task‘s stack. */
29.
30.if( pxNewTCB != NULL)
31.{
32./* Store the stack location in the TCB. */
33.pxNewTCB-》pxStack = pxStack;
34.}
35.else
36.{
37./* The stack cannot be used as the TCB was not created. Free
38. * it again. */
39.vPortFree( pxStack );
40.}
41.}
42.else
43.{
44.pxNewTCB = NULL;
45.}
46.}
47.# endif/* portSTACK_GROWTH */
48.
49.if( pxNewTCB != NULL)
50.{
51.# if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
52.{
53./* Tasks can be created statically or dynamically, so note this
54. * task was created dynamically in case it is later deleted. */
55.pxNewTCB-》ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
56.}
57.# endif/* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
58.
59.prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL);
60.prvAddNewTaskToReadyList( pxNewTCB ); //將新任務加入到就緒鏈表候著
61.xReturn = pdPASS;
62.}
63.else
64.{
65.xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
66.}
67.
68.returnxReturn;
69.}
申請任務控制塊內(nèi)存,檢查配置參數(shù),初始化,將任務信息加入到就緒鏈表,等待調(diào)度。前面鏈表部分提到,freeRTOS的任務信息都是使用鏈表記錄,在task.c有
1.PRIVILEGED_DATA staticList_t pxReadyTasksLists[configMAX_PRIORITIES]; //就緒
2.PRIVILEGED_DATA staticList_t xDelayedTaskList1; //延時
3.PRIVILEGED_DATA staticList_t xDelayedTaskList2;
4.PRIVILEGED_DATA staticList_t xPendingReadyList; //掛起
5.PRIVILEGED_DATA staticList_t xSuspendedTaskList; //阻塞
分別記錄就緒態(tài)、阻塞態(tài)和掛起的任務,其中阻塞態(tài)有2個,是因為特殊考慮,時間溢出 的問題,實際開發(fā)單片機項目計時超過24h的可以借鑒。其中pxReadyTasksLists鏈表數(shù)組,其下標就是任務的優(yōu)先級。
4.5 任務調(diào)度源碼分析
創(chuàng)建完任務的時候,vTaskStartScheduler開啟調(diào)度器,空閑任務、定時器任務也是在開啟調(diào)度函數(shù)中實現(xiàn)的。
為什么要空閑任務?因為 FreeRTOS一旦啟動,就必須要保證系統(tǒng)中每時每刻都有一個任務處于運行態(tài)(Runing),并且空閑任務不可以被掛起與刪除,空閑任務的優(yōu)先級是最低的,以便系統(tǒng)中其他任務能隨時搶占空閑任務的 CPU 使用權(quán)。這些都是系統(tǒng)必要的東西,也無需自己實現(xiàn)。
1.voidvTaskStartScheduler( void)
2. {
3.BaseType_t xReturn;
4.
5./* Add the idle task at the lowest priority. */
6.# if( configSUPPORT_STATIC_ALLOCATION == 1 )
7.{
8./***/
9.}
10.# else/* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
11.{
12./*創(chuàng)建空閑任務*/
13.xReturn = xTaskCreate( prvIdleTask,
14.configIDLE_TASK_NAME,
15.configMINIMAL_STACK_SIZE,
16.( void* ) NULL,
17.portPRIVILEGE_BIT, //優(yōu)先級為0
18.&xIdleTaskHandle );
19.}
20.# endif/* configSUPPORT_STATIC_ALLOCATION */
21.
22.# if( configUSE_TIMERS == 1 )
23.{
24.if( xReturn == pdPASS )
25.{
26.//創(chuàng)建定時器task,接收開始、結(jié)束定時器等命令
27.xReturn = xTimerCreateTimerTask;
28.}
29.else
30.{
31.mtCOVERAGE_TEST_MARKER;
32.}
33.}
34.# endif/* configUSE_TIMERS */
35.
36.if( xReturn == pdPASS )
37.{
38./* freertos_tasks_c_additions_init should only be called if the user
39. * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT is defined, as that is
40. * the only macro called by the function. */
41.# ifdefFREERTOS_TASKS_C_ADDITIONS_INIT
42.{
43.freertos_tasks_c_additions_init;
44.}
45.# endif
46.
47.portDISABLE_INTERRUPTS;
48.
49.# if( configUSE_NEWLIB_REENTRANT == 1 )
50.{
51._impure_ptr = &( pxCurrentTCB-》xNewLib_reent );
52.}
53.# endif/* configUSE_NEWLIB_REENTRANT */
54.
55.xNextTaskUnblockTime = portMAX_DELAY;
56.xSchedulerRunning = pdTRUE;
57.xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
58.
59.portCONFIGURE_TIMER_FOR_RUN_TIME_STATS;
60.
61.traceTASK_SWITCHED_IN;
62.
63./* Setting up the timer tick is hardware specific and thus in the
64. * portable interface. */
65.if( xPortStartScheduler != pdFALSE )
66.{
67./* 系統(tǒng)開始運行 */
68.}
69.else
70.{
71./* Should only reach here if a task calls xTaskEndScheduler. */
72.}
73.}
74.else
75.{
76./*****/
77.}
4.6 任務狀態(tài)切換
FreeRTOS 系統(tǒng)中的每一個任務都有多種運行狀態(tài),具體如下:
? 任務掛起函數(shù)
vTaskSuspend
掛起指定任務,被掛起的任務絕不會得到 CPU 的使用權(quán)
vTaskSuspendAll
將所有的任務都掛起? 任務恢復函數(shù)
vTaskResume
vTaskResume
xTaskResumeFromISR
任務恢復就是讓掛起的任務重新進入就緒狀態(tài),恢復的任務會保留掛起前的狀態(tài)信息,在恢復的時候根據(jù)掛起時的狀態(tài)繼續(xù)運行。xTaskResumeFromISR 專門用在中斷服務程序中。無論通過調(diào)用一次或多次vTaskSuspend函數(shù)而被掛起的任務,也只需調(diào)用一次恢復即可解掛 。
? 任務刪除函數(shù)vTaskDelete用于刪除任務。當一個任務可以刪除另外一個任務,形參為要刪除任 務創(chuàng)建時返回的任務句柄,如果是刪除自身, 則形參為 NULL。
4.7 任務使用注意點
1、中斷服務函數(shù)是不允許調(diào)用任何會阻塞運行的接口。一般在中斷服務函數(shù)中只做標記事件的發(fā)生,然后通知任務,讓對應任務去執(zhí)行相關(guān)處理 。
2、將緊急的處理事件的任務優(yōu)先級設(shè)置偏高一些。
3、空閑任務(idle 任務)是 FreeRTOS 系統(tǒng)中沒有其他工作進行時自動進入的系統(tǒng)任務,永遠不會掛起空閑任務,不應該陷入死循環(huán)。
4、創(chuàng)建任務使用的內(nèi)存不要過多,按需申請。如果浪費太多,后續(xù)應用申請大空間可能提示內(nèi)存不足。
五、 隊列 5.1 隊列的概念
隊列用于任務間通信的數(shù)據(jù)結(jié)構(gòu),通過消息隊列服務,任務或中斷服務將消息放入消息隊列中。其他任務或者自身從消息隊列中獲得消息。實現(xiàn)隊列可以在任務與任務間、中斷和任務間傳遞信息。隊列操作支持阻塞等待,向已經(jīng)填滿的隊列發(fā)送數(shù)據(jù)或者從空隊列讀出數(shù)據(jù),都會導致阻塞,時間自定義。消息隊列的運作過程具如下:
5.2 隊列創(chuàng)建
xQueueCreate用于創(chuàng)建一個新的隊列并返回可用于訪問這個隊列的句柄。隊列句柄其實就是一個指向隊列數(shù)據(jù)結(jié)構(gòu)類型的指針。
1.master_queue = xQueueCreate( 50, sizeof( task_message_struct_t));
創(chuàng)建隊列,占用50個單元,每個單元為sizeof(task_message_struct_t)字節(jié),和 malloc比較類似。其最終使用的函數(shù)是 xQueueGenericCreate,后續(xù)信號量等也是使用它創(chuàng)建,只是最后的隊列類型不同。
申請內(nèi)存后,xQueueGenericReset再對其進行初始化,隊列的結(jié)構(gòu)體xQUEUE成員:
1.typedefstructQueueDefinition/* Theoldnamingconventionisusedtopreventbreakingkernelawaredebuggers. */
2. {
3.int8_t* pcHead; /*《 Points to the beginning of the queue storage area. */
4.int8_t* pcWriteTo; /*《 Points to the free next place in the storage area. */
5.//類型
6.union
7.{
8.QueuePointers_t xQueue; /*《 Data required exclusively when this structure is used as a queue. */
9.SemaphoreData_t xSemaphore; /*《 Data required exclusively when this structure is used as a semaphore. */
10.} u;
11.
12.//當前向隊列寫數(shù)據(jù)阻塞的任務列表或者從隊列取數(shù)阻塞的鏈表
13.List_t xTasksWaitingToSend;
14.List_t xTasksWaitingToReceive;
15.
16.//隊列里有多少個單元被占用,應用中需要
17.volatileUBaseType_t uxMessagesWaiting;
18.
19.UBaseType_t uxLength; /*《 The length of the queue defined as the number of items it will hold, not the number of bytes. */
20.UBaseType_t uxItemSize; /*《 The size of each items that the queue will hold. */
21.
22./******/
23.} xQUEUE;
5.3 隊列刪除
隊列刪除函數(shù) vQueueDelete需傳入要刪除的消息隊列的句柄即可,刪除之后這個消息隊列的所有信息都會被系統(tǒng)回收清空,而且不能再次使用這個消息隊列了。實際應用中很少使用。
5.4 向隊列發(fā)送消息
任務或者中斷服務程序都可以給消息隊列發(fā)送消息,當發(fā)送消息時,如果隊列未滿或者允許覆蓋入隊,F(xiàn)reeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據(jù)用戶指定的超時時間進行阻塞,消息發(fā)送接口很多,最簡單的是 xQueueSend,用于向隊列尾部發(fā)送一個隊列消息。消息以拷貝的形式入隊,該函數(shù)絕對不能在中斷服務程序里面被調(diào)用,中斷中必須使用帶有中斷保護功能的 xQueueSendFromISR來代替。
BaseType_t xQueueSend(QueueHandle_t xQueue, constvoid* pvItemToQueue, TickType_t xTicksToWait) ;
用于向隊列尾部發(fā)送一個隊列消息。
參數(shù)
xQueue 隊列句柄
pvItemToQueue 指針,指向要發(fā)送到隊列尾部的隊列消息。
xTicksToWait 隊列滿時,等待隊列空閑的最大超時時間。如果隊列滿并且xTicksToWait 被設(shè)置成 0,函數(shù)立刻返回。超時時間的單位為系統(tǒng)節(jié)拍周期 tick,延時為 portMAX_DELAY 將導致任務掛起(沒有超時)。
返回值
消息發(fā)送成功成功返回 pdTRUE,否則返回 errQUEUE_FULL。
xQueueSendToBack與xQueueSend完全相同, xQueueSendFromISR與 xQueueSendToBackFromISR,帶FromISR表示只能在中斷中使用,freeRTOS所以帶這個后綴的都是這個含義。xQueueSendToFront和QueueSendToFrontFromISR用于向隊列隊首發(fā)送一個消息。這些在任務中發(fā)送消息的函數(shù)都是 xQueueGenericSend展開的宏定義。
1.BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
2.constvoid* constpvItemToQueue,
3.TickType_t xTicksToWait,
4.constBaseType_t xCopyPosition ) //發(fā)送數(shù)據(jù)到消息隊列的位置
一般使用xQueueSend和xQueueSendFromISR,如不確定當前運行的是系統(tǒng)服務,還是中斷服務,一般ARM都支持查詢中斷狀態(tài)寄存器判斷,可以封裝一層接口,只管發(fā)消息,內(nèi)部判斷是否使用支持中斷嵌套的版本,UIS8910就是如此。特殊情況下,如發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包未收到服務器響應,期望立刻入隊再次發(fā)送它,可以xQueueSendToFront向隊頭發(fā)消息。
5.5 從隊列讀取消息
當任務試圖讀隊列中的消息時,可以指定一個阻塞超時時間,當且僅當消息隊列中有消息的時候,任務才能讀取到消息。如果隊列為空,該任務將保持阻塞狀態(tài)以等待隊列數(shù)據(jù)有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數(shù)據(jù),該任務將自動由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。當任務等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數(shù)據(jù),任務也會自動從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。所有的task主入口while循環(huán)體都是按這個執(zhí)行。例如:
1.staticvoidtrack_master_task_main
2. {
3.track_task_message_struct_tqueue_item = { 0};
4./****/
5.
6.while( 1)
7.{
8.if(xQueueReceive(master_queue, &queue_item, portMAX_DELAY)) //阻塞等待
9.{
10.track_master_task_msg_handler(&queue_item);
11.}
12.}
13.}
xQueueReceive用于從一個隊列中接收消息并把消息從隊列中刪除。如果不想刪除消息的話,就調(diào)用 xQueuePeek函數(shù)。xQueueReceiveFromISR與xQueuePeekFromISR是中斷版本,用于在中斷服務程序中接收一個隊列消息并把消息。這兩個函數(shù)只能用于中斷,是不帶有阻塞機制的,實際項目沒有使用。
5.6 查詢隊列使用情況
uxQueueMessagesWaiting查詢隊列中存儲的信息數(shù)目,具有中斷保護的版本為uxQueueMessagesWaitingFromISR。查詢隊列的空閑數(shù)目uxQueueSpacesAvailable。
5.7 隊列使用注意點
使用隊列函數(shù)需要注意以下幾點:
1、中斷中必須使用帶FromISR后綴的接口;
2、發(fā)送或者是接收消息都是以拷貝的方式進行,如果消息內(nèi)容過于龐大,可以將消息的地址作為消息進行發(fā)送、接收。
1.typedefstruct
2. {
3.TaskHandle_t src_mod_id;
4.intmessage_id;
5.int32_tparam;
6.union
7.{
8.int32_tresult;
9.int32_tsocket_id;
10.};
11.void* pvdata; //大數(shù)據(jù)使用動態(tài)申請內(nèi)存保存,隊列只傳遞指針
12.} track_task_message_struct_t;
3、隊列并不屬于任何任務,所有任務都可以向同一隊列寫入和讀出,一個隊列可以由多任務或中斷讀寫。
4、隊列的深度要結(jié)合實際,可以多申請點,前提是每個隊列單元盡可能小。
5、隊列存在一定限制,在隊頭沒有取出來之前,是無法取出第二個,和STL鏈表存在差異。
六、 軟件定時器 6.1 軟件定時器的概念
定時器有硬件定時器和軟件定時器之分,硬件定時器是芯片本身提供的定時功能精度高,并且是中斷觸發(fā)方式。軟件定時器是由操作系統(tǒng)封裝的接口,它構(gòu)建在硬件定時器基礎(chǔ)之上,使系統(tǒng)能夠提供不受硬件定時器資源限制,其實現(xiàn)的功能與硬件定時器也是類似的。
在操作系統(tǒng)中,通常軟件定時器以系統(tǒng)節(jié)拍周期為計時單位。系統(tǒng)節(jié)拍配置為configTICK_RATE_HZ,該宏在 FreeRTOSConfig.h 中,一般是100或者1000。根據(jù)實際系統(tǒng) CPU 的處理能力和實時性需求設(shè)置合適的數(shù)值,系統(tǒng)節(jié)拍周期的值越小,精度越高,但是系統(tǒng)開銷也將越大,因為這代表在 1 秒中系統(tǒng)進入時鐘中斷的次數(shù)也就越多。
6.2 軟件定時器創(chuàng)建
軟件定時器需先創(chuàng)建才允許使用,動態(tài)創(chuàng)建方式是xTimerCreate,返回一個句柄。軟件定時器在創(chuàng)建成功后是處于休眠狀態(tài)的,沒有開始計時運行。FreeRTOS的軟件定時器支持單次模式和周期模式。
單次模式:當用戶創(chuàng)建了定時器并啟動了定時器后,定時時間到了,只執(zhí)行一次回調(diào)函數(shù),之后不再執(zhí)行。周期模式:定時器會按照設(shè)置的定時時間循環(huán)執(zhí)行回調(diào)函數(shù),直到用戶將定時器停止或刪除。
實際項目中使用這種模式對單片機喂狗就比較省事。
1.TimerHandle_t xTimerCreate( constchar* constpcTimerName, //定時器名稱
2.constTickType_t xTimerPeriodInTicks, //定時時間
3.constUBaseType_t uxAutoReload, //是否自動重載
4.void* constpvTimerID, //回調(diào)函數(shù)的參數(shù)
5.TimerCallbackFunction_t pxCallbackFunction ) //回調(diào)函數(shù)
6.3 軟件定時器開啟
新創(chuàng)建的定時器沒有開始計時啟動,可以使用
xTimerStart、
xTimerReset、
xTimerStartFromISR 、xTimerResetFromISR
xTimerChangePeriod、xTimerChangePeriodFromISR
這些函數(shù)將其狀態(tài)轉(zhuǎn)換為活躍態(tài),開始運行。區(qū)別:如果定時器設(shè)定60秒間隔,已經(jīng)運行了30秒,reset是將定時器重置為原來設(shè)定的時間間隔,也就是重新開始延時60秒。ChangePeriod重新設(shè)置計時周期。
6.4 軟件定時器停止
xTimerStop 用于停止一個已經(jīng)啟動的軟件定時器,xTimerStopFromISR是中斷版本。
6.5 軟件定時器刪除
xTimerDelete用于刪除一個已經(jīng)被創(chuàng)建成功的軟件定時器,釋放資源,刪除之后不能再使用。實際項目中,任務和隊列都是按需創(chuàng)建,一直使用,但是定時器不使用的就應該刪除,并且刪除后一定要將句柄置為NULL。
6.6 軟件定時器源碼分析
軟件定時器任務是在系統(tǒng)開始調(diào)度的時候就被創(chuàng)建:vTaskStartScheduler—xTimerCreateTimerTask。
1.BaseType_t xTimerCreateTimerTask( void)
2. {
3.BaseType_t xReturn = pdFAIL;
4.
5.prvCheckForValidListAndQueue; //創(chuàng)建定時器任務的隊列
6.
7.if( xTimerQueue != NULL)
8.{
9.# if( configSUPPORT_STATIC_ALLOCATION == 1 )
10.{
11./**/
12.}
13.# else/* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
14.{
15.//創(chuàng)建定時器任務
16.xReturn = xTaskCreate( prvTimerTask,
17.configTIMER_SERVICE_TASK_NAME,
18.configTIMER_TASK_STACK_DEPTH,
19.NULL,
20.( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
21.&xTimerTaskHandle );
22.}
23.# endif/* configSUPPORT_STATIC_ALLOCATION */
24.}
25./**/
26.returnxReturn;
27.}
任務創(chuàng)建后,等候命令執(zhí)行
1.staticportTASK_FUNCTION( prvTimerTask, pvParameters )
2. {
3./**/
4.
5.for( ; ; )
6.{
7.//最近即將超時的定時器還有多長時間溢出
8.xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
9.
10.//阻塞等待,定時器溢出或受到命令,進入下一步(原因不明)
11.prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
12.
13.//接收命令并處理,見下面
14.prvProcessReceivedCommands;
15.}
16.}
所有定時器接口,都是使用xTimerGenericCommand向隊列發(fā)送控制命令,命令如下:
1.# definetmrCOMMAND_START_DONT_TRACE ( ( BaseType_t ) 0 )
2.# definetmrCOMMAND_START ( ( BaseType_t ) 1 )
3.# definetmrCOMMAND_RESET ( ( BaseType_t ) 2 )
4.# definetmrCOMMAND_STOP ( ( BaseType_t ) 3 )
5.# definetmrCOMMAND_CHANGE_PERIOD ( ( BaseType_t ) 4 )
6.# definetmrCOMMAND_DELETE ( ( BaseType_t ) 5 )
6.7 軟件定時器使用注意點
1、查看其他開源代碼,對定時器的使用并不多,但實際項目中過多依賴定時器,導致應用邏輯混亂。
2、 freeRTOS 的定時器不是無限制的,其根源是接收定時器控制命令消息的隊列,默認只有10個單元。
1.xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
定時器過多,可能出現(xiàn)發(fā)起定時器命令失敗,原因是隊列已滿。可以將默認的10擴大為15,后續(xù)盡量使用信號量來優(yōu)化代碼。
4、軟件定時器的回調(diào)函數(shù)要快進快出,而且不能有任何阻塞任務運行的情況,不能有vTaskDelay 以及其它能阻塞任務運行的函數(shù)。特別說明,其回調(diào)函數(shù)是在定時器任務執(zhí)行的,并不是開啟定時器的任務。
七、 信號量 7.1 信號量的概念
信號量(Semaphore)是一種實現(xiàn)任務間通信的機制,可以實現(xiàn)任務之間同步或臨界資源的互斥訪問,常用于協(xié)助一組相互競爭的任務來訪問臨界資源。在多任務系統(tǒng)中,各任務之間需要同步或互斥實現(xiàn)臨界資源的保護,信號量功能可以為用戶提供這方面的支持??梢院唵握J為是為支持多任務同時操作的全局變量(個人理解)。
7.1.1 二值信號量
比如有一個停車位,多個人都想占用停車,這種情況就可以使用一個變量標記車位狀態(tài),它只有兩種情況,被占用或者沒被占用。在多任務中使用二值信號量表示,用于任務與任務、任務與中斷的同步。在freeRTOS中,二值信號量看作只有一個消息的隊列,因此這個隊列只能為空或滿。
7.1.2 計數(shù)信號量
如果有100個停車位,可以停100輛車,每進去一輛車,車位的數(shù)量就要減一,當停車場停滿了 100 輛車的時候,再來的車就不能停進去了。這種場景就需要計數(shù)信號量來表示多個狀態(tài)。二進制信號量可以被認為是長度為 1 的隊列,而計數(shù)信號量則可以被認為長度大于 1 的隊列,信號量使用者依然不必關(guān)心存儲在隊列中的消息,只需關(guān)心隊列是否有消息即可。
7.1.3 互斥信號量
還是前面車位問題,只剩一個空車位,雖然員工車離得近,但是領(lǐng)導車來了,要優(yōu)先安排給領(lǐng)導使用,這就是由地位決定?;コ庑盘柫科鋵嵤翘厥獾亩敌盘柫浚捎谄涮赜械膬?yōu)先級繼承機制從而使它更適用于簡單互鎖,也就是保護臨界資源。
優(yōu)先級翻轉(zhuǎn)問題:假設(shè)有任務H,任務M和任務L三個任務,優(yōu)先級逐次降低。低優(yōu)先級的任務L搶先占有資源,導致高優(yōu)先級的任務H阻塞等待,此時再有中等優(yōu)先級的任務M,它不需要該資源,且優(yōu)先級高于任務L,它優(yōu)先執(zhí)行;之后再執(zhí)行任務L,最后才執(zhí)行任務H??雌饋砭褪歉邇?yōu)先級的任務反而不如低優(yōu)先級的任務,即優(yōu)先級翻轉(zhuǎn)。
改進型的互斥信號量具有優(yōu)先級繼承機制,操作系統(tǒng)對獲取到臨界資源的任務提高其優(yōu)先級為所有等待該資源的任務中的最高優(yōu)先級。一旦任務釋放了該資源,就恢復到原來的優(yōu)先級。
任務L先占用資源,任務H申請不到資源會進入阻塞態(tài),同時系統(tǒng)就會把當前正在使用資源的任務L的優(yōu)先級臨時提高到與任務H優(yōu)先級相同,即使任務M被喚醒了,因為它的優(yōu)先級比任務H低,所以無法打斷任務L,因為任務L的優(yōu)先級被臨時提升到 H;任務L使用完該資源,任務H優(yōu)先級最高,將接著搶占 CPU 的使用權(quán),這樣保證任務H在任務M前優(yōu)先執(zhí)行。
上面的這些就是為了說明,二值信號量因為優(yōu)先級翻轉(zhuǎn),不能用于對臨界區(qū)的訪問。
7.1.4 遞歸互斥信號量
信號量是每獲取一次,可用信號量個數(shù)就會減少一個,釋放一次就增加一個。但是遞歸信號量則不同。對于已經(jīng)獲取遞歸互斥量的任務可以重復獲取該遞歸互斥量,該任務擁有遞歸信號量的所有權(quán)。任務成功獲取幾次遞歸互斥量,就要返還幾次,在此之前遞歸互斥量都處于無效狀態(tài),其他任務無法獲取,只有持有遞歸信號量的任務才能獲取與釋放。類似棧的效果。
7.2 二值信號量的應用
二值信號量是任務與任務間、任務與中斷間同步的重要手段。例如,任務A使用串口發(fā)出AT數(shù)據(jù)后,獲取二值信號量無效進入阻塞;
某個時間后,任務B中串口收到正確的回復,釋放二值信號量。
任務A就立即從阻塞態(tài)中解除,進入就緒態(tài),等待運行。這種機制用在模塊AT交互很合適。
7.3 計數(shù)信號量的應用
計數(shù)信號量可以用于資源管理,允許多個任務獲取信號量訪問共享資源。例如有公共資源車位3個,但是有多個任務要使用,這種場景就必須使用計數(shù)信號量。三個資源最多支持 3 個任務訪問,那么第 4 個任務訪問的時候,會因為獲取不到信號量而進入阻塞。也就是第4個人無法占用車位,必須前面有車離開。等到其中一個有任務(比如任務 1) 釋放掉該資源的時候,第 4 個任務才能獲取到信號量從而進行資源的訪問。其運作的機制類似下圖。
在這里插入圖片描述 7.4 互斥信號量的應用
多任務環(huán)境下往往存在多個任務競爭同一臨界資源的應用場景,互斥量可被用于對臨界資源的保護從而實現(xiàn)獨占式訪問?;コ饬靠梢越档托盘柫看嬖诘膬?yōu)先級翻轉(zhuǎn)問題帶來的影響。
比如有兩個任務需要對串口進行發(fā)送數(shù)據(jù),其硬件資源只有一個,那么兩個任務肯定不能同時發(fā)送,不然導致數(shù)據(jù)錯誤,那么就可以用互斥量對串口資源進行保護,當一個任務正在使用串口的時候,另一個任務則無法使用串口,等到前一個任務使用串口完成后, 另外一個任務才能獲得串口的使用權(quán)。
另外需要注意的是互斥量不能在中斷服務函數(shù)中使用,因為其特有的優(yōu)先級繼承機制只在任務起作用,在中斷的上下文環(huán)境毫無意義。
互斥信號量可以在多個任務之間進行資源保護,而臨界區(qū)只能是在同一個任務進行,但是其速度快。(個人理解)
7.5 信號量接口
所有信號量semaphore使用套路相近,都是創(chuàng)建creat、刪除delete、釋放give和獲取take四種;釋放和獲取支持任務級和中斷級FromISR,其中互斥量和遞歸互斥量不支持中斷。使用對應的信號量,需要在FreeRTOSConfig.h開啟對應的功能。
7.5.1 信號量創(chuàng)建
xSemaphoreCreateBinary用于創(chuàng)建一個二值信號量,并返回一個句柄,默認二值信號量為空,在使用函數(shù) xSemaphoreTake獲取之前必須 先 調(diào) 用 函 數(shù) xSemaphoreGive 釋放后才可以獲取。
xSemaphoreCreateCounting創(chuàng)建計數(shù)信號量。
1.# definexSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
uxMaxCount 計數(shù)信號量的最大值,當達到這個值的時候,信號量不能再被釋放。uxInitialCount 創(chuàng)建計數(shù)信號量的初始值。
xSemaphoreCreateMutex用于創(chuàng)建一個互斥量,并返回一個互斥量句柄,只能被同一個任務獲取一次,如果同一個任務想再次獲取則會失敗。
xSemaphoreCreateRecursiveMutex用于創(chuàng)建一個遞歸互斥量,遞歸信號量可以被同一個任務獲取很多次,獲取多少次就需要釋放多少次。遞歸信號量與互斥量一樣,都實現(xiàn)了優(yōu)先級繼承機制,可以減少優(yōu)先級反轉(zhuǎn)的反生。
7.5.2 信號量刪除
vSemaphoreDelete用于刪除一個信號量,包括二值信號量,計數(shù)信號量,互斥量和遞 歸互斥量。如果有任務阻塞在該信號量上,暫時不要刪除該信號量。傳入的參數(shù)為創(chuàng)建時返回的句柄。
7.5.3 信號量釋放
當信號量有效的時候,任務才能獲取信號量,信號量變得有效就是釋放信號量。每調(diào)用一次該函數(shù)就釋放一個信號量,注意釋放的次數(shù),尤其是計數(shù)信號量。
xSemaphoreGive是任務中釋放信號量的宏,可以用于二值信號量、計數(shù)信號量、互斥量的釋放,但不能釋放由函數(shù)xSemaphoreCreateRecursiveMutex創(chuàng)建的遞歸互斥量,遞歸互斥信號量用xSemaphoreGiveRecursive釋放。xSemaphoreGiveFromISR帶中斷保護釋放一個信號量,被釋放的信號量可以是二值信號量和計數(shù)信號量,不能釋放互斥量和遞歸互斥量,因為互斥量和遞歸互斥量不可在中斷中使用,互斥量的優(yōu)先級繼承機制只能在任務中起作用。
7.5.4 信號量獲取
與釋放信號量對應的是獲取信號量,當信號量有效的時候,任務才能獲取信號量,當任務獲取了某個信號量的時候,該信號量的可用個數(shù)就減一,當它減到0 的時候,任務就無法再獲取了,并且獲取的任務會進入阻塞態(tài)(如果設(shè)定了阻塞超時時間)。
xSemaphoreTake函數(shù)用于獲取信號量,不帶中斷保護。獲取的信號量對象可以是二值信號量、計數(shù)信號量和互斥量,但是遞歸互斥量并不能使用它。
1.# definexSemaphoreTake( xSemaphore, xBlockTime )
xSemaphore 信號量句柄
xBlockTime 等待信號量可用的最大超時時間,單位為 tick
獲取 成 功 則 返 回 pdTRUE ,在 指定的 超時 時間 中 沒 有 獲 取 成 功 則 返 回errQUEUE_EMPTY。
使用xSemaphoreTakeRecursive獲取遞歸互斥量。xSemaphoreTakeFromISR是獲取信號量的中斷版本,是一個不帶阻塞機制獲取信號量的函數(shù),獲取對象必須由是已經(jīng)創(chuàng)建的信號量,信號量類型可以是二值信號量和計數(shù)信號量,它與 xSemaphoreTake函數(shù)不同,它不能用于獲取互斥量,因為互斥量不可以在中斷中使用,并且互斥量特有的優(yōu)先級繼承機制只能在任務中起作用,而在中斷中毫無意義。
7.6 信號量使用注意點
1、建議合理使用信號量進行事件同步處理,減少對定時器的依賴。
2、使用前合理設(shè)定超時時間和依賴關(guān)系,避免多個任務互相等待對方釋放的信號量而死鎖。
八、 事件 8.1 事件的概念
信號量用于單個任務與任務或任務與中斷之間的同步,但有些任務可能與多個任務由關(guān)聯(lián),此時信號量實現(xiàn)就比較麻煩,可以使用事件機制。
事件是一種實現(xiàn)任務間通信的機制,多任務環(huán)境下,任務、中斷之間往往需要同步操作,一個事件發(fā)生會告知等待中的任務,即形成一個任務與任務、中斷與任務間的同步。事件可以提供一對多、多對多的同步操作。一對多同步模型:一個任務等待多個事件的觸發(fā),這種情況是比較常見的。
任務可以通過設(shè)置事件位來實現(xiàn)事件的觸發(fā)和等待操作。FreeRTOS 的事件僅用于同步,不提供數(shù)據(jù)傳輸功能。
8.2 事件的應用
在某些場合,可能需要多個事件發(fā)生了才能進行下一步操作。各個事件可分別發(fā)送或一起操作事件標志組,而任務可以等待多個事件,任務僅對感興趣的事件進行關(guān)注。當有感興趣的事件發(fā)生時并且符合感興趣的條件,任務將被喚醒并進行后續(xù)的處理動作。
其機制類似一個全局變量,子任務使用特殊的接口函數(shù)對指定的位進行寫1或者清零,主任務阻塞等待該變量滿足設(shè)定的規(guī)則,則返回運行。
例如項目中的喂狗機制,多個任務,只要有一個任務發(fā)生異常,則主任務停止喂狗,等待被重啟。
不使用事件機制,則3個任務定時向主master task發(fā)送消息,表明自身任務運行正常;同時master task定時查詢,是否收到3個任務的消息,如果全都收到表示正常,清除進入下一個定時檢查周期;如果其中一個未收到則表示對應任務異常,故意停止喂狗等待被重啟。
使用事件機制,則相對容易,3個任務定時設(shè)置對應的標志位,master task只需要等待指定的事件位,超時就表示異常;不需要自身定時查詢,也省去了定時發(fā)消息。當然缺點是master task只能阻塞等待事件不能執(zhí)行其他業(yè)務邏輯。
8.3 事件接口
xEventGroupCreate用于創(chuàng)建一個事件組,vEventGroupDelete刪除事件對象控制塊來釋放系統(tǒng)資源。
事件組置位,任務中使用 xEventGroupSetBits,中斷中使用xEventGroupSetBitsFromISR;
xEventGroup 事件句柄。uxBitsToSet 指定事件中的事件標志位。如設(shè)置 uxBitsToSet 為 0x09 則位 3和位 0 都需要被置位。返回調(diào)用 xEventGroupSetBits 時事件組中的值。
事件組清除位,任務中使用xEventGroupClearBits,中斷中使用 xEventGroupClearBitsFromISR,都是用于清除事件組指定的位,如果在獲取事件的時候沒有將對應的標志位清除,那么就需要用這個函數(shù)來進行顯式清除。
xEventGroup 事件句柄。uxBitsToClear 指定事件組中的哪個位需要清除。如設(shè)置 uxBitsToSet 為 0x09則位 3和位 0 都需要被清除。
讀取事件標志,任務中使用 xEventGroupGetBits,中斷中使用xEventGroupGetBitsFromISR。
重點是等待事件函數(shù)xEventGroupWaitBits,獲取任務感興趣的事件且支持等待超時機制,當且僅當任務等待的事件發(fā)生時,任務才能獲取到事件信息。否則任務將保持阻塞狀態(tài)以等待事件發(fā)生。當其它任務或中斷服務程序往其等待的事件設(shè)置對應的標志位,該任務將自動由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。
EventGroupWaitBits用于獲取事件組中的一個或多個事件發(fā)生標志,當要讀取的事件標志位沒有被置位時,任務將進入阻塞等待狀態(tài)。要想使用該函數(shù)必 須 把FreeRTOS/source/event_groups.c 這個 C 文件添加到工程中。
1.EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
2.constEventBits_t uxBitsToWaitFor,
3.constBaseType_t xClearOnExit,
4.constBaseType_t xWaitForAllBits,
5.TickType_t xTicksToWait )
參數(shù)
xEventGroup 事件句柄。
uxBitsToWaitFor 一個按位或的值,指定需要等待事件組中的哪些位置1。如需要等待 bits 0 and/or bit 1 and/or bit 2則 uxBitsToWaitFor 配置為 0x07(0111b)。
xClearOnExit pdTRUE:xEventGroupWaitBits 等待到滿足任務喚醒的事件時,系統(tǒng)將清除由形參uxBitsToWaitFor 指定的事件標志位。pdFALSE:不會清除由形參 uxBitsToWaitFor 指定的事件標志位。
xWaitForAllBits pdTRUE :當形參 uxBitsToWaitFor 指定的位都置位的時候,xEventGroupWaitBits才滿足任務喚醒的條件,這也是“邏輯與”等待事件,并且在沒有超時的情況下返回對應的事件標志位的值。pdFALSE:當形參 uxBitsToWaitFor 指定的位有其中任意一個置位的時候,這也是常說的“邏輯或”等待事件,在沒有超時的情況下 函數(shù)返回對應的事件標志位的值。xTicksToWait 最大超時時間,單位為系統(tǒng)節(jié)拍周期
返回值
返回事件中的哪些事件標志位被置位,返回值很可能并不是用戶指定的事件位,需要對返回值進行判斷再處理 。
其應用類似某個全局變量,等待事件的任務在設(shè)定的時間內(nèi),監(jiān)控該變量某些位的值;該值由其他任務或中斷修改。
九、 任務通知
FreeRTOS 從 V8.2.0 版本開始提供任務通知這個功能,可以在一定場合下替代 FreeRTOS 的信號量,隊列、事件組等,但是使用也有局限性。將宏定義 configUSE_TASK_NOTIFICATIONS 設(shè)置為 1才能開啟開功能。但該功能并不常用。
十、 內(nèi)存管理 10.1 內(nèi)存管理的概念
FreeRTOS 內(nèi)存管理模塊管理用于系統(tǒng)中內(nèi)存資源,它是操作系統(tǒng)的核心模塊之一。主要包括內(nèi)存的初始化、分配以及釋放。一般不同的平臺移植代碼,內(nèi)存的動態(tài)申請和釋放接口需要替換。嵌入式實時操作系統(tǒng)中,一般不支持標準C庫中的 malloc和 free,其內(nèi)存有限,隨著內(nèi)存不斷被分配和釋放,整個系統(tǒng)內(nèi)存區(qū)域會產(chǎn)生越來越多的碎片。
FreeRTOS提供了 5 種內(nèi)存管理算法,源文件在SourceportableMemMang 路徑下,使用的時候選擇其中一個。heap_1.c、heap_2.c 和 heap_4.c 這三種內(nèi)存管理方案,內(nèi)存堆實際上是一個很大的 數(shù) 組ucHeap。
heap_1.c內(nèi)存管理方案簡單,它只能申請內(nèi)存而不能進行內(nèi)存釋放。有些嵌入式系統(tǒng)并不會經(jīng)常動態(tài)申請與釋放內(nèi)存,一般都是在系統(tǒng)啟動后就一直使用下去,永不刪除,適合這種方式。
heap_2.c 方案支持釋放申請的內(nèi)存,但是它不能把相鄰的兩個小的內(nèi)存塊合成一個大的內(nèi)存塊,對于每次申請內(nèi)存大小都比較固定的;但每次申請并不是固定內(nèi)存大小的則會造成內(nèi)存碎片。如下圖,隨著不斷的申請釋放,空閑空間會變成很多小片段。
heap_3.c 方案只是封裝了標準 C 庫中的 malloc和 free函數(shù),由編譯器提供,需要通過編譯器或者啟動文件設(shè)置堆空間。
heap_4.c 方案是在heap_2.c 基礎(chǔ)上,對內(nèi)存碎片進行了改進,能把相鄰的空閑的內(nèi)存塊合并成一個更大的塊,這樣可以減少內(nèi)存碎片。
heap_5.c 方案在實現(xiàn)動態(tài)內(nèi)存分配時與 heap4.c 方案一樣,采用最佳匹配算法和合并算法,并且允許內(nèi)存堆跨越多個非連續(xù)的內(nèi)存區(qū),也就是允許在不連續(xù)的內(nèi)存堆中實現(xiàn)內(nèi)存分配,比如做圖形顯示,可能芯片內(nèi)部的 RAM 不足,額外擴展SDRAM,那這種內(nèi)存管理方案則比較合適。
一般物聯(lián)網(wǎng)平臺使用的是heap_4.c。
10.2 內(nèi)存管理接口
不管其內(nèi)部的管理如何實現(xiàn)的,對上層應用層的接口都是一樣的。
1.void* pvPortMalloc( size_txSize ) ; //內(nèi)存申請函數(shù)
2.voidvPortFree( void*pv ) ; //內(nèi)存釋放函數(shù)
3.voidvPortInitialiseBlocks( void) ; //初始化內(nèi)存堆函數(shù)
4.size_txPortGetFreeHeapSize( void) ; //獲取當前未分配的內(nèi)存堆大小
5.size_txPortGetMinimumEverFreeHeapSize( void) ; //獲取未分配的內(nèi)存堆歷史最小值
一般主要是使用內(nèi)存申請和釋放兩個接口,用法和注意事項同malloc/free一樣,成對使用。內(nèi)存釋放后盡量將指針設(shè)為NULL。
十一、 通用接口
一些常用接口進行說明。
11.1 臨界段
進入和退出臨界段的宏在 task.h 中定義,進入和退出臨界段的宏分中斷保護版本和非中斷版本,但最終都是通過開/關(guān)中斷來實現(xiàn)。主要用于對全局變量的控制,系統(tǒng)使用非常多,但實際項目中沒使用,因為全局變量的異常訪問時小概率問題,只是測試沒發(fā)現(xiàn),理論上是存在問題的。
1./* 在中斷場合*/{
2.uint32_tulReturn;
3.
4.ulReturn = taskENTER_CRITICAL_FROM_ISR; /* 進入臨界段,臨界段可以嵌套 */
5.
6./* 臨界段代碼 */
7.
8.taskEXIT_CRITICAL_FROM_ISR( ulReturn ); } /* 退出臨界段 */
1./* 在非中斷場合 */{
2.
3.taskENTER_CRITICAL; /* 進入臨界段 */
4.
5./* 臨界段代碼 */
6.
7.taskEXIT_CRITICAL; } /* 退出臨界段*/
11.2 任務阻塞延時
vTaskDelay 阻塞延時,任務調(diào)用該延時函數(shù)后會被剝離 CPU 使用權(quán),進入阻塞狀態(tài),直到延時結(jié)束。但是該函數(shù)不能用在中斷服務和定時回調(diào)函數(shù)。延時單位是tick。
11.3 獲取系統(tǒng)時鐘計數(shù)值 1.TickType_t xTaskGetTickCount( void)
2. TickType_t xTaskGetTickCountFromISR( void)
注意該接口分任務版和中斷版,該接口獲取的是tick計數(shù)值,需要結(jié)合系統(tǒng)時鐘頻率轉(zhuǎn)換成時間。
11.4 中斷回調(diào)函數(shù)
和其它平臺不同,中斷回調(diào)中釋放中斷標記即可,freeRTOS中,中斷觸發(fā)后,可能某些阻塞的任務獲取了相關(guān)信號,需要立刻執(zhí)行,因此中斷服務發(fā)送消息后,需要主動查詢阻塞任務的情況,執(zhí)行任務切換動作。
1.staticuint32_tulExampleInterruptHandler( void)
2. {
3.BaseType_t xHigherPriorityTaskWoken;
4.
5.xQueueSendToBackFromISR (xQueueRx,&cChar,&xHigherPriorityTaskWoken);
6.portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
評論
查看更多