在例程中我選擇動(dòng)態(tài)內(nèi)存分配方式,configSUPPORT_DYNAMIC_ALLOCATION為1,相較靜態(tài)內(nèi)存分配方式使用更簡單,但不好的地方就是用戶不能明確分配各個(gè)任務(wù)內(nèi)存。在動(dòng)態(tài)內(nèi)存分配方式,F(xiàn)reeRTOS首先需要有一個(gè)分配好的RAM空間——堆。堆的大小通過configTOTAL_HEAP_SIZE來指定。在例程中分配的是1024個(gè)字,也就是2048字節(jié)。實(shí)際項(xiàng)目中可以在調(diào)試階段借助xPortGetFreeHeapSize()函數(shù)來判斷堆的剩余空間,以便合理定義堆的大小。
在動(dòng)態(tài)分配方式下,當(dāng)用戶調(diào)用xTaskCreate()進(jìn)行任務(wù)創(chuàng)建的時(shí)候,將自動(dòng)在堆中分配任務(wù)堆棧和任務(wù)控制塊,任務(wù)堆棧的大小在創(chuàng)建任務(wù)時(shí)由實(shí)參進(jìn)行指定。同時(shí),如隊(duì)列、互斥信號(hào)量和計(jì)數(shù)型信號(hào)量等在創(chuàng)建后也需從堆中分配空間,堆的示意如下圖。
在任務(wù)創(chuàng)建過程中,經(jīng)pxPortInitialiseStack()函數(shù)完成堆棧初始化后的任務(wù)堆棧情況如下圖,這張圖也是任務(wù)切換時(shí)壓棧和出棧所操作的內(nèi)容,務(wù)必清晰。
堆棧初始化之后,我們看一下如何啟動(dòng)第一個(gè)任務(wù),分析前必須了解如下基礎(chǔ)知識(shí)。
1)程序指針PC(Program Counter):
dsPIC33的PC為24位可尋址4M× 24位的程序空間,這個(gè)空間均等地劃分為用戶程序空間(0x000000~0x7FFFFF)和配置(或測試)存儲(chǔ)空間(0x800000~0xFFFFFF)。因此用戶可以僅用PC<22:0>即可訪問任何用戶程序空間。
注意:堆棧初始化圖中可以看到將任務(wù)函數(shù)的地址賦值給PC<15:0>,而PC<22:16>始終為0。這是因?yàn)閐sPIC33系列MCU是16位的,雖然16位地址可以訪問所有數(shù)據(jù)空間,但不能訪問4M× 24位的程序空間,只能訪問64K× 24位的程序空間。因此這里要提出跳轉(zhuǎn)表的概念,當(dāng)任務(wù)函數(shù)分配到大于64K× 24位的程序空間時(shí)編譯器會(huì)鏈接出一個(gè).handle的段,這個(gè)段中會(huì)定義一條GOTO指令,GOTO到實(shí)際的任務(wù)函數(shù)地址。所以才有堆棧初始化中PC<22:16>始終為0,而PC<15:0> = pxTaskCode的情況,因?yàn)槿艉瘮?shù)分配到了大于64K× 24位的程序空間,則pxTaskCode被鏈接為.handle段跳轉(zhuǎn)表中的1條GOTO指令,借助該指令跳轉(zhuǎn)到任務(wù)函數(shù)。
2)堆棧指針:
dsPIC33堆棧指針就是W15,大家清晰即可。
3)匯編指令:
為了真正的理解堆棧初始化及后續(xù)的任務(wù)切換過程,需要了解掌握如下的基本匯編語法。
同時(shí)xTaskCreate()進(jìn)行任務(wù)創(chuàng)建的時(shí)候還干了一件事,就是通過調(diào)用函數(shù)prvAddNewTaskToReadyList() 將新建任務(wù)添加到就緒列表中,并且會(huì)找到這些新建任務(wù)中優(yōu)先級(jí)最高的任務(wù)。這些任務(wù)創(chuàng)建后,任務(wù)調(diào)度器將通過xPortStartScheduler() 啟動(dòng)第一個(gè)任務(wù),函數(shù)如下。
其中關(guān)鍵的portRESTORE_CONTEXT()的內(nèi)容如下。首先看第一條指令,將所有任務(wù)中優(yōu)先級(jí)最高任務(wù)的任務(wù)控制塊TCB的第一個(gè)成員pxTopOfStack賦值給堆棧指針W15,接下來就比較簡單,按照堆棧初始化的內(nèi)容依次進(jìn)行出棧,一直到SR寄存器出棧。
回過頭我們繼續(xù)看一下xPortStartScheduler(),其在執(zhí)行完portRESTORE_CONTEXT() 后調(diào)用了一條匯編return指令,這個(gè)return指令的結(jié)果便是將啟動(dòng)的第一個(gè)任務(wù)的任務(wù)函數(shù)pxTaskCode的地址賦值給PC指針,之后根據(jù)任務(wù)函數(shù)pxTaskCode在flash中的存儲(chǔ)位置,采用直接運(yùn)行任務(wù)函數(shù)或通過跳轉(zhuǎn)表GOTO間接運(yùn)行任務(wù)函數(shù)。至此FreeRTOS的任務(wù)順利完成了啟動(dòng)工作。