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