FreeRTOS(讀作"free-arr-toss")是一個嵌入式系統(tǒng)使用的開源實時操作系統(tǒng)。FreeRTOS被設(shè)計為“小巧,簡單,和易用”,能支持許多不同硬件架構(gòu)以及交叉編譯器。
FreeRTOS自2002年Richard Barry開始開發(fā)以來,一直都在積極開發(fā)中。至于我,我不是FreeRTOS的開發(fā)人員或貢獻者,我只不過是一個最終用戶和愛好者。因此,這章將著重與FreeRTOS架構(gòu)之“是什么”和“怎么做”,而相對本書其他章節(jié)來說,較少去講“為什么”。
就像所有操作系統(tǒng)一樣,F(xiàn)reeRTOS的主要工作是執(zhí)行任務(wù)。大部分FreeRTOS的代碼都涉及優(yōu)先權(quán)、調(diào)度以及執(zhí)行用戶自定義任務(wù)。但又與所有其他操作系統(tǒng)不同,F(xiàn)reeRTOS是一款運行在嵌入式系統(tǒng)上的實時操作系統(tǒng)。
到本章結(jié)束,我希望你可以了解FreeRTOS的基本架構(gòu)。大部分FreeRTOS致力于執(zhí)行任務(wù),所以你可以很好地看到它究竟是如何做到的。
如果這是你首次去深入了解一個操作系統(tǒng),我還是希望你可以學(xué)習(xí)到最基本的操作系統(tǒng)是如何工作的。FreeRTOS是相對簡單的,特別是相比Windows,linux,或者OS X而言,不過所有操作系統(tǒng)都有著相同的概念和目標,所以不論學(xué)習(xí)哪個操作系統(tǒng)都是有啟發(fā)和有趣的。
3.1 什么是“嵌入式”和“實時”?
“嵌入式”和“實時”對于不同的人來說代表不同的理解,所以讓我們像FreeRTOS用戶那樣來定義它們。
嵌入式系統(tǒng)就是一個專門設(shè)計用來做一些簡單事情的計算機系統(tǒng),就像是電視遙控器,車載GPS,電子手表,或者起搏器這類。嵌入式系統(tǒng)比通用計算機系統(tǒng)顯著的區(qū)別在于更小和更慢,通常也更便宜。一個典型的低端嵌入式系統(tǒng)可能有一個運行速度為25MHz的8位CPU,幾KB的內(nèi)存,和也許32KB的閃存。一個高端的嵌入式系統(tǒng)可能有一個運行速度為750MHz的32位CPU,一個GB左右的內(nèi)存,和幾個GB的閃存。
實時系統(tǒng)是設(shè)計去完成一定時間內(nèi)的事,它們保證這些事是在應(yīng)該做的時候去做。
心臟起搏器是實時嵌入式系統(tǒng)的一個極好例子。起搏器必須在正確的時間收縮心肌,以挽救你的生命;它不能夠太忙而沒有及時響應(yīng)。心臟起搏器以及其他的實時嵌入式系統(tǒng)都必須精心設(shè)計,以便在任何時刻都能及時執(zhí)行它們的任務(wù)。
3.2架構(gòu)概述
FreeRTOS是一個相對較小的應(yīng)用程序。最小化的FreeRTOS內(nèi)核僅包括3個(.c)文件和少數(shù)頭文件,總共不到9000行代碼,還包括了注釋和空行。一個典型的編譯后(二進制)代碼映像小于10KB。
FreeRTOS的代碼可以分解為三個主要區(qū)塊:任務(wù),通訊,和硬件接口。
●任務(wù):大約有一半的FreeRTOS的核心代碼用來處理多數(shù)操作系統(tǒng)首要關(guān)注的問題:任務(wù)。任務(wù)是給定優(yōu)先級的用戶定義的C函數(shù)。task.c和task.h完成了所有有關(guān)創(chuàng)建,調(diào)度,和維護任務(wù)的繁重工作。
●通訊:任務(wù)很重要,不過任務(wù)間可以互相通訊則更為重要!它給我們帶來FreeRTOS的第二項任務(wù):通訊。大約40%的FreeRTOS核心代碼是用來處理通訊的。queue.c和queue.h是負責處理FreeRTOS的通訊的。任務(wù)和中斷使用隊列互相發(fā)送數(shù)據(jù),并且使用信號燈和互斥來發(fā)送臨界資源的使用情況。
●硬件接口:接近9000行的代碼拼湊起基本的FreeRTOS,是硬件無關(guān)的;相同的代碼都能夠運行,不論FreeRTOS是運行在不起眼的8051,還是最新、最炫的ARM內(nèi)核上。大約有6%的FreeRTOS的核心代碼,在硬件無關(guān)的FreeRTOS內(nèi)核與硬件相關(guān)的代碼間扮演著墊片的角色。我們將在下個部分討論硬件相關(guān)的代碼。
硬件注意事項
硬件無關(guān)的FreeRTOS層在硬件相關(guān)層之上。硬件相關(guān)層聲明了你選擇什么樣的芯片架構(gòu)。圖3.1顯示了FreeRTOS的各層。
圖3.1:FreeRTOS的軟件層
FreeRTOS包含所有你需要用來啟動很運行系統(tǒng)的硬件無關(guān)以及硬件相關(guān)的代碼。它支持許多編譯器(CodeWarrior,GCC,IAR等)也支持許多處理器架構(gòu)(ARM7,ARM Cortex-M3,PICs各系列,Silicon Labs 8051, x86等)。請參閱FreeRTOS網(wǎng)站,可以看到處理器和編譯器支持列表。
FreeRTOS是高可配置設(shè)計。FreeRTOS可以被編譯成為適合單CPU,極簡RTOS,只之支持少數(shù)任務(wù)的操作系統(tǒng),也可以被編譯成為適合多核功能強大的結(jié)合了TCP/IP,文件系統(tǒng),和USB的怪獸。
配置選項可以通過設(shè)置不同的#defines,在FreeRTOSConfig.h文件里選擇。時鐘速度,堆大小,互斥,和API子集,連同其他許多選項,都可以在這個文件中配置。這里是幾個例子,設(shè)置了任務(wù)優(yōu)先級的最大數(shù)量,CPU的頻率,系統(tǒng)節(jié)拍器的頻率,最小的堆棧大小和總的堆大?。?br />
#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 )
#define configCPU_CLOCK_HZ ( 12000000UL )
#define configTICK_RATE_HZ ( ( portTickType ) 1000 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 100 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024 ) )
對于不同的交叉編譯器和CPU架構(gòu),硬件相關(guān)代碼分布在多個文件中。舉例來說,如果你使用ARM Cortex-M3芯片,IAR編譯器工作,那么硬件相關(guān)的代碼就存在FreeRTOS/Source/portable/IAR/ARM_CM3/目錄下。portmacro.h文件聲明了所有硬件特定功能,port.c和portasm.s 包含了所有實際硬件相關(guān)的代碼。硬件無關(guān)的頭文件portable.h在編譯的時候用#include's引入正確的portmacro.h文件。FreeRTOS使用#define'd調(diào)用在portmacro.h中聲明的硬件特定功能。
讓我們來看一個例子,F(xiàn)reeRTOS是如何調(diào)用一個硬件相關(guān)功能的。硬件無關(guān)的文件tasks.c常常需要插入一個代碼的臨界區(qū),以防止搶占。在不同架構(gòu)上,插入一個臨界區(qū)的表現(xiàn)也不同,并且硬件無關(guān)的task.c不需要了解硬件相關(guān)的細節(jié)。所以task.c調(diào)用全局宏指令portENTER_CRITICAL(), 樂得忽略它實際上是如何做到的。假設(shè)我們使用IAR編譯器在ARM Crotex-M3芯片上編譯生成FreeRTOS,使用那個定義了portENTER_CRITICAL()的文件/Source/portable/IAR/ARM_CM3/portmacro.h,如下所示:
#define portENTER_CRITICAL() vPortEnterCritical()
vPortEnterCritical()實際上是在FreeRTOS/Source/portable/IAR/ARM_CM3/port.c中定義的。這個port.c文件是一個硬件相關(guān)的文件,同時包含了對IAR編譯器和Cortex-M3芯片認識的代碼文件。vPortEnterCritical()函數(shù)利用這些硬件特定的知識進入臨界區(qū),又返回到與硬件無關(guān)的task.c中。
protmacro.h文件也定義了一個數(shù)據(jù)類型的基本架構(gòu)。這個數(shù)據(jù)類型中包括了基本整型變量,指針,以及系統(tǒng)時鐘節(jié)拍器的數(shù)據(jù)類型,在ARM Cortex-M3 chips上使用IAR編譯器時,就采用如下定義:
#define portBASE_TYPE long // Basic integer variable type
#define portSTACK_TYPE unsigned long // Pointers to memory locations
typedef unsigned portLONG portTickType; // The system timer tick type
這樣使用數(shù)據(jù)類型的方法,和函數(shù)透過小層的#defines,看上去略微有點復(fù)雜,不過它允許了FreeRTOS能夠被重新編譯在一個完全不同的系統(tǒng)架構(gòu)上,僅僅只需要通過修改這些硬件相關(guān)的文件。同時,如果你想要讓FreeRTOS運行在一個當前尚未被支持的架構(gòu)上,你只僅僅需要去實現(xiàn)硬件相關(guān)的功能,這要比在FreeRTOS上去實現(xiàn)硬件無關(guān)的部分,要少得多。
就如同我們已經(jīng)見到的,F(xiàn)reeRTOS用C的預(yù)處理宏#define來實現(xiàn)硬件相關(guān)的功能。FreeRTOS也同樣用#define來應(yīng)對大量的硬件無關(guān)的代碼。對于非嵌入式應(yīng)用程序這樣頻繁使用#define是一個嚴重的錯誤,不過在許多小型嵌入式系統(tǒng)中這點開銷比起“實時”所提供的功能來說就微不足道了。
3.3. 調(diào)度任務(wù):快速概述
任務(wù)優(yōu)先級和就緒列表
所有任務(wù)都有一個用戶指定優(yōu)先級,從0(最低優(yōu)先級)到 configMAX_PRIORITIES-1的編譯時間值(最高優(yōu)先級)。例如,如果configMAX_PRIORITIES設(shè)置為5,當FreeRTOS使用5個優(yōu)先等級時:0(最低優(yōu)先級),1,2,3,和4(最高優(yōu)先級)。
FreeRTOS使用一個“就緒列表”來跟蹤所有已經(jīng)準備好運行的任務(wù)。它像一個任務(wù)列表數(shù)組來實現(xiàn)就緒列表,如下所示:
static xList pxReadyTasksLists[ configMAX_PRIORITIES ]; /* Prioritised ready tasks. */
pxReadyTasksLists[0]是所有準備好的優(yōu)先級為0的任務(wù)列表,pxReadyTasksLists[1]是所有準備好的優(yōu)先級為1的任務(wù)列表,以此類推,直到pxReadyTasksLists[configMAX_PRIORITIES-1]。
系統(tǒng)節(jié)拍器(時鐘)
FreeRTOS系統(tǒng)的心跳就是被稱為系統(tǒng)節(jié)拍器(時鐘)。FreeRTOS配置這個系統(tǒng)生成一個定期的節(jié)拍(時鐘)中斷。用戶可以配置的節(jié)拍中斷頻率,通常是在毫秒范圍。vTaskSwitchContext()函數(shù)在每次的節(jié)拍中斷釋放的時候被調(diào)用。vTaskSwitchContext()選擇優(yōu)先級最高的就緒任務(wù)并將它賦予pxCurrentTCB變量,如下所示:
/* Find the highest-priority queue that contains ready tasks. */
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )
{
configASSERT( uxTopReadyPriority );
--uxTopReadyPriority;
}
/* listGET_OWNER_OF_NEXT_ENTRY walks through the list, so the tasks of the same
priority get an equal share of the processor time. */
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );
在當型循環(huán)(while loop)開始之前,uxTopReadyPriority就被確保大于或等于優(yōu)先級最高的就緒任務(wù)。while()循環(huán)從優(yōu)先級uxTopReadyPriority開始,循環(huán)走下去從pxReadyTasksLists[]數(shù)組里找到就緒任務(wù)優(yōu)先級最高的那個。接著listGET_OWNER_OF_NEXT_ENTRY()就搶占那個就緒列表中優(yōu)先級最高的下一個就緒任務(wù)。
現(xiàn)在pxCurrentTCB指向了優(yōu)先級最高的任務(wù),并且當vTaskSwitchContext()返回硬件相關(guān)代碼時開始運行那個任務(wù)。
那九行代碼是FreeRTOS的絕對核心。其余FreeRTOS的8900+行代碼都是用來確保那九行代碼,全都是用來保持優(yōu)先級最高任務(wù)的運行的。
圖3.2是一個大致的就緒列表看起來像什么的圖。這個例子有三個優(yōu)先級,有一個優(yōu)先級為0的任務(wù),沒有優(yōu)先級為1的任務(wù),和三個優(yōu)先級為2的任務(wù)。這張圖是準確的,但不完整的;它的少掉一些細節(jié),我們稍后將補充。
圖3.2:FreeRTOS的就緒列表的基本視圖
現(xiàn)在我們有了大致概述的方式,讓我們?nèi)ド罹克募毠?jié)。我們將著眼于三個主要FreeRTOS的數(shù)據(jù)結(jié)構(gòu):任務(wù),列表和隊列。
3.4. 任務(wù)
所有操作系統(tǒng)的主要工作是運行和協(xié)調(diào)用戶任務(wù)。像多數(shù)操作系統(tǒng)一樣,F(xiàn)reeRTOS中的基本工作單元是任務(wù)。FreeRTOS的使用任務(wù)控制塊(TCB)來表示每個任務(wù)。
任務(wù)控制塊(TCB)
TCB的在tasks.c定義是這樣的:
typedef struct tskTaskControlBlock
{
volatile portSTACK_TYPE *pxTopOfStack; /* Points to the location of
the last item placed on
the tasks stack. THIS
MUST BE THE FIRST MEMBER
OF THE STRUCT. */
xListItem xGenericListItem; /* List item used to place
the TCB in ready and
blocked queues. */
xListItem xEventListItem; /* List item used to place
the TCB in event lists.*/
unsigned portBASE_TYPE uxPriority; /* The priority of the task
where 0 is the lowest
priority. */
portSTACK_TYPE *pxStack; /* Points to the start of
the stack. */
signed char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* Descriptive name given
to the task when created.
Facilitates debugging
only. */
#if ( portSTACK_GROWTH > 0 )
portSTACK_TYPE *pxEndOfStack; /* Used for stack overflow
checking on architectures
where the stack grows up
from low memory. */
#endif
#if ( configUSE_MUTEXES == 1 )
unsigned portBASE_TYPE uxBasePriority; /* The priority last
assigned to the task -
used by the priority
inheritance mechanism. */
#endif
} tskTCB;
TCB在pxStack里存儲堆棧的起始地址,以及在pxTopOfStack里存儲當前堆棧的頂部。如果堆?!跋蛏稀痹鲩L到更高的地址,它還在pxEndOfStack存儲堆棧的結(jié)束的指針來檢查堆棧溢出。如果堆?!跋蛳隆痹鲩L到更低的地址,那么通過比較當前堆棧的頂部與pxStack中的堆內(nèi)存起始位置來檢查溢出。
TCB在uxPriority和uxBasePriority中存儲任務(wù)的初始優(yōu)先級。一個任務(wù)在它創(chuàng)建的時候被賦予優(yōu)先級,同時任務(wù)的優(yōu)先級是可以被改變的。如果FreeRTOS實現(xiàn)了優(yōu)先級繼承,那么當任務(wù)臨時提升到“繼承的”優(yōu)先級時,它使用uxBasePriority去記住原來的優(yōu)先級。(優(yōu)先級繼承,請參見下面關(guān)于互斥的討論。)
每個任務(wù)有兩個清單項目給FreeRTOS操作系統(tǒng)的各種調(diào)度列表使用。當一個任務(wù)被插入到FreeRTOS的一個列表中,不會直接向TCB插入一個指針。取而代之的是,它向TCB的xGenericListItem或xEventListItem插入一個指針。這些xListItem變量,比起若是僅僅獲得一個指向TCB的指針來說,讓FreeRTOS的列表變得更加靈活。
任務(wù)可以在以下四種狀態(tài)之一:運行,準備運行,掛起或阻塞。你可能希望每個任務(wù)都有一個變量來告訴FreeRTOS它正處于什么狀態(tài),但事實上并非如此。相反,F(xiàn)reeRTOS通過把任務(wù)放入相應(yīng)的列表:就緒列表,掛起列表等,隱式地跟蹤任務(wù)狀態(tài)。隨著任務(wù)的變化,從一個狀態(tài)到另一個,F(xiàn)reeRTOS的只是簡單的將它從一個列表移動到另一個。
任務(wù)設(shè)置
我們已經(jīng)觸及如何利用pxReadyTasksLists數(shù)組來選擇和調(diào)度一個任務(wù)的;現(xiàn)在讓我們來看一看一個任務(wù)最初是如何被創(chuàng)建的。當xTaskCreate()函數(shù)被調(diào)用的時候,一個任務(wù)被創(chuàng)建。FreeRTOS為一個任務(wù)新分配一個TCB對象,來記錄它的名稱,優(yōu)先級,和其他細節(jié),接著分配用戶請求的總的堆棧(假設(shè)有足夠使用的內(nèi)存)和在TCB的pxStack成員中記錄堆內(nèi)存的開始。
堆棧被初始化看起來就像一個已經(jīng)在運行的新任務(wù)被上下文切換所中斷。這就是任務(wù)調(diào)度處理最新創(chuàng)建的任務(wù)的方法,同樣也是處理運行了一段時間的任務(wù)的方法;任務(wù)調(diào)度在不需要任何特殊(case)代碼的情況下去處理新的任務(wù)。
任務(wù)的堆棧建立看起來像是它通過上下文切換來被中斷,這個方法是取決于FreeRTOS正在運行的架構(gòu),但這個ARM Cortex-M3處理器的實現(xiàn)是一個很好的例子:
unsigned int *pxPortInitialiseStack( unsigned int *pxTopOfStack,
pdTASK_CODE pxCode,
void *pvParameters )
{
/* Simulate the stack frame as it would be created by a context switch interrupt. */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on
entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) pxCode; /* PC */
pxTopOfStack--;
*pxTopOfStack = 0; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( portSTACK_TYPE ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
當一個任務(wù)被中斷的時候,ARM Cortex-M3處理器就壓寄存器入堆棧。pxPortInitialiseStack()修改堆棧使之看來像是即便任務(wù)實際上還未開始運行,寄存器就已經(jīng)被壓入了。已知的值被存儲到堆棧,賦給ARM寄存器xPSR,PC,LR,和R0。剩余的寄存器R1-R12獲得由棧頂指針遞減分配給它們的寄存器空間,但沒有具體的數(shù)據(jù)存儲在這些寄存器堆棧內(nèi)。ARM架構(gòu)告訴我們那些寄存器在復(fù)位的時候未被定義,所以一個(非弱智的)程序?qū)⒉灰蕾囉谝阎闹怠?/p>
堆棧準備好后,任務(wù)幾乎是同時準備運行。首先,F(xiàn)reeRTOS禁用中斷:我們將開始使用就緒列表和其他任務(wù)調(diào)度的數(shù)據(jù)結(jié)構(gòu),同時我們不希望它們被其他人背著我們私底下修改。
如果這是被創(chuàng)建的第一個任務(wù),F(xiàn)reeRTOS將初始化調(diào)度的任務(wù)列表。FreeRTOS操作系統(tǒng)的調(diào)度有一個就緒列表的數(shù)組,pxReadyTasksLists [],為每一個可能的優(yōu)先級提供一個就緒列表。FreeRTOS也有一些其他的列表用來跟蹤任務(wù)的掛起,終止和延時。現(xiàn)在這些也都是初始化的。
任何第一次初始化完成后,新的任務(wù)以它指定的優(yōu)先級被加入就緒列表。中斷被重新啟用,同時新任務(wù)的創(chuàng)建完成。
3.5. 列表
任務(wù)之后,最常用的FreeRTOS數(shù)據(jù)結(jié)構(gòu)是列表。FreeRTOS使用列表結(jié)構(gòu)來跟蹤調(diào)度任務(wù),并執(zhí)行隊列。
圖3.3:就緒列表全貌
這個FreeRTOS的列表是一個有著幾個有趣的補充的標準循環(huán)雙鏈表。下面就是列表元素:
struct xLIST_ITEM
{
portTickType xItemValue; /* The value being listed. In most cases
this is used to sort the list in
descending order. */
volatile struct xLIST_ITEM * pxNext; /* Pointer to the next xListItem in the
list. */
volatile struct xLIST_ITEM * pxPrevious; /* Pointer to the previous xListItem in
the list. */
void * pvOwner; /* Pointer to the object (normally a TCB)
that contains the list item. There is
therefore a two-way link between the
object containing the list item and
the list item itself. */
void * pvContainer; /* Pointer to the list in which this list
item is placed (if any). */
};
每個元素持有一個數(shù)字,xItemValue,這通常是一個被跟蹤的任務(wù)優(yōu)先級或者是一個調(diào)度事件的計時器值。列表保存從高到低的優(yōu)先級指令,這意味著最高的優(yōu)先級xItemValue(最大數(shù))在列表的最前端,而最低的優(yōu)先級xItemValue(最小數(shù))在列表的末尾。
pxNext和pxPrevious指針是標準鏈表指針。pvOwner 列表元素所有者的指針。這通常是任務(wù)的TCB對象的指針。pvOwner被用來在vTaskSwitchContext()中加快任務(wù)切換:當最高優(yōu)先級任務(wù)元素在pxReadyTasksLists[]中被發(fā)現(xiàn),這個列表元素的pvOwner指針直接連接到需要任務(wù)調(diào)度的TCB。
pvContainer指向自己所在的這個列表。若列表項處于一個特定列表它被用作快速終止。任意列表元素可以被置于一個列表,如下所定義:
typedef struct xLIST
{
volatile unsigned portBASE_TYPE uxNumberOfItems;
volatile xListItem * pxIndex; /* Used to walk through the list. Points to
the last item returned by a call to
pvListGetOwnerOfNextEntry (). */
volatile xMiniListItem xListEnd; /* List item that contains the maximum
possible item value, meaning it is always
at the end of the list and is therefore
used as a marker. */
} xList;
列表的大小任何時候都是被存儲在uxNumberOfItems中,用于快速列表大小操作。所有的列表都被初始化為容納一個元素:xListEnd元素。xListEnd.xItemValue是一個定點值,當portTickType是16位數(shù)時,它等于xItemValue變量的最大值:0xffff,portTickType是32位數(shù)時為0xffffffff。其他的列表元素也可以使用相同的值;插入算法保證了xListEnd總是列表項中最后一個值。
自列表從高到低排序后,xListEnd被用作列表開始的記號。并且,自循環(huán)開始,xListEnd也被用作列表結(jié)束的記號。
你也許可以用一個單獨的for()循環(huán)或者是函數(shù)調(diào)用來訪問大多數(shù)“傳統(tǒng)的”列表,去做所有的工作,就像這樣:
for (listPtr = listStart; listPtr != NULL; listPtr = listPtr->next) {
// Do something with listPtr here...
}
FreeRTOS經(jīng)常需要通過多個for()和while()循環(huán),也包括函數(shù)調(diào)用來訪問列表,因此它使用操縱pxIndex指針的列表函數(shù)來遍歷這個列表。這個列表函數(shù)listGET_OWNER_OF_NEXT_ENTRY()執(zhí)行pxIndex = pxIndex->pxNext;并且返回pxIndex。(當然它也會正確檢測列尾環(huán)繞。)這種,當執(zhí)行遍歷的時候使用pxIndex,由列表自己負責跟蹤“在哪里”的方法,使FreeRTOS可以休息而不用關(guān)心這方面的事。
圖3.4:系統(tǒng)節(jié)拍計時器下的FreeRTOS就緒列表全貌
pxReadyTasksLists[]列出了在vTaskSwitchContext()中已經(jīng)操縱完成的內(nèi)容,是如何使用pxIndex的一個很好的例子。讓我們假設(shè)我們僅有一個優(yōu)先級,優(yōu)先級0,并且有三個任務(wù)在此優(yōu)先級上。這與我們之前看到的基本就緒列表圖相似,但這一次我們將包括所有的數(shù)據(jù)結(jié)構(gòu)和字段。
就如你在圖3.3中所見,pxCurrentTCB顯示我們當前正在運行任務(wù)B。下一個時刻,vTaskSwitchContext()運行,它調(diào)用listGET_OWNER_OF_NEXT_ENTRY()載入下一個任務(wù)來運行。如圖3.4所示,這個函數(shù)使用pxIndex->pxNext找出下一個任務(wù)是任務(wù)C,并且pxIndex指向任務(wù)C的列表元素,同時pxCurrentTCB指向任務(wù)C的TCB。
請注意,每個struct xlistitem對象實際上都是來自相關(guān)TCB的xGenericListItem對象。
3.6. 隊列
FreeRTOS允許任務(wù)使用隊列來互相間通信和同步。中斷服務(wù)程序(ISRs)同樣使用隊列來通信和同步。
基本隊列數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct QueueDefinition
{
signed char *pcHead; /* Points to the beginning of the queue
storage area. */
signed char *pcTail; /* Points to the byte at the end of the
queue storage area. One more byte is
allocated than necessary to store the
queue items; this is used as a marker. */
signed char *pcWriteTo; /* Points to the free next place in the
storage area. */
signed char *pcReadFrom; /* Points to the last place that a queued
item was read from. */
xList xTasksWaitingToSend; /* List of tasks that are blocked waiting
to post onto this queue. Stored in
priority order. */
xList xTasksWaitingToReceive; /* List of tasks that are blocked waiting
to read from this queue. Stored in
priority order. */
volatile unsigned portBASE_TYPE uxMessagesWaiting; /* The number of items currently
in the queue. */
unsigned portBASE_TYPE uxLength; /* The length of the queue
defined as the number of
items it will hold, not the
number of bytes. */
unsigned portBASE_TYPE uxItemSize; /* The size of each items that
the queue will hold. */
} xQUEUE;
這是一個頗為標準的隊列,不但包括了頭部和尾部指針,而且指針指向我們剛剛讀過或者寫過的位置。
當剛剛創(chuàng)建一個隊列,用戶指定了隊列的長度和需要隊列跟蹤的項目大小。pcHead和pcTail被用來跟蹤隊列的內(nèi)部存儲器。加入一個項目到隊列就對隊列內(nèi)部存儲器進行一次深拷貝。
FreeRTOS用深拷貝替代在項目中存放一個指針是因為有可能項目插入的生命周期要比隊列的生命周期短。例如,試想一個簡單的整數(shù)隊列使用局部變量,跨幾個函數(shù)調(diào)用的插入和刪除。如果這個隊列在局部變量里存儲這些整數(shù)的指針,當整數(shù)的局部變量離開作用域時指針將會失效,同時局部變量的存儲空間將被新的數(shù)值使用。
什么需要用戶選擇使用隊列。若內(nèi)容很少,用戶可以把復(fù)制的內(nèi)容進行排列,就像上圖中簡單整數(shù)的例子,或者,若內(nèi)容很多,用戶可以排列內(nèi)容的指針。請注意,在這兩種情況下FreeRTOS都是在做深拷貝:如果用戶選擇排列復(fù)制的內(nèi)容,那么這個隊列存儲了每項內(nèi)容的一份深拷貝;如果用戶選擇了排列指針,隊列存儲了指針的一份深拷貝。當然,用戶在隊列里存儲了指針,那么用戶有責任管理與內(nèi)存相關(guān)的指針。隊列并不關(guān)心你存儲了什么樣的數(shù)據(jù),它只需要知道數(shù)據(jù)的大小。
FreeRTOS支持阻塞和非阻塞隊列的插入和移除。非阻塞隊列操作會立即返回"隊列的插入是否完成?"或者 "隊列的移除是否完成?"的狀態(tài)。阻塞操作則根據(jù)特定的超時。一個任務(wù)可以無限期地阻塞或者在有限時間里阻塞。
一個阻塞任務(wù)——叫它任務(wù)A——將保持阻塞只要它的插入/移除操作沒有完成,并且它的超時(如果存在)沒有過期。如果一個中斷或者另一個任務(wù)編輯了這個隊列以便任務(wù)A的操作能夠完成,任務(wù)A將被解除阻塞。如果此時任務(wù)A的隊列操作仍然是允許的,那么它實際上會執(zhí)行操作,于是任務(wù)A會完成它的隊列操作,并且返回“成功”的狀態(tài)。不過,任務(wù)A正在執(zhí)行的那個時間,有可能同時有一個高優(yōu)先級任務(wù)或者中斷也在同一個隊列上執(zhí)行另一個操作,這會阻止任務(wù)A正在執(zhí)行的操作。在這種情況下任務(wù)A將檢查它的超時,同時,如果它未超時就恢復(fù)阻塞,否則就返回隊列操作“失敗”的狀態(tài)。
特別需要注意的是,當任務(wù)被阻塞在一個隊列時,系統(tǒng)保持運行所帶來的風(fēng)險;以及當任務(wù)被阻塞在一個隊列時,有其他任務(wù)或中斷在繼續(xù)運行。這種阻塞任務(wù)的方法能不浪費CPU周期,使其他任務(wù)和中斷可以有效地使用CPU周期。
FreeRTOS使用xTasksWaitingToSend列表來保持對正阻塞在插入隊列里的任務(wù)的跟蹤。每當有一個元素被移出隊列,xTasksWaitingToSend列表就會被檢查。如果有個任務(wù)在那個列表中等待,那個是未阻塞任務(wù)。同樣的,xTasksWaitingToReceive保持對那些正阻塞在移除隊列里的任務(wù)的跟蹤。每當有一個新元素被插入到隊列,xTasksWaitingToReceive列表就會被檢查。如果有個任務(wù)在那個列表中等待,那個是未阻塞任務(wù)。
信號燈和互斥
FreeRTOS使用它的隊列與任務(wù)通信,也在任務(wù)間通信。FreeRTOS也使用它的隊列來實現(xiàn)信號燈與互斥。
有什么區(qū)別?
信號燈與互斥聽上去像一回事,但它們不是。FreeRTOS同樣地實現(xiàn)了它們,但本來它們以不同的方式被使用。它們是如何不同地被使用?嵌入式系統(tǒng)宗師Michael Barr說這是在他文章中寫得最好的,“信號燈與互斥揭秘”:
The correct use of a semaphore is for signaling from one task to another. A mutex is meant to be taken and released, always in that order, by each task that uses the shared resource it protects. By contrast, tasks that use semaphores either signal ["send" in FreeRTOS terms] or wait ["receive" in FreeRTOS terms] - not both.
正確使用的一個信號是從一個任務(wù)向另一個發(fā)信號。從每個使用被保護共享資源的任務(wù)來看,總是認為,一個互斥意味著獲得和釋放。相比之下,使用信號燈的任務(wù)不是發(fā)信號[在FreeRTOS里“發(fā)送”]就是在等信號[在FreeRTOS里“接收”]——不能同時。
互斥被用來保護共享資源。一個使用共享資源的任務(wù)獲得互斥,接著釋放互斥。當有另一個任務(wù)占據(jù)互斥時,沒有一個任務(wù)可以獲得這個互斥。這就是保證,在同一時刻僅有一個任務(wù)可以使用共享資源。
一個任務(wù)向另一個任務(wù)發(fā)信號時使用信號燈。以下引用Barr的文章:
For example, Task 1 may contain code to post (i.e., signal or increment) a particular semaphore when the "power" button is pressed and Task 2, which wakes the display, pends on that same semaphore. In this scenario, one task is the producer of the event signal; the other the consumer.
舉例來說,任務(wù)一可能包含當“電源”按鈕被按下時,發(fā)布(即,發(fā)信號或增加信號量)一個特定的信號燈的代碼,并且喚醒顯示屏的任務(wù)二,取決于同一個信號燈。在這種情況下,一個任務(wù)是發(fā)信號事件的制造者;另一個是消費者。
如果你是在信號燈和互斥有任何疑問,請查閱Michael的文章。
實現(xiàn)
FreeRTOS像隊列一樣來實現(xiàn)一個N元素的信號燈,這樣就可以控制N個項。它沒有去存儲隊列每項的任何實際數(shù)據(jù);信號燈只關(guān)心有多少在隊列的uxMessagesWaiting字段中被跟蹤的隊列記錄,目前正被占用。當FreeRTOS的頭文件semphr.h調(diào)用它的時候,它正在做“純同步”。因此這個隊列有一個零字節(jié)的項 (uxItemSize == 0)。每個信號燈uxMessagesWaiting字段遞增或遞減;沒有項或數(shù)據(jù)的復(fù)制是需要的。
同信號燈一樣,互斥也被實現(xiàn)為一個隊列,不過有幾個xQUEUE結(jié)構(gòu)字段被用#defines重載:
/* Effectively make a union out of the xQUEUE structure. */
#define uxQueueType pcHead
#define pxMutexHolder pcTail
當互斥沒有在隊列中存儲任何數(shù)據(jù)時,它不需要任何內(nèi)部存儲器,同樣pcHead和pcTail字段也不需要。FreeRTOS設(shè)置uxQueueType字段(實際上的pcHead字段)為0來表明,這個隊列正在被一個互斥使用。FreeRTOS使用重載的pcTail字段來實現(xiàn)互斥的優(yōu)先級繼承。
萬一你不熟悉優(yōu)先級繼承,我將再次引用Michael Barr的話來定義它,這次是來自他的文章,“優(yōu)先級倒置”:
[Priority inheritance] mandates that a lower-priority task inherit the priority of any higher-priority task pending on a resource they share. This priority change should take place as soon as the high-priority task begins to pend; it should end when the resource is released.
[優(yōu)先級繼承]要求低優(yōu)先級任務(wù)繼承任何高優(yōu)先級任務(wù)的優(yōu)先級,取決于它們共享的資源。這個優(yōu)先級的改變應(yīng)當在高優(yōu)先級任務(wù)一開始掛起時就發(fā)生;資源被釋放時就結(jié)束。
FreeRTOS使用pxMutexHolder字段(實際上是#define重載的pcTail字段)來實現(xiàn)優(yōu)先級繼承。FreeRTOS記錄在pxMutexHolder字段里包含一個互斥的任務(wù)。當一個高優(yōu)先級任務(wù)正在等待一個由低優(yōu)先級任務(wù)取得的互斥,F(xiàn)reeRTOS“更新”低優(yōu)先級任務(wù)到高優(yōu)先級任務(wù)的優(yōu)先級,直至這個互斥再次可用。
3.7. 結(jié)論
我們完成了對FreeRTOS架構(gòu)的一覽。希望你現(xiàn)在對于FreeRTOS的任務(wù)是如何執(zhí)行以及通信有一個好的感覺。如果之前你從未了解過操作系統(tǒng)的內(nèi)在,我希望現(xiàn)在你對于它們是如何工作的有一個基本的思路。
顯然,本章沒有覆蓋FreeRTOS架構(gòu)的全部。值得注意的是,我沒有提到的內(nèi)存分配,中斷服務(wù),調(diào)試,或MPU支持。本章也沒有討論如何設(shè)置或使用FreeRTOS。Richard Barry已經(jīng)寫了一本極好的書,使用FreeRTOS實時內(nèi)核:實用指南,這本書就是講這些內(nèi)容的;如果你要使用FreeRTOS的話,我強烈推薦它。
評論