嵌入式系統(tǒng)不只是ARM+Linux,不是只有安卓,凡是電子產(chǎn)品都可稱為嵌入式系統(tǒng)。物聯(lián)網(wǎng)行業(yè)的興起,也提升了FreeRTOS市場(chǎng)占有率。本文就是介紹FreeRTOS基礎(chǔ)及其應(yīng)用,只是個(gè)人整理,可能存在問題,其目的只是簡(jiǎn)要介紹系統(tǒng)的基礎(chǔ),只能作為入門資料。
目錄
一、 為什么要學(xué)習(xí)RTOS
二、 操作系統(tǒng)基礎(chǔ)
三、 初識(shí) FreeRTOS
四、 任務(wù)
五、 隊(duì)列
六、 軟件定時(shí)器
七、 信號(hào)量
八、 事件
九、 任務(wù)通知
十、 內(nèi)存管理
十一、 通用接口
一、 為什么要學(xué)習(xí) RTOS
進(jìn)入嵌入式這個(gè)領(lǐng)域,入門首先接觸的是單片機(jī)編程,尤其是C51 單片機(jī)來,基礎(chǔ)的單片機(jī)編程通常都是指裸機(jī)編程,即不加入任何 RTOS(Real Time Operating System 實(shí)時(shí)操作系統(tǒng))。常用的有國(guó)外的FreeRTOS、μC/OS、RTX 和國(guó)內(nèi)的 RT-thread、Huawei LiteOS 和 AliOS-Things 等,其中開源且免費(fèi)的 FreeRTOS 的市場(chǎng)占有率較高。
1.1 前后臺(tái)系統(tǒng)
在裸機(jī)系統(tǒng)中,所有的操作都是在一個(gè)無限的大循環(huán)里面實(shí)現(xiàn),支持中斷檢測(cè)。外部中斷緊急事件在中斷里面標(biāo)記或者響應(yīng),中斷服務(wù)稱為前臺(tái),main 函數(shù)里面的while(1)無限循環(huán)稱為后臺(tái),按順序處理業(yè)務(wù)功能,以及中斷標(biāo)記的可執(zhí)行的事件。小型的電子產(chǎn)品用的都是裸機(jī)系統(tǒng),而且也能夠滿足需求。
1.2 多任務(wù)系統(tǒng)
多任務(wù)系統(tǒng)的事件響應(yīng)也是在中斷中完成的,但是事件的處理是在任務(wù)中完成的。如果事件對(duì)應(yīng)的任務(wù)的優(yōu)先級(jí)足夠高,中斷對(duì)應(yīng)的事件會(huì)立刻執(zhí)行。相比前后臺(tái)系統(tǒng),多任務(wù)系統(tǒng)的實(shí)時(shí)性又被提高了。
在多任務(wù)系統(tǒng)中,根據(jù)程序的功能,把這個(gè)程序主體分割成一個(gè)個(gè)獨(dú)立的,無限循環(huán)且不能返回的子程序,稱之為任務(wù)。每個(gè)任務(wù)都是獨(dú)立的,互不干擾的,且具備自身的優(yōu)先級(jí),它由操作系統(tǒng)調(diào)度管理。加入操作系統(tǒng)后,開發(fā)人員不需要關(guān)注每個(gè)功能模塊之間的沖突,重心放在子程序的實(shí)現(xiàn)。缺點(diǎn)是整個(gè)系統(tǒng)隨之帶來的額外RAM開銷,但對(duì)目前的單片機(jī)的來影響不大。
1.3 學(xué)習(xí)RTOS的意義
學(xué)習(xí) RTOS,一是項(xiàng)目需要,隨著產(chǎn)品要實(shí)現(xiàn)的功能越來越多,單純的裸機(jī)系統(tǒng)已經(jīng)不能完美地解決問題,反而會(huì)使編程變得更加復(fù)雜,如果想降低編程的難度,就必須引入 RTOS實(shí)現(xiàn)多任務(wù)管理。二是技能需要,掌握操作系統(tǒng),和基于RTOS的編程,實(shí)現(xiàn)更好的職業(yè)規(guī)劃,對(duì)個(gè)人發(fā)展尤其是錢途是必不可少的。
以前一直覺得學(xué)操作系統(tǒng)就必須是linux,實(shí)際每個(gè)系統(tǒng)都有其應(yīng)用場(chǎng)景,對(duì)于物聯(lián)網(wǎng)行業(yè),殺雞焉用牛刀,小而美,且應(yīng)用廣泛的FreeRTOS 是首選。有一個(gè)操作系統(tǒng)的基礎(chǔ),即使后續(xù)基于其他系統(tǒng)開發(fā)軟件,也可觸類旁通,對(duì)新技術(shù)快速入門。目前接觸的幾款芯片都是基于FreeRTOS。
如何學(xué)習(xí)RTOS?最簡(jiǎn)單的就是在別人移植好的系統(tǒng)之上,看看 RTOS 里面的 API 使用說明,然后調(diào)用這些 API 實(shí)現(xiàn)自己想要的功能即可。完全不用關(guān)心底層的移植,這是最簡(jiǎn)單快速的入門方法。這種學(xué)習(xí)方式,如果是做產(chǎn)品,可以快速的實(shí)現(xiàn)功能,弊端是當(dāng)程序出現(xiàn)問題的時(shí)候,如果對(duì)RTOS不夠了解,會(huì)導(dǎo)致調(diào)試?yán)щy,無從下手。
各種RTOS內(nèi)核實(shí)現(xiàn)方式都差不多,我們只需要深入學(xué)習(xí)其中一款就行。萬變不離其宗,正如掌握了C51基礎(chǔ),后續(xù)換其他型號(hào)或者更高級(jí)的ARM單片機(jī),在原理和方法上,都是有借鑒意義,可以比較快的熟悉并掌握新單片機(jī)的使用。
二、 操作系統(tǒng)基礎(chǔ) 2.1 鏈表
鏈表作為 C 語言中一種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),在平時(shí)寫程序的時(shí)候用的并不多,但在操作系統(tǒng)里面使用的非常多。FreeRTOS 中存在著大量的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)鏈表和鏈表項(xiàng)的操作(list 和 list item)。FreeRTOS 中與鏈表相關(guān)的操作均在 list.h 和 list.c 這兩個(gè)文件中實(shí)現(xiàn)。
鏈表比數(shù)組,最大優(yōu)勢(shì)是占用的內(nèi)存空間可以隨著需求擴(kuò)大或縮小,動(dòng)態(tài)調(diào)整。實(shí)際FreeRTOS中各種任務(wù)的記錄都是依靠鏈表動(dòng)態(tài)管理,具體的可以參考源碼的任務(wù)控制塊tskTCB。任務(wù)切換狀態(tài),就是將對(duì)應(yīng)的鏈表進(jìn)行操作,鏈表操作涉及創(chuàng)建和插入、刪除和查找。
2.2 隊(duì)列
隊(duì)列是一種只允許在表的前端(front)進(jìn)行刪除操作,而在表的后端(rear)進(jìn)行插入操作。隊(duì)尾放入數(shù)據(jù),對(duì)頭擠出。先進(jìn)先出,稱為FIFO
2.3 任務(wù)
在裸機(jī)系統(tǒng)中,系統(tǒng)的主體就是 main 函數(shù)里面順序執(zhí)行的無限循環(huán),這個(gè)無限循環(huán)里面 CPU 按照順序完成各種事情。在多任務(wù)系統(tǒng)中,根據(jù)功能的不同,把整個(gè)系統(tǒng)分割成一個(gè)個(gè)獨(dú)立的且無法返回的函數(shù),這個(gè)函數(shù)我們稱為任務(wù)。系統(tǒng)中的每一任務(wù)都有多種運(yùn)行狀態(tài)。系統(tǒng)初始化完成后,創(chuàng)建的任務(wù)就可以在系統(tǒng)中競(jìng)爭(zhēng)一定的資源,由內(nèi)核進(jìn)行調(diào)度。
就緒(Ready):該任務(wù)在就緒列表中,就緒的任務(wù)已經(jīng)具備執(zhí)行的能力,只等待調(diào)度器進(jìn)行調(diào)度,新創(chuàng)建的任務(wù)會(huì)初始化為就緒態(tài)。
運(yùn)行(Running):該狀態(tài)表明任務(wù)正在執(zhí)行,此時(shí)它占用處理器,調(diào)度器選擇運(yùn)行的永遠(yuǎn)是處于最高優(yōu)先級(jí)的就緒態(tài)任務(wù)。
阻塞(Blocked):任務(wù)當(dāng)前正在等待某個(gè)事件,比如信號(hào)量或外部中斷。
掛起態(tài)(Suspended):處于掛起態(tài)的任務(wù)對(duì)調(diào)度器而言是不可見的。
掛起態(tài)與阻塞態(tài)的區(qū)別,當(dāng)任務(wù)有較長(zhǎng)的時(shí)間不允許運(yùn)行的時(shí)候,我們可以掛起任務(wù),這樣子調(diào)度器就不會(huì)管這個(gè)任務(wù)的任何信息,直到調(diào)用恢復(fù)任務(wù)的 接口;而任務(wù)處于阻塞態(tài)的時(shí)候,系統(tǒng)還需要判斷阻塞態(tài)的任務(wù)是否超時(shí),是否可以解除阻塞。
各任務(wù)運(yùn)行時(shí)使用消息、信號(hào)量等方式進(jìn)行通信,不能是全局變量。任務(wù)通常會(huì)運(yùn)行在一個(gè)死循環(huán)中,不會(huì)退出,如果不再需要,可以調(diào)用刪除任務(wù)。
2.4 臨界區(qū)
臨界區(qū)就是一段在執(zhí)行的時(shí)候不能被中斷的代碼段。在多任務(wù)操作系統(tǒng)里面,對(duì)全局變量的操作不能被打斷,不能執(zhí)行到一半就被其他任務(wù)再次操作。一般被打斷,原因就是系統(tǒng)調(diào)度或外部中斷。對(duì)臨界區(qū)的保護(hù)控制,歸根到底就是對(duì)系統(tǒng)中斷的使能控制。在使用臨界區(qū)時(shí),關(guān)閉中斷響應(yīng),對(duì)部分優(yōu)先級(jí)的中斷進(jìn)行屏蔽,因此臨界區(qū)不允許運(yùn)行時(shí)間過長(zhǎng)。為了對(duì)臨界區(qū)進(jìn)行控制,就需要使用信號(hào)量通信,實(shí)現(xiàn)同步或互斥操作。
三、 初識(shí) FreeRTOS 3.1 FreeRTOS源碼
FreeRTOS 由美國(guó)的 Richard Barry 于 2003 年發(fā)布, 2018 年被亞馬遜收購,改名為 AWS FreeRTOS,版本號(hào)升級(jí)為 V10,支持MIT開源協(xié)議,亞馬遜收購 FreeRTOS 也是為了進(jìn)入物聯(lián)網(wǎng)和人工智能,新版本增加了物聯(lián)網(wǎng)行業(yè)的網(wǎng)絡(luò)協(xié)議等功能。
FreeRTOS 是開源免費(fèi)的,可從官網(wǎng) www.freertos.org 下載源碼和說明手冊(cè)。例如展銳的UIS8910使用的是V10。以FreeRTOSv10.4.1為例,包含 Demo 例程,Source內(nèi)核的源碼,License許可文件。
3.1.1 Source 文件夾
FreeRTOS/ Source 文件夾下的文件:
包括FreeRTOS 的通用的頭文件include和 C 文件,包括任務(wù)、隊(duì)列、定時(shí)器等,適用于各種編譯器和處理器,是通用的。
需要特殊處理適配的在portblle文件夾,其下內(nèi)容與編譯器和處理器相關(guān), FreeRTOS 要想運(yùn)行在一個(gè)單片機(jī)上面,它們就必須關(guān)聯(lián)在一起,通常由匯編和 C 聯(lián)合編寫。通常難度比較高,不過一般芯片原廠提供移植好的接口文件。這里不介紹移植的方法,因?yàn)樽约阂膊幻靼住?/p>
Portblle/MemMang 文件夾下存放的是跟內(nèi)存管理相關(guān)的,總共有五個(gè) heap 文件,有5種內(nèi)存動(dòng)態(tài)分配方式,一般物聯(lián)網(wǎng)產(chǎn)品選用 heap4.c 。
3.1.2 Demo 文件夾
里面包含了 FreeRTOS 官方為各個(gè)單片機(jī)移植好的工程代碼,F(xiàn)reeRTOS 為了推廣自己,會(huì)給針對(duì)不同半導(dǎo)體廠商的評(píng)估板實(shí)現(xiàn)基礎(chǔ)功能范例, Demo下就是參考范例。
3.1.3 FreeRTOSConfig.h配置
FreeRTOSConfig.h頭文件對(duì)FreeRTOS 所需的功能的宏均做了定義,需要根據(jù)應(yīng)用情況配置合適的參數(shù),其作用類似MTK功能機(jī)平臺(tái)的主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)時(shí)鐘tick等參數(shù)在就這個(gè)文件配置,具體作用可以看注釋。一般情況下使用SDK不需要改動(dòng),特殊情況下咨詢?cè)瓘S再調(diào)整。
3.2 FreeRTOS 編碼規(guī)范
接觸一個(gè)新平臺(tái)或者SDK,明白它的編碼規(guī)范,文件作用,可以提高源碼閱讀效率,快速熟悉其內(nèi)部實(shí)現(xiàn)。
3.2.1 數(shù)據(jù)類型
FreeRTOS針對(duì)不同的處理器,對(duì)標(biāo)準(zhǔn)C的數(shù)據(jù)類型進(jìn)行了重定義。
1.# defineportCHAR char
2.# defineportFLOAT float
3.# defineportDOUBLE double
4.# defineportLONG long
5.# defineportSHORT short
6.# defineportSTACK_TYPE uint32_t
7.# defineportBASE_TYPE long
應(yīng)用編碼中,推薦使用的是下面這種風(fēng)格。
1.typedefintint32_t;
2.typedefshort int16_t;
3.typedefcharint8_t;
4.typedefunsignedintuint32_t;
5.typedefunsignedshort uint16_t;
6.typedefunsignedcharuint8_t;
3.2.2 變量名
FreeRTOS 中,定義變量的時(shí)候往往會(huì)把變量的類型當(dāng)作前綴,好處看到就知道其類型。
char 型變量的前綴是 c
short 型變量的前綴是 s
long 型變量的前綴是 l
復(fù)雜的結(jié)構(gòu)體,句柄等定義的變量名的前綴是 x
變量是無符號(hào)型的再加前綴 u,是指針變量則加前綴 p
3.2.3 函數(shù)名
函數(shù)名包含了函數(shù)返回值的類型、函數(shù)所在的文件名和函數(shù)的功能,如果是私有的函數(shù)則會(huì)加一個(gè) prv(private)的前綴。
例如vTaskPrioritySet函數(shù)的返回值為 void 型,在 task.c 這個(gè)文件中定義。
3.2.4 宏
宏內(nèi)容是由大寫字母表示,前綴是小寫字母,表示該宏在哪個(gè)頭文件定義,如:
1.# definetaskYIELD portYIELD
表示該宏是在task.h。
3.2.5 個(gè)人解讀
1、編碼不缺編碼規(guī)范,但是實(shí)際使用中很難完全依照標(biāo)準(zhǔn)執(zhí)行,即使freeRTOS源碼也是如此。
2、關(guān)于函數(shù)或者宏定義中帶文件名的作用,使用Source Insight 編輯代碼,該前綴的意義不大。
3、規(guī)則是活的,只要所有人都按一個(gè)規(guī)則執(zhí)行,它就是標(biāo)準(zhǔn)。
3.3 FreeRTOS應(yīng)用開發(fā)
關(guān)于freeRTOS的應(yīng)用開發(fā),主要是任務(wù)的創(chuàng)建和調(diào)度,任務(wù)間的通信與同步,涉及隊(duì)列、信號(hào)量等操作系統(tǒng)通用接口。結(jié)合應(yīng)用需求,涉及定時(shí)器、延時(shí)、中斷控制等接口。
特別說明,有些功能的實(shí)現(xiàn)方式有多種形式,只針對(duì)常用方式進(jìn)行說明,例如task的創(chuàng)建,只說明動(dòng)態(tài)創(chuàng)建方式,因?yàn)楹苌偈褂渺o態(tài)方式。
四、 任務(wù) 4.1 創(chuàng)建任務(wù)
xTaskCreate使用動(dòng)態(tài)內(nèi)存的方式創(chuàng)建一個(gè)任務(wù)。
1.ret = xTaskCreate((TaskFunction_t) master_task_main, /* 任務(wù)入口函數(shù) */( 1)
2.“MASTER”, /* 任務(wù)名字 */( 2)
3.64* 1024, /* 任務(wù)棧大小 */( 3)
4.NULL, , /* 任務(wù)入口函數(shù)參數(shù) */( 4)
5.TASK_PRIORITY_NORMAL, /* 任務(wù)的優(yōu)先級(jí) */( 5)
6.&task_master_handler); /* 任務(wù)控制塊指針 */( 6)
創(chuàng)建任務(wù)就是軟件運(yùn)行時(shí)的一個(gè)while(1)的入口,一般閱讀其他代碼,找到這個(gè)函數(shù),再跟蹤到任務(wù)入口函數(shù),學(xué)習(xí)基于freeRTOS系統(tǒng)的代碼,首先就是找到main和這個(gè)接口。
(1):任務(wù)入口函數(shù),即任務(wù)函數(shù)的名稱,需要我們自己定義并且實(shí)現(xiàn)。
(2):任務(wù)名字,字符串形式,最大長(zhǎng)度由FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會(huì)被自動(dòng)截掉,只是方便調(diào)試。
(3):任務(wù)堆棧大小,單位為字, 4 個(gè)字節(jié),這個(gè)要注意,否則系統(tǒng)內(nèi)存緊缺。
(4):任務(wù)入口函數(shù)形參,不用的時(shí)候配置為 0 或者NULL 即可。
(5) :任務(wù)的優(yōu)先級(jí),在 FreeRTOS 中,數(shù)值越大優(yōu)先級(jí)越高,0代表最低優(yōu)先級(jí)。基于其SDK開發(fā),可將自定義的所有業(yè)務(wù)功能task設(shè)為同一個(gè)優(yōu)先級(jí),按時(shí)間片輪詢調(diào)度。
(6):任務(wù)控制塊指針,使用動(dòng)態(tài)內(nèi)存的時(shí)候,任務(wù)創(chuàng)建函數(shù)xTaskCreate會(huì)返回一個(gè)指針指向任務(wù)控制塊,也可以設(shè)為NULL,因?yàn)槿蝿?wù)句柄后期可以不使用。
4.2 開啟調(diào)度
當(dāng)任務(wù)創(chuàng)建成功后處于就緒狀態(tài)(Ready),在就緒態(tài)的任務(wù)可以參與操作系統(tǒng)的調(diào)度。操作系統(tǒng)任務(wù)調(diào)度器只啟動(dòng)一次,之后就不會(huì)再次執(zhí)行了,F(xiàn)reeRTOS 中啟動(dòng)任務(wù)調(diào)度器的函數(shù)是 vTaskStartScheduler,并且啟動(dòng)任務(wù)調(diào)度器的時(shí)候就不會(huì)返回,從此任務(wù)管理都由FreeRTOS 管理,此時(shí)才是真正進(jìn)入實(shí)時(shí)操作系統(tǒng)中的第一步。
vTaskStartScheduler開啟調(diào)度時(shí),順便會(huì)創(chuàng)建空閑任務(wù)和定時(shí)器任務(wù)。
FreeRTOS 為了任務(wù)啟動(dòng)和任務(wù)切換使用了三個(gè)異常:SVC、PendSV 和SysTick。
SVC(系統(tǒng)服務(wù)調(diào)用,亦簡(jiǎn)稱系統(tǒng)調(diào)用)用于任務(wù)啟動(dòng)。
PendSV(可掛起系統(tǒng)調(diào)用)用于完成任務(wù)切換,它是可以像普通的中斷一樣被掛起的,它的最大特性是如果當(dāng)前有優(yōu)先級(jí)比它高的中斷在運(yùn)行,PendSV會(huì)延遲執(zhí)行,直到高優(yōu)先級(jí)中斷執(zhí)行完畢,這樣產(chǎn)生的PendSV 中斷就不會(huì)打斷其他中斷的運(yùn)行。
SysTick 用于產(chǎn)生系統(tǒng)節(jié)拍時(shí)鐘,提供一個(gè)時(shí)間片,如果多個(gè)任務(wù)共享同一個(gè)優(yōu)先級(jí),則每次 SysTick 中斷,下一個(gè)任務(wù)將獲得一個(gè)時(shí)間片。
FreeRTOS 中的任務(wù)是搶占式調(diào)度機(jī)制,高優(yōu)先級(jí)的任務(wù)可打斷低優(yōu)先級(jí)任務(wù),低優(yōu)先級(jí)任務(wù)必須在高優(yōu)先級(jí)任務(wù)阻塞或結(jié)束后才能得到調(diào)度。相同優(yōu)先級(jí)的任務(wù)采用時(shí)間片輪轉(zhuǎn)方式進(jìn)行調(diào)度(也就是分時(shí)調(diào)度),時(shí)間片輪轉(zhuǎn)調(diào)度僅在當(dāng)前系統(tǒng)中無更高優(yōu)先級(jí)就緒任務(wù)存在的情況下才有效。
4.3 啟動(dòng)方式
FreeRTOS有兩種啟動(dòng)方式,效果一樣,看個(gè)人喜好。
第一種:main 函數(shù)中將硬件初始化, RTOS 系統(tǒng)初始化,所有任務(wù)的創(chuàng)建完成,最后一步開啟調(diào)度。目前看到的幾個(gè)芯片SDK都是這種方式。
第二種:main 函數(shù)中將硬件和 RTOS 系統(tǒng)先初始化好,只創(chuàng)建一個(gè)任務(wù)后就啟動(dòng)調(diào)度器,然后在這個(gè)任務(wù)里面創(chuàng)建其它應(yīng)用任務(wù),當(dāng)所有任務(wù)都創(chuàng)建成功后,啟動(dòng)任務(wù)再把自己刪除。
4.4 任務(wù)創(chuàng)建源碼分析
xTaskCreate創(chuàng)建任務(wù)。
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 ); //將新任務(wù)加入到就緒鏈表候著
61.xReturn = pdPASS;
62.}
63.else
64.{
65.xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
66.}
67.
68.returnxReturn;
69.}
申請(qǐng)任務(wù)控制塊內(nèi)存,檢查配置參數(shù),初始化,將任務(wù)信息加入到就緒鏈表,等待調(diào)度。前面鏈表部分提到,freeRTOS的任務(wù)信息都是使用鏈表記錄,在task.c有
1.PRIVILEGED_DATA staticList_t pxReadyTasksLists[configMAX_PRIORITIES]; //就緒
2.PRIVILEGED_DATA staticList_t xDelayedTaskList1; //延時(shí)
3.PRIVILEGED_DATA staticList_t xDelayedTaskList2;
4.PRIVILEGED_DATA staticList_t xPendingReadyList; //掛起
5.PRIVILEGED_DATA staticList_t xSuspendedTaskList; //阻塞
分別記錄就緒態(tài)、阻塞態(tài)和掛起的任務(wù),其中阻塞態(tài)有2個(gè),是因?yàn)樘厥饪紤],時(shí)間溢出 的問題,實(shí)際開發(fā)單片機(jī)項(xiàng)目計(jì)時(shí)超過24h的可以借鑒。其中pxReadyTasksLists鏈表數(shù)組,其下標(biāo)就是任務(wù)的優(yōu)先級(jí)。
4.5 任務(wù)調(diào)度源碼分析
創(chuàng)建完任務(wù)的時(shí)候,vTaskStartScheduler開啟調(diào)度器,空閑任務(wù)、定時(shí)器任務(wù)也是在開啟調(diào)度函數(shù)中實(shí)現(xiàn)的。
為什么要空閑任務(wù)?因?yàn)?FreeRTOS一旦啟動(dòng),就必須要保證系統(tǒng)中每時(shí)每刻都有一個(gè)任務(wù)處于運(yùn)行態(tài)(Runing),并且空閑任務(wù)不可以被掛起與刪除,空閑任務(wù)的優(yōu)先級(jí)是最低的,以便系統(tǒng)中其他任務(wù)能隨時(shí)搶占空閑任務(wù)的 CPU 使用權(quán)。這些都是系統(tǒng)必要的東西,也無需自己實(shí)現(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)建空閑任務(wù)*/
13.xReturn = xTaskCreate( prvIdleTask,
14.configIDLE_TASK_NAME,
15.configMINIMAL_STACK_SIZE,
16.( void* ) NULL,
17.portPRIVILEGE_BIT, //優(yōu)先級(jí)為0
18.&xIdleTaskHandle );
19.}
20.# endif/* configSUPPORT_STATIC_ALLOCATION */
21.
22.# if( configUSE_TIMERS == 1 )
23.{
24.if( xReturn == pdPASS )
25.{
26.//創(chuàng)建定時(shí)器task,接收開始、結(jié)束定時(shí)器等命令
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)開始運(yùn)行 */
68.}
69.else
70.{
71./* Should only reach here if a task calls xTaskEndScheduler. */
72.}
73.}
74.else
75.{
76./*****/
77.}
4.6 任務(wù)狀態(tài)切換
FreeRTOS 系統(tǒng)中的每一個(gè)任務(wù)都有多種運(yùn)行狀態(tài),具體如下:
? 任務(wù)掛起函數(shù)
vTaskSuspend
掛起指定任務(wù),被掛起的任務(wù)絕不會(huì)得到 CPU 的使用權(quán)
vTaskSuspendAll
將所有的任務(wù)都掛起? 任務(wù)恢復(fù)函數(shù)
vTaskResume
vTaskResume
xTaskResumeFromISR
任務(wù)恢復(fù)就是讓掛起的任務(wù)重新進(jìn)入就緒狀態(tài),恢復(fù)的任務(wù)會(huì)保留掛起前的狀態(tài)信息,在恢復(fù)的時(shí)候根據(jù)掛起時(shí)的狀態(tài)繼續(xù)運(yùn)行。xTaskResumeFromISR 專門用在中斷服務(wù)程序中。無論通過調(diào)用一次或多次vTaskSuspend函數(shù)而被掛起的任務(wù),也只需調(diào)用一次恢復(fù)即可解掛 。
? 任務(wù)刪除函數(shù)vTaskDelete用于刪除任務(wù)。當(dāng)一個(gè)任務(wù)可以刪除另外一個(gè)任務(wù),形參為要?jiǎng)h除任 務(wù)創(chuàng)建時(shí)返回的任務(wù)句柄,如果是刪除自身, 則形參為 NULL。
4.7 任務(wù)使用注意點(diǎn)
1、中斷服務(wù)函數(shù)是不允許調(diào)用任何會(huì)阻塞運(yùn)行的接口。一般在中斷服務(wù)函數(shù)中只做標(biāo)記事件的發(fā)生,然后通知任務(wù),讓對(duì)應(yīng)任務(wù)去執(zhí)行相關(guān)處理 。
2、將緊急的處理事件的任務(wù)優(yōu)先級(jí)設(shè)置偏高一些。
3、空閑任務(wù)(idle 任務(wù))是 FreeRTOS 系統(tǒng)中沒有其他工作進(jìn)行時(shí)自動(dòng)進(jìn)入的系統(tǒng)任務(wù),永遠(yuǎn)不會(huì)掛起空閑任務(wù),不應(yīng)該陷入死循環(huán)。
4、創(chuàng)建任務(wù)使用的內(nèi)存不要過多,按需申請(qǐng)。如果浪費(fèi)太多,后續(xù)應(yīng)用申請(qǐng)大空間可能提示內(nèi)存不足。
五、 隊(duì)列 5.1 隊(duì)列的概念
隊(duì)列用于任務(wù)間通信的數(shù)據(jù)結(jié)構(gòu),通過消息隊(duì)列服務(wù),任務(wù)或中斷服務(wù)將消息放入消息隊(duì)列中。其他任務(wù)或者自身從消息隊(duì)列中獲得消息。實(shí)現(xiàn)隊(duì)列可以在任務(wù)與任務(wù)間、中斷和任務(wù)間傳遞信息。隊(duì)列操作支持阻塞等待,向已經(jīng)填滿的隊(duì)列發(fā)送數(shù)據(jù)或者從空隊(duì)列讀出數(shù)據(jù),都會(huì)導(dǎo)致阻塞,時(shí)間自定義。消息隊(duì)列的運(yùn)作過程具如下:
5.2 隊(duì)列創(chuàng)建
xQueueCreate用于創(chuàng)建一個(gè)新的隊(duì)列并返回可用于訪問這個(gè)隊(duì)列的句柄。隊(duì)列句柄其實(shí)就是一個(gè)指向隊(duì)列數(shù)據(jù)結(jié)構(gòu)類型的指針。
1.master_queue = xQueueCreate( 50, sizeof( task_message_struct_t));
創(chuàng)建隊(duì)列,占用50個(gè)單元,每個(gè)單元為sizeof(task_message_struct_t)字節(jié),和 malloc比較類似。其最終使用的函數(shù)是 xQueueGenericCreate,后續(xù)信號(hào)量等也是使用它創(chuàng)建,只是最后的隊(duì)列類型不同。
申請(qǐng)內(nèi)存后,xQueueGenericReset再對(duì)其進(jìn)行初始化,隊(duì)列的結(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.//當(dāng)前向隊(duì)列寫數(shù)據(jù)阻塞的任務(wù)列表或者從隊(duì)列取數(shù)阻塞的鏈表
13.List_t xTasksWaitingToSend;
14.List_t xTasksWaitingToReceive;
15.
16.//隊(duì)列里有多少個(gè)單元被占用,應(yīng)用中需要
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 隊(duì)列刪除
隊(duì)列刪除函數(shù) vQueueDelete需傳入要?jiǎng)h除的消息隊(duì)列的句柄即可,刪除之后這個(gè)消息隊(duì)列的所有信息都會(huì)被系統(tǒng)回收清空,而且不能再次使用這個(gè)消息隊(duì)列了。實(shí)際應(yīng)用中很少使用。
5.4 向隊(duì)列發(fā)送消息
任務(wù)或者中斷服務(wù)程序都可以給消息隊(duì)列發(fā)送消息,當(dāng)發(fā)送消息時(shí),如果隊(duì)列未滿或者允許覆蓋入隊(duì),F(xiàn)reeRTOS 會(huì)將消息拷貝到消息隊(duì)列隊(duì)尾,否則,會(huì)根據(jù)用戶指定的超時(shí)時(shí)間進(jìn)行阻塞,消息發(fā)送接口很多,最簡(jiǎn)單的是 xQueueSend,用于向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息。消息以拷貝的形式入隊(duì),該函數(shù)絕對(duì)不能在中斷服務(wù)程序里面被調(diào)用,中斷中必須使用帶有中斷保護(hù)功能的 xQueueSendFromISR來代替。
BaseType_t xQueueSend(QueueHandle_t xQueue, constvoid* pvItemToQueue, TickType_t xTicksToWait) ;
用于向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息。
參數(shù)
xQueue 隊(duì)列句柄
pvItemToQueue 指針,指向要發(fā)送到隊(duì)列尾部的隊(duì)列消息。
xTicksToWait 隊(duì)列滿時(shí),等待隊(duì)列空閑的最大超時(shí)時(shí)間。如果隊(duì)列滿并且xTicksToWait 被設(shè)置成 0,函數(shù)立刻返回。超時(shí)時(shí)間的單位為系統(tǒng)節(jié)拍周期 tick,延時(shí)為 portMAX_DELAY 將導(dǎo)致任務(wù)掛起(沒有超時(shí))。
返回值
消息發(fā)送成功成功返回 pdTRUE,否則返回 errQUEUE_FULL。
xQueueSendToBack與xQueueSend完全相同, xQueueSendFromISR與 xQueueSendToBackFromISR,帶FromISR表示只能在中斷中使用,freeRTOS所以帶這個(gè)后綴的都是這個(gè)含義。xQueueSendToFront和QueueSendToFrontFromISR用于向隊(duì)列隊(duì)首發(fā)送一個(gè)消息。這些在任務(wù)中發(fā)送消息的函數(shù)都是 xQueueGenericSend展開的宏定義。
1.BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
2.constvoid* constpvItemToQueue,
3.TickType_t xTicksToWait,
4.constBaseType_t xCopyPosition ) //發(fā)送數(shù)據(jù)到消息隊(duì)列的位置
一般使用xQueueSend和xQueueSendFromISR,如不確定當(dāng)前運(yùn)行的是系統(tǒng)服務(wù),還是中斷服務(wù),一般ARM都支持查詢中斷狀態(tài)寄存器判斷,可以封裝一層接口,只管發(fā)消息,內(nèi)部判斷是否使用支持中斷嵌套的版本,UIS8910就是如此。特殊情況下,如發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包未收到服務(wù)器響應(yīng),期望立刻入隊(duì)再次發(fā)送它,可以xQueueSendToFront向隊(duì)頭發(fā)消息。
5.5 從隊(duì)列讀取消息
當(dāng)任務(wù)試圖讀隊(duì)列中的消息時(shí),可以指定一個(gè)阻塞超時(shí)時(shí)間,當(dāng)且僅當(dāng)消息隊(duì)列中有消息的時(shí)候,任務(wù)才能讀取到消息。如果隊(duì)列為空,該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列數(shù)據(jù)有效。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的隊(duì)列中寫入了數(shù)據(jù),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。當(dāng)任務(wù)等待的時(shí)間超過了指定的阻塞時(shí)間,即使隊(duì)列中尚無有效數(shù)據(jù),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。所有的task主入口while循環(huán)體都是按這個(gè)執(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用于從一個(gè)隊(duì)列中接收消息并把消息從隊(duì)列中刪除。如果不想刪除消息的話,就調(diào)用 xQueuePeek函數(shù)。xQueueReceiveFromISR與xQueuePeekFromISR是中斷版本,用于在中斷服務(wù)程序中接收一個(gè)隊(duì)列消息并把消息。這兩個(gè)函數(shù)只能用于中斷,是不帶有阻塞機(jī)制的,實(shí)際項(xiàng)目沒有使用。
5.6 查詢隊(duì)列使用情況
uxQueueMessagesWaiting查詢隊(duì)列中存儲(chǔ)的信息數(shù)目,具有中斷保護(hù)的版本為uxQueueMessagesWaitingFromISR。查詢隊(duì)列的空閑數(shù)目uxQueueSpacesAvailable。
5.7 隊(duì)列使用注意點(diǎn)
使用隊(duì)列函數(shù)需要注意以下幾點(diǎn):
1、中斷中必須使用帶FromISR后綴的接口;
2、發(fā)送或者是接收消息都是以拷貝的方式進(jìn)行,如果消息內(nèi)容過于龐大,可以將消息的地址作為消息進(jìn)行發(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ù)使用動(dòng)態(tài)申請(qǐng)內(nèi)存保存,隊(duì)列只傳遞指針
12.} track_task_message_struct_t;
3、隊(duì)列并不屬于任何任務(wù),所有任務(wù)都可以向同一隊(duì)列寫入和讀出,一個(gè)隊(duì)列可以由多任務(wù)或中斷讀寫。
4、隊(duì)列的深度要結(jié)合實(shí)際,可以多申請(qǐng)點(diǎn),前提是每個(gè)隊(duì)列單元盡可能小。
5、隊(duì)列存在一定限制,在隊(duì)頭沒有取出來之前,是無法取出第二個(gè),和STL鏈表存在差異。
六、 軟件定時(shí)器 6.1 軟件定時(shí)器的概念
定時(shí)器有硬件定時(shí)器和軟件定時(shí)器之分,硬件定時(shí)器是芯片本身提供的定時(shí)功能精度高,并且是中斷觸發(fā)方式。軟件定時(shí)器是由操作系統(tǒng)封裝的接口,它構(gòu)建在硬件定時(shí)器基礎(chǔ)之上,使系統(tǒng)能夠提供不受硬件定時(shí)器資源限制,其實(shí)現(xiàn)的功能與硬件定時(shí)器也是類似的。
在操作系統(tǒng)中,通常軟件定時(shí)器以系統(tǒng)節(jié)拍周期為計(jì)時(shí)單位。系統(tǒng)節(jié)拍配置為configTICK_RATE_HZ,該宏在 FreeRTOSConfig.h 中,一般是100或者1000。根據(jù)實(shí)際系統(tǒng) CPU 的處理能力和實(shí)時(shí)性需求設(shè)置合適的數(shù)值,系統(tǒng)節(jié)拍周期的值越小,精度越高,但是系統(tǒng)開銷也將越大,因?yàn)檫@代表在 1 秒中系統(tǒng)進(jìn)入時(shí)鐘中斷的次數(shù)也就越多。
6.2 軟件定時(shí)器創(chuàng)建
軟件定時(shí)器需先創(chuàng)建才允許使用,動(dòng)態(tài)創(chuàng)建方式是xTimerCreate,返回一個(gè)句柄。軟件定時(shí)器在創(chuàng)建成功后是處于休眠狀態(tài)的,沒有開始計(jì)時(shí)運(yùn)行。FreeRTOS的軟件定時(shí)器支持單次模式和周期模式。
單次模式:當(dāng)用戶創(chuàng)建了定時(shí)器并啟動(dòng)了定時(shí)器后,定時(shí)時(shí)間到了,只執(zhí)行一次回調(diào)函數(shù),之后不再執(zhí)行。周期模式:定時(shí)器會(huì)按照設(shè)置的定時(shí)時(shí)間循環(huán)執(zhí)行回調(diào)函數(shù),直到用戶將定時(shí)器停止或刪除。
實(shí)際項(xiàng)目中使用這種模式對(duì)單片機(jī)喂狗就比較省事。
1.TimerHandle_t xTimerCreate( constchar* constpcTimerName, //定時(shí)器名稱
2.constTickType_t xTimerPeriodInTicks, //定時(shí)時(shí)間
3.constUBaseType_t uxAutoReload, //是否自動(dòng)重載
4.void* constpvTimerID, //回調(diào)函數(shù)的參數(shù)
5.TimerCallbackFunction_t pxCallbackFunction ) //回調(diào)函數(shù)
6.3 軟件定時(shí)器開啟
新創(chuàng)建的定時(shí)器沒有開始計(jì)時(shí)啟動(dòng),可以使用
xTimerStart、
xTimerReset、
xTimerStartFromISR 、xTimerResetFromISR
xTimerChangePeriod、xTimerChangePeriodFromISR
這些函數(shù)將其狀態(tài)轉(zhuǎn)換為活躍態(tài),開始運(yùn)行。區(qū)別:如果定時(shí)器設(shè)定60秒間隔,已經(jīng)運(yùn)行了30秒,reset是將定時(shí)器重置為原來設(shè)定的時(shí)間間隔,也就是重新開始延時(shí)60秒。ChangePeriod重新設(shè)置計(jì)時(shí)周期。
6.4 軟件定時(shí)器停止
xTimerStop 用于停止一個(gè)已經(jīng)啟動(dòng)的軟件定時(shí)器,xTimerStopFromISR是中斷版本。
6.5 軟件定時(shí)器刪除
xTimerDelete用于刪除一個(gè)已經(jīng)被創(chuàng)建成功的軟件定時(shí)器,釋放資源,刪除之后不能再使用。實(shí)際項(xiàng)目中,任務(wù)和隊(duì)列都是按需創(chuàng)建,一直使用,但是定時(shí)器不使用的就應(yīng)該刪除,并且刪除后一定要將句柄置為NULL。
6.6 軟件定時(shí)器源碼分析
軟件定時(shí)器任務(wù)是在系統(tǒng)開始調(diào)度的時(shí)候就被創(chuàng)建:vTaskStartScheduler—xTimerCreateTimerTask。
1.BaseType_t xTimerCreateTimerTask( void)
2. {
3.BaseType_t xReturn = pdFAIL;
4.
5.prvCheckForValidListAndQueue; //創(chuàng)建定時(shí)器任務(wù)的隊(duì)列
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)建定時(shí)器任務(wù)
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.}
任務(wù)創(chuàng)建后,等候命令執(zhí)行
1.staticportTASK_FUNCTION( prvTimerTask, pvParameters )
2. {
3./**/
4.
5.for( ; ; )
6.{
7.//最近即將超時(shí)的定時(shí)器還有多長(zhǎng)時(shí)間溢出
8.xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
9.
10.//阻塞等待,定時(shí)器溢出或受到命令,進(jìn)入下一步(原因不明)
11.prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
12.
13.//接收命令并處理,見下面
14.prvProcessReceivedCommands;
15.}
16.}
所有定時(shí)器接口,都是使用xTimerGenericCommand向隊(duì)列發(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 軟件定時(shí)器使用注意點(diǎn)
1、查看其他開源代碼,對(duì)定時(shí)器的使用并不多,但實(shí)際項(xiàng)目中過多依賴定時(shí)器,導(dǎo)致應(yīng)用邏輯混亂。
2、 freeRTOS 的定時(shí)器不是無限制的,其根源是接收定時(shí)器控制命令消息的隊(duì)列,默認(rèn)只有10個(gè)單元。
1.xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
定時(shí)器過多,可能出現(xiàn)發(fā)起定時(shí)器命令失敗,原因是隊(duì)列已滿。可以將默認(rèn)的10擴(kuò)大為15,后續(xù)盡量使用信號(hào)量來優(yōu)化代碼。
4、軟件定時(shí)器的回調(diào)函數(shù)要快進(jìn)快出,而且不能有任何阻塞任務(wù)運(yùn)行的情況,不能有vTaskDelay 以及其它能阻塞任務(wù)運(yùn)行的函數(shù)。特別說明,其回調(diào)函數(shù)是在定時(shí)器任務(wù)執(zhí)行的,并不是開啟定時(shí)器的任務(wù)。
七、 信號(hào)量 7.1 信號(hào)量的概念
信號(hào)量(Semaphore)是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,可以實(shí)現(xiàn)任務(wù)之間同步或臨界資源的互斥訪問,常用于協(xié)助一組相互競(jìng)爭(zhēng)的任務(wù)來訪問臨界資源。在多任務(wù)系統(tǒng)中,各任務(wù)之間需要同步或互斥實(shí)現(xiàn)臨界資源的保護(hù),信號(hào)量功能可以為用戶提供這方面的支持。可以簡(jiǎn)單認(rèn)為是為支持多任務(wù)同時(shí)操作的全局變量(個(gè)人理解)。
7.1.1 二值信號(hào)量
比如有一個(gè)停車位,多個(gè)人都想占用停車,這種情況就可以使用一個(gè)變量標(biāo)記車位狀態(tài),它只有兩種情況,被占用或者沒被占用。在多任務(wù)中使用二值信號(hào)量表示,用于任務(wù)與任務(wù)、任務(wù)與中斷的同步。在freeRTOS中,二值信號(hào)量看作只有一個(gè)消息的隊(duì)列,因此這個(gè)隊(duì)列只能為空或滿。
7.1.2 計(jì)數(shù)信號(hào)量
如果有100個(gè)停車位,可以停100輛車,每進(jìn)去一輛車,車位的數(shù)量就要減一,當(dāng)停車場(chǎng)停滿了 100 輛車的時(shí)候,再來的車就不能停進(jìn)去了。這種場(chǎng)景就需要計(jì)數(shù)信號(hào)量來表示多個(gè)狀態(tài)。二進(jìn)制信號(hào)量可以被認(rèn)為是長(zhǎng)度為 1 的隊(duì)列,而計(jì)數(shù)信號(hào)量則可以被認(rèn)為長(zhǎng)度大于 1 的隊(duì)列,信號(hào)量使用者依然不必關(guān)心存儲(chǔ)在隊(duì)列中的消息,只需關(guān)心隊(duì)列是否有消息即可。
7.1.3 互斥信號(hào)量
還是前面車位問題,只剩一個(gè)空車位,雖然員工車離得近,但是領(lǐng)導(dǎo)車來了,要優(yōu)先安排給領(lǐng)導(dǎo)使用,這就是由地位決定。互斥信號(hào)量其實(shí)是特殊的二值信號(hào)量,由于其特有的優(yōu)先級(jí)繼承機(jī)制從而使它更適用于簡(jiǎn)單互鎖,也就是保護(hù)臨界資源。
優(yōu)先級(jí)翻轉(zhuǎn)問題:假設(shè)有任務(wù)H,任務(wù)M和任務(wù)L三個(gè)任務(wù),優(yōu)先級(jí)逐次降低。低優(yōu)先級(jí)的任務(wù)L搶先占有資源,導(dǎo)致高優(yōu)先級(jí)的任務(wù)H阻塞等待,此時(shí)再有中等優(yōu)先級(jí)的任務(wù)M,它不需要該資源,且優(yōu)先級(jí)高于任務(wù)L,它優(yōu)先執(zhí)行;之后再執(zhí)行任務(wù)L,最后才執(zhí)行任務(wù)H。看起來就是高優(yōu)先級(jí)的任務(wù)反而不如低優(yōu)先級(jí)的任務(wù),即優(yōu)先級(jí)翻轉(zhuǎn)。
改進(jìn)型的互斥信號(hào)量具有優(yōu)先級(jí)繼承機(jī)制,操作系統(tǒng)對(duì)獲取到臨界資源的任務(wù)提高其優(yōu)先級(jí)為所有等待該資源的任務(wù)中的最高優(yōu)先級(jí)。一旦任務(wù)釋放了該資源,就恢復(fù)到原來的優(yōu)先級(jí)。
任務(wù)L先占用資源,任務(wù)H申請(qǐng)不到資源會(huì)進(jìn)入阻塞態(tài),同時(shí)系統(tǒng)就會(huì)把當(dāng)前正在使用資源的任務(wù)L的優(yōu)先級(jí)臨時(shí)提高到與任務(wù)H優(yōu)先級(jí)相同,即使任務(wù)M被喚醒了,因?yàn)樗膬?yōu)先級(jí)比任務(wù)H低,所以無法打斷任務(wù)L,因?yàn)槿蝿?wù)L的優(yōu)先級(jí)被臨時(shí)提升到 H;任務(wù)L使用完該資源,任務(wù)H優(yōu)先級(jí)最高,將接著搶占 CPU 的使用權(quán),這樣保證任務(wù)H在任務(wù)M前優(yōu)先執(zhí)行。
上面的這些就是為了說明,二值信號(hào)量因?yàn)閮?yōu)先級(jí)翻轉(zhuǎn),不能用于對(duì)臨界區(qū)的訪問。
7.1.4 遞歸互斥信號(hào)量
信號(hào)量是每獲取一次,可用信號(hào)量個(gè)數(shù)就會(huì)減少一個(gè),釋放一次就增加一個(gè)。但是遞歸信號(hào)量則不同。對(duì)于已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量,該任務(wù)擁有遞歸信號(hào)量的所有權(quán)。任務(wù)成功獲取幾次遞歸互斥量,就要返還幾次,在此之前遞歸互斥量都處于無效狀態(tài),其他任務(wù)無法獲取,只有持有遞歸信號(hào)量的任務(wù)才能獲取與釋放。類似棧的效果。
7.2 二值信號(hào)量的應(yīng)用
二值信號(hào)量是任務(wù)與任務(wù)間、任務(wù)與中斷間同步的重要手段。例如,任務(wù)A使用串口發(fā)出AT數(shù)據(jù)后,獲取二值信號(hào)量無效進(jìn)入阻塞;
某個(gè)時(shí)間后,任務(wù)B中串口收到正確的回復(fù),釋放二值信號(hào)量。
任務(wù)A就立即從阻塞態(tài)中解除,進(jìn)入就緒態(tài),等待運(yùn)行。這種機(jī)制用在模塊AT交互很合適。
7.3 計(jì)數(shù)信號(hào)量的應(yīng)用
計(jì)數(shù)信號(hào)量可以用于資源管理,允許多個(gè)任務(wù)獲取信號(hào)量訪問共享資源。例如有公共資源車位3個(gè),但是有多個(gè)任務(wù)要使用,這種場(chǎng)景就必須使用計(jì)數(shù)信號(hào)量。三個(gè)資源最多支持 3 個(gè)任務(wù)訪問,那么第 4 個(gè)任務(wù)訪問的時(shí)候,會(huì)因?yàn)楂@取不到信號(hào)量而進(jìn)入阻塞。也就是第4個(gè)人無法占用車位,必須前面有車離開。等到其中一個(gè)有任務(wù)(比如任務(wù) 1) 釋放掉該資源的時(shí)候,第 4 個(gè)任務(wù)才能獲取到信號(hào)量從而進(jìn)行資源的訪問。其運(yùn)作的機(jī)制類似下圖。
在這里插入圖片描述 7.4 互斥信號(hào)量的應(yīng)用
多任務(wù)環(huán)境下往往存在多個(gè)任務(wù)競(jìng)爭(zhēng)同一臨界資源的應(yīng)用場(chǎng)景,互斥量可被用于對(duì)臨界資源的保護(hù)從而實(shí)現(xiàn)獨(dú)占式訪問。互斥量可以降低信號(hào)量存在的優(yōu)先級(jí)翻轉(zhuǎn)問題帶來的影響。
比如有兩個(gè)任務(wù)需要對(duì)串口進(jìn)行發(fā)送數(shù)據(jù),其硬件資源只有一個(gè),那么兩個(gè)任務(wù)肯定不能同時(shí)發(fā)送,不然導(dǎo)致數(shù)據(jù)錯(cuò)誤,那么就可以用互斥量對(duì)串口資源進(jìn)行保護(hù),當(dāng)一個(gè)任務(wù)正在使用串口的時(shí)候,另一個(gè)任務(wù)則無法使用串口,等到前一個(gè)任務(wù)使用串口完成后, 另外一個(gè)任務(wù)才能獲得串口的使用權(quán)。
另外需要注意的是互斥量不能在中斷服務(wù)函數(shù)中使用,因?yàn)槠涮赜械膬?yōu)先級(jí)繼承機(jī)制只在任務(wù)起作用,在中斷的上下文環(huán)境毫無意義。
互斥信號(hào)量可以在多個(gè)任務(wù)之間進(jìn)行資源保護(hù),而臨界區(qū)只能是在同一個(gè)任務(wù)進(jìn)行,但是其速度快。(個(gè)人理解)
7.5 信號(hào)量接口
所有信號(hào)量semaphore使用套路相近,都是創(chuàng)建creat、刪除delete、釋放give和獲取take四種;釋放和獲取支持任務(wù)級(jí)和中斷級(jí)FromISR,其中互斥量和遞歸互斥量不支持中斷。使用對(duì)應(yīng)的信號(hào)量,需要在FreeRTOSConfig.h開啟對(duì)應(yīng)的功能。
7.5.1 信號(hào)量創(chuàng)建
xSemaphoreCreateBinary用于創(chuàng)建一個(gè)二值信號(hào)量,并返回一個(gè)句柄,默認(rèn)二值信號(hào)量為空,在使用函數(shù) xSemaphoreTake獲取之前必須 先 調(diào) 用 函 數(shù) xSemaphoreGive 釋放后才可以獲取。
xSemaphoreCreateCounting創(chuàng)建計(jì)數(shù)信號(hào)量。
1.# definexSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
uxMaxCount 計(jì)數(shù)信號(hào)量的最大值,當(dāng)達(dá)到這個(gè)值的時(shí)候,信號(hào)量不能再被釋放。uxInitialCount 創(chuàng)建計(jì)數(shù)信號(hào)量的初始值。
xSemaphoreCreateMutex用于創(chuàng)建一個(gè)互斥量,并返回一個(gè)互斥量句柄,只能被同一個(gè)任務(wù)獲取一次,如果同一個(gè)任務(wù)想再次獲取則會(huì)失敗。
xSemaphoreCreateRecursiveMutex用于創(chuàng)建一個(gè)遞歸互斥量,遞歸信號(hào)量可以被同一個(gè)任務(wù)獲取很多次,獲取多少次就需要釋放多少次。遞歸信號(hào)量與互斥量一樣,都實(shí)現(xiàn)了優(yōu)先級(jí)繼承機(jī)制,可以減少優(yōu)先級(jí)反轉(zhuǎn)的反生。
7.5.2 信號(hào)量刪除
vSemaphoreDelete用于刪除一個(gè)信號(hào)量,包括二值信號(hào)量,計(jì)數(shù)信號(hào)量,互斥量和遞 歸互斥量。如果有任務(wù)阻塞在該信號(hào)量上,暫時(shí)不要?jiǎng)h除該信號(hào)量。傳入的參數(shù)為創(chuàng)建時(shí)返回的句柄。
7.5.3 信號(hào)量釋放
當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能獲取信號(hào)量,信號(hào)量變得有效就是釋放信號(hào)量。每調(diào)用一次該函數(shù)就釋放一個(gè)信號(hào)量,注意釋放的次數(shù),尤其是計(jì)數(shù)信號(hào)量。
xSemaphoreGive是任務(wù)中釋放信號(hào)量的宏,可以用于二值信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量的釋放,但不能釋放由函數(shù)xSemaphoreCreateRecursiveMutex創(chuàng)建的遞歸互斥量,遞歸互斥信號(hào)量用xSemaphoreGiveRecursive釋放。xSemaphoreGiveFromISR帶中斷保護(hù)釋放一個(gè)信號(hào)量,被釋放的信號(hào)量可以是二值信號(hào)量和計(jì)數(shù)信號(hào)量,不能釋放互斥量和遞歸互斥量,因?yàn)榛コ饬亢瓦f歸互斥量不可在中斷中使用,互斥量的優(yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用。
7.5.4 信號(hào)量獲取
與釋放信號(hào)量對(duì)應(yīng)的是獲取信號(hào)量,當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能獲取信號(hào)量,當(dāng)任務(wù)獲取了某個(gè)信號(hào)量的時(shí)候,該信號(hào)量的可用個(gè)數(shù)就減一,當(dāng)它減到0 的時(shí)候,任務(wù)就無法再獲取了,并且獲取的任務(wù)會(huì)進(jìn)入阻塞態(tài)(如果設(shè)定了阻塞超時(shí)時(shí)間)。
xSemaphoreTake函數(shù)用于獲取信號(hào)量,不帶中斷保護(hù)。獲取的信號(hào)量對(duì)象可以是二值信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量,但是遞歸互斥量并不能使用它。
1.# definexSemaphoreTake( xSemaphore, xBlockTime )
xSemaphore 信號(hào)量句柄
xBlockTime 等待信號(hào)量可用的最大超時(shí)時(shí)間,單位為 tick
獲取 成 功 則 返 回 pdTRUE ,在 指定的 超時(shí) 時(shí)間 中 沒 有 獲 取 成 功 則 返 回errQUEUE_EMPTY。
使用xSemaphoreTakeRecursive獲取遞歸互斥量。xSemaphoreTakeFromISR是獲取信號(hào)量的中斷版本,是一個(gè)不帶阻塞機(jī)制獲取信號(hào)量的函數(shù),獲取對(duì)象必須由是已經(jīng)創(chuàng)建的信號(hào)量,信號(hào)量類型可以是二值信號(hào)量和計(jì)數(shù)信號(hào)量,它與 xSemaphoreTake函數(shù)不同,它不能用于獲取互斥量,因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂茫⑶一コ饬刻赜械膬?yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,而在中斷中毫無意義。
7.6 信號(hào)量使用注意點(diǎn)
1、建議合理使用信號(hào)量進(jìn)行事件同步處理,減少對(duì)定時(shí)器的依賴。
2、使用前合理設(shè)定超時(shí)時(shí)間和依賴關(guān)系,避免多個(gè)任務(wù)互相等待對(duì)方釋放的信號(hào)量而死鎖。
八、 事件 8.1 事件的概念
信號(hào)量用于單個(gè)任務(wù)與任務(wù)或任務(wù)與中斷之間的同步,但有些任務(wù)可能與多個(gè)任務(wù)由關(guān)聯(lián),此時(shí)信號(hào)量實(shí)現(xiàn)就比較麻煩,可以使用事件機(jī)制。
事件是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,多任務(wù)環(huán)境下,任務(wù)、中斷之間往往需要同步操作,一個(gè)事件發(fā)生會(huì)告知等待中的任務(wù),即形成一個(gè)任務(wù)與任務(wù)、中斷與任務(wù)間的同步。事件可以提供一對(duì)多、多對(duì)多的同步操作。一對(duì)多同步模型:一個(gè)任務(wù)等待多個(gè)事件的觸發(fā),這種情況是比較常見的。
任務(wù)可以通過設(shè)置事件位來實(shí)現(xiàn)事件的觸發(fā)和等待操作。FreeRTOS 的事件僅用于同步,不提供數(shù)據(jù)傳輸功能。
8.2 事件的應(yīng)用
在某些場(chǎng)合,可能需要多個(gè)事件發(fā)生了才能進(jìn)行下一步操作。各個(gè)事件可分別發(fā)送或一起操作事件標(biāo)志組,而任務(wù)可以等待多個(gè)事件,任務(wù)僅對(duì)感興趣的事件進(jìn)行關(guān)注。當(dāng)有感興趣的事件發(fā)生時(shí)并且符合感興趣的條件,任務(wù)將被喚醒并進(jìn)行后續(xù)的處理動(dòng)作。
其機(jī)制類似一個(gè)全局變量,子任務(wù)使用特殊的接口函數(shù)對(duì)指定的位進(jìn)行寫1或者清零,主任務(wù)阻塞等待該變量滿足設(shè)定的規(guī)則,則返回運(yùn)行。
例如項(xiàng)目中的喂狗機(jī)制,多個(gè)任務(wù),只要有一個(gè)任務(wù)發(fā)生異常,則主任務(wù)停止喂狗,等待被重啟。
不使用事件機(jī)制,則3個(gè)任務(wù)定時(shí)向主master task發(fā)送消息,表明自身任務(wù)運(yùn)行正常;同時(shí)master task定時(shí)查詢,是否收到3個(gè)任務(wù)的消息,如果全都收到表示正常,清除進(jìn)入下一個(gè)定時(shí)檢查周期;如果其中一個(gè)未收到則表示對(duì)應(yīng)任務(wù)異常,故意停止喂狗等待被重啟。
使用事件機(jī)制,則相對(duì)容易,3個(gè)任務(wù)定時(shí)設(shè)置對(duì)應(yīng)的標(biāo)志位,master task只需要等待指定的事件位,超時(shí)就表示異常;不需要自身定時(shí)查詢,也省去了定時(shí)發(fā)消息。當(dāng)然缺點(diǎn)是master task只能阻塞等待事件不能執(zhí)行其他業(yè)務(wù)邏輯。
8.3 事件接口
xEventGroupCreate用于創(chuàng)建一個(gè)事件組,vEventGroupDelete刪除事件對(duì)象控制塊來釋放系統(tǒng)資源。
事件組置位,任務(wù)中使用 xEventGroupSetBits,中斷中使用xEventGroupSetBitsFromISR;
xEventGroup 事件句柄。uxBitsToSet 指定事件中的事件標(biāo)志位。如設(shè)置 uxBitsToSet 為 0x09 則位 3和位 0 都需要被置位。返回調(diào)用 xEventGroupSetBits 時(shí)事件組中的值。
事件組清除位,任務(wù)中使用xEventGroupClearBits,中斷中使用 xEventGroupClearBitsFromISR,都是用于清除事件組指定的位,如果在獲取事件的時(shí)候沒有將對(duì)應(yīng)的標(biāo)志位清除,那么就需要用這個(gè)函數(shù)來進(jìn)行顯式清除。
xEventGroup 事件句柄。uxBitsToClear 指定事件組中的哪個(gè)位需要清除。如設(shè)置 uxBitsToSet 為 0x09則位 3和位 0 都需要被清除。
讀取事件標(biāo)志,任務(wù)中使用 xEventGroupGetBits,中斷中使用xEventGroupGetBitsFromISR。
重點(diǎn)是等待事件函數(shù)xEventGroupWaitBits,獲取任務(wù)感興趣的事件且支持等待超時(shí)機(jī)制,當(dāng)且僅當(dāng)任務(wù)等待的事件發(fā)生時(shí),任務(wù)才能獲取到事件信息。否則任務(wù)將保持阻塞狀態(tài)以等待事件發(fā)生。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的事件設(shè)置對(duì)應(yīng)的標(biāo)志位,該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。
EventGroupWaitBits用于獲取事件組中的一個(gè)或多個(gè)事件發(fā)生標(biāo)志,當(dāng)要讀取的事件標(biāo)志位沒有被置位時(shí),任務(wù)將進(jìn)入阻塞等待狀態(tài)。要想使用該函數(shù)必 須 把FreeRTOS/source/event_groups.c 這個(gè) 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 一個(gè)按位或的值,指定需要等待事件組中的哪些位置1。如需要等待 bits 0 and/or bit 1 and/or bit 2則 uxBitsToWaitFor 配置為 0x07(0111b)。
xClearOnExit pdTRUE:xEventGroupWaitBits 等待到滿足任務(wù)喚醒的事件時(shí),系統(tǒng)將清除由形參uxBitsToWaitFor 指定的事件標(biāo)志位。pdFALSE:不會(huì)清除由形參 uxBitsToWaitFor 指定的事件標(biāo)志位。
xWaitForAllBits pdTRUE :當(dāng)形參 uxBitsToWaitFor 指定的位都置位的時(shí)候,xEventGroupWaitBits才滿足任務(wù)喚醒的條件,這也是“邏輯與”等待事件,并且在沒有超時(shí)的情況下返回對(duì)應(yīng)的事件標(biāo)志位的值。pdFALSE:當(dāng)形參 uxBitsToWaitFor 指定的位有其中任意一個(gè)置位的時(shí)候,這也是常說的“邏輯或”等待事件,在沒有超時(shí)的情況下 函數(shù)返回對(duì)應(yīng)的事件標(biāo)志位的值。xTicksToWait 最大超時(shí)時(shí)間,單位為系統(tǒng)節(jié)拍周期
返回值
返回事件中的哪些事件標(biāo)志位被置位,返回值很可能并不是用戶指定的事件位,需要對(duì)返回值進(jìn)行判斷再處理 。
其應(yīng)用類似某個(gè)全局變量,等待事件的任務(wù)在設(shè)定的時(shí)間內(nèi),監(jiān)控該變量某些位的值;該值由其他任務(wù)或中斷修改。
九、 任務(wù)通知
FreeRTOS 從 V8.2.0 版本開始提供任務(wù)通知這個(gè)功能,可以在一定場(chǎng)合下替代 FreeRTOS 的信號(hào)量,隊(duì)列、事件組等,但是使用也有局限性。將宏定義 configUSE_TASK_NOTIFICATIONS 設(shè)置為 1才能開啟開功能。但該功能并不常用。
十、 內(nèi)存管理 10.1 內(nèi)存管理的概念
FreeRTOS 內(nèi)存管理模塊管理用于系統(tǒng)中內(nèi)存資源,它是操作系統(tǒng)的核心模塊之一。主要包括內(nèi)存的初始化、分配以及釋放。一般不同的平臺(tái)移植代碼,內(nèi)存的動(dòng)態(tài)申請(qǐng)和釋放接口需要替換。嵌入式實(shí)時(shí)操作系統(tǒng)中,一般不支持標(biāo)準(zhǔn)C庫中的 malloc和 free,其內(nèi)存有限,隨著內(nèi)存不斷被分配和釋放,整個(gè)系統(tǒng)內(nèi)存區(qū)域會(huì)產(chǎn)生越來越多的碎片。
FreeRTOS提供了 5 種內(nèi)存管理算法,源文件在SourceportableMemMang 路徑下,使用的時(shí)候選擇其中一個(gè)。heap_1.c、heap_2.c 和 heap_4.c 這三種內(nèi)存管理方案,內(nèi)存堆實(shí)際上是一個(gè)很大的 數(shù) 組ucHeap。
heap_1.c內(nèi)存管理方案簡(jiǎn)單,它只能申請(qǐng)內(nèi)存而不能進(jìn)行內(nèi)存釋放。有些嵌入式系統(tǒng)并不會(huì)經(jīng)常動(dòng)態(tài)申請(qǐng)與釋放內(nèi)存,一般都是在系統(tǒng)啟動(dòng)后就一直使用下去,永不刪除,適合這種方式。
heap_2.c 方案支持釋放申請(qǐng)的內(nèi)存,但是它不能把相鄰的兩個(gè)小的內(nèi)存塊合成一個(gè)大的內(nèi)存塊,對(duì)于每次申請(qǐng)內(nèi)存大小都比較固定的;但每次申請(qǐng)并不是固定內(nèi)存大小的則會(huì)造成內(nèi)存碎片。如下圖,隨著不斷的申請(qǐng)釋放,空閑空間會(huì)變成很多小片段。
heap_3.c 方案只是封裝了標(biāo)準(zhǔn) C 庫中的 malloc和 free函數(shù),由編譯器提供,需要通過編譯器或者啟動(dòng)文件設(shè)置堆空間。
heap_4.c 方案是在heap_2.c 基礎(chǔ)上,對(duì)內(nèi)存碎片進(jìn)行了改進(jìn),能把相鄰的空閑的內(nèi)存塊合并成一個(gè)更大的塊,這樣可以減少內(nèi)存碎片。
heap_5.c 方案在實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存分配時(shí)與 heap4.c 方案一樣,采用最佳匹配算法和合并算法,并且允許內(nèi)存堆跨越多個(gè)非連續(xù)的內(nèi)存區(qū),也就是允許在不連續(xù)的內(nèi)存堆中實(shí)現(xiàn)內(nèi)存分配,比如做圖形顯示,可能芯片內(nèi)部的 RAM 不足,額外擴(kuò)展SDRAM,那這種內(nèi)存管理方案則比較合適。
一般物聯(lián)網(wǎng)平臺(tái)使用的是heap_4.c。
10.2 內(nèi)存管理接口
不管其內(nèi)部的管理如何實(shí)現(xiàn)的,對(duì)上層應(yīng)用層的接口都是一樣的。
1.void* pvPortMalloc( size_txSize ) ; //內(nèi)存申請(qǐng)函數(shù)
2.voidvPortFree( void*pv ) ; //內(nèi)存釋放函數(shù)
3.voidvPortInitialiseBlocks( void) ; //初始化內(nèi)存堆函數(shù)
4.size_txPortGetFreeHeapSize( void) ; //獲取當(dāng)前未分配的內(nèi)存堆大小
5.size_txPortGetMinimumEverFreeHeapSize( void) ; //獲取未分配的內(nèi)存堆歷史最小值
一般主要是使用內(nèi)存申請(qǐng)和釋放兩個(gè)接口,用法和注意事項(xiàng)同malloc/free一樣,成對(duì)使用。內(nèi)存釋放后盡量將指針設(shè)為NULL。
十一、 通用接口
一些常用接口進(jìn)行說明。
11.1 臨界段
進(jìn)入和退出臨界段的宏在 task.h 中定義,進(jìn)入和退出臨界段的宏分中斷保護(hù)版本和非中斷版本,但最終都是通過開/關(guān)中斷來實(shí)現(xiàn)。主要用于對(duì)全局變量的控制,系統(tǒng)使用非常多,但實(shí)際項(xiàng)目中沒使用,因?yàn)槿肿兞康漠惓TL問時(shí)小概率問題,只是測(cè)試沒發(fā)現(xiàn),理論上是存在問題的。
1./* 在中斷場(chǎng)合*/{
2.uint32_tulReturn;
3.
4.ulReturn = taskENTER_CRITICAL_FROM_ISR; /* 進(jìn)入臨界段,臨界段可以嵌套 */
5.
6./* 臨界段代碼 */
7.
8.taskEXIT_CRITICAL_FROM_ISR( ulReturn ); } /* 退出臨界段 */
1./* 在非中斷場(chǎng)合 */{
2.
3.taskENTER_CRITICAL; /* 進(jìn)入臨界段 */
4.
5./* 臨界段代碼 */
6.
7.taskEXIT_CRITICAL; } /* 退出臨界段*/
11.2 任務(wù)阻塞延時(shí)
vTaskDelay 阻塞延時(shí),任務(wù)調(diào)用該延時(shí)函數(shù)后會(huì)被剝離 CPU 使用權(quán),進(jìn)入阻塞狀態(tài),直到延時(shí)結(jié)束。但是該函數(shù)不能用在中斷服務(wù)和定時(shí)回調(diào)函數(shù)。延時(shí)單位是tick。
11.3 獲取系統(tǒng)時(shí)鐘計(jì)數(shù)值 1.TickType_t xTaskGetTickCount( void)
2. TickType_t xTaskGetTickCountFromISR( void)
注意該接口分任務(wù)版和中斷版,該接口獲取的是tick計(jì)數(shù)值,需要結(jié)合系統(tǒng)時(shí)鐘頻率轉(zhuǎn)換成時(shí)間。
11.4 中斷回調(diào)函數(shù)
和其它平臺(tái)不同,中斷回調(diào)中釋放中斷標(biāo)記即可,freeRTOS中,中斷觸發(fā)后,可能某些阻塞的任務(wù)獲取了相關(guān)信號(hào),需要立刻執(zhí)行,因此中斷服務(wù)發(fā)送消息后,需要主動(dòng)查詢阻塞任務(wù)的情況,執(zhí)行任務(wù)切換動(dòng)作。
1.staticuint32_tulExampleInterruptHandler( void)
2. {
3.BaseType_t xHigherPriorityTaskWoken;
4.
5.xQueueSendToBackFromISR (xQueueRx,&cChar,&xHigherPriorityTaskWoken);
6.portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
評(píng)論