FreeRTOS(讀作"free-arr-toss")是一個(gè)嵌入式系統(tǒng)使用的開源實(shí)時(shí)操作系統(tǒng)。FreeRTOS被設(shè)計(jì)為“小巧,簡單,和易用”,能支持許多不同硬件架構(gòu)以及交叉編譯器。
FreeRTOS自2002年Richard Barry開始開發(fā)以來,一直都在積極開發(fā)中。至于我,我不是FreeRTOS的開發(fā)人員或貢獻(xiàn)者,我只不過是一個(gè)最終用戶和愛好者。因此,這章將著重與FreeRTOS架構(gòu)之“是什么”和“怎么做”,而相對(duì)本書其他章節(jié)來說,較少去講“為什么”。
就像所有操作系統(tǒng)一樣,F(xiàn)reeRTOS的主要工作是執(zhí)行任務(wù)。大部分FreeRTOS的代碼都涉及優(yōu)先權(quán)、調(diào)度以及執(zhí)行用戶自定義任務(wù)。但又與所有其他操作系統(tǒng)不同,F(xiàn)reeRTOS是一款運(yùn)行在嵌入式系統(tǒng)上的實(shí)時(shí)操作系統(tǒng)。
到本章結(jié)束,我希望你可以了解FreeRTOS的基本架構(gòu)。大部分FreeRTOS致力于執(zhí)行任務(wù),所以你可以很好地看到它究竟是如何做到的。
如果這是你首次去深入了解一個(gè)操作系統(tǒng),我還是希望你可以學(xué)習(xí)到最基本的操作系統(tǒng)是如何工作的。FreeRTOS是相對(duì)簡單的,特別是相比Windows,linux,或者OS X而言,不過所有操作系統(tǒng)都有著相同的概念和目標(biāo),所以不論學(xué)習(xí)哪個(gè)操作系統(tǒng)都是有啟發(fā)和有趣的。
“嵌入式”和“實(shí)時(shí)”對(duì)于不同的人來說代表不同的理解,所以讓我們像FreeRTOS用戶那樣來定義它們。
嵌入式系統(tǒng)就是一個(gè)專門設(shè)計(jì)用來做一些簡單事情的計(jì)算機(jī)系統(tǒng),就像是電視遙控器,車載GPS,電子手表,或者起搏器這類。嵌入式系統(tǒng)比通用計(jì)算機(jī)系統(tǒng)顯著的區(qū)別在于更小和更慢,通常也更便宜。一個(gè)典型的低端嵌入式系統(tǒng)可能有一個(gè)運(yùn)行速度為25MHz的8位CPU,幾KB的內(nèi)存,和也許32KB的閃存。一個(gè)高端的嵌入式系統(tǒng)可能有一個(gè)運(yùn)行速度為750MHz的32位CPU,一個(gè)GB左右的內(nèi)存,和幾個(gè)GB的閃存。
實(shí)時(shí)系統(tǒng)是設(shè)計(jì)去完成一定時(shí)間內(nèi)的事,它們保證這些事是在應(yīng)該做的時(shí)候去做。
心臟起搏器是實(shí)時(shí)嵌入式系統(tǒng)的一個(gè)極好例子。起搏器必須在正確的時(shí)間收縮心肌,以挽救你的生命;它不能夠太忙而沒有及時(shí)響應(yīng)。心臟起搏器以及其他的實(shí)時(shí)嵌入式系統(tǒng)都必須精心設(shè)計(jì),以便在任何時(shí)刻都能及時(shí)執(zhí)行它們的任務(wù)。
FreeRTOS是一個(gè)相對(duì)較小的應(yīng)用程序。最小化的FreeRTOS內(nèi)核僅包括3個(gè)(.c
)文件和少數(shù)頭文件,總共不到9000行代碼,還包括了注釋和空行。一個(gè)典型的編譯后(二進(jìn)制)代碼映像小于10KB。
FreeRTOS的代碼可以分解為三個(gè)主要區(qū)塊:任務(wù),通訊,和硬件接口。
●任務(wù):大約有一半的FreeRTOS的核心代碼用來處理多數(shù)操作系統(tǒng)首要關(guān)注的問題:任務(wù)。任務(wù)是給定優(yōu)先級(jí)的用戶定義的C函數(shù)。task.c和task.h完成了所有有關(guān)創(chuàng)建,調(diào)度,和維護(hù)任務(wù)的繁重工作。
●通訊:任務(wù)很重要,不過任務(wù)間可以互相通訊則更為重要!它給我們帶來FreeRTOS的第二項(xiàng)任務(wù):通訊。大約40%的FreeRTOS核心代碼是用來處理通訊的。queue.c
和queue.h
是負(fù)責(zé)處理FreeRTOS的通訊的。任務(wù)和中斷使用隊(duì)列互相發(fā)送數(shù)據(jù),并且使用信號(hào)燈和互斥來發(fā)送臨界資源的使用情況。
●硬件接口:接近9000行的代碼拼湊起基本的FreeRTOS,是硬件無關(guān)的;相同的代碼都能夠運(yùn)行,不論FreeRTOS是運(yùn)行在不起眼的8051,還是最新、最炫的ARM內(nèi)核上。大約有6%的FreeRTOS的核心代碼,在硬件無關(guān)的FreeRTOS內(nèi)核與硬件相關(guān)的代碼間扮演著墊片的角色。我們將在下個(gè)部分討論硬件相關(guān)的代碼。
硬件注意事項(xiàng)
硬件無關(guān)的FreeRTOS層在硬件相關(guān)層之上。硬件相關(guān)層聲明了你選擇什么樣的芯片架構(gòu)。圖3.1顯示了FreeRTOS的各層。
圖3.2:FreeRTOS的就緒列表的基本視圖
現(xiàn)在我們有了大致概述的方式,讓我們?nèi)ド罹克募?xì)節(jié)。我們將著眼于三個(gè)主要FreeRTOS的數(shù)據(jù)結(jié)構(gòu):任務(wù),列表和隊(duì)列。
所有操作系統(tǒng)的主要工作是運(yùn)行和協(xié)調(diào)用戶任務(wù)。像多數(shù)操作系統(tǒng)一樣,F(xiàn)reeRTOS中的基本工作單元是任務(wù)。FreeRTOS的使用任務(wù)控制塊(TCB)來表示每個(gè)任務(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
里存儲(chǔ)堆棧的起始地址,以及在pxTopOfStack
里存儲(chǔ)當(dāng)前堆棧的頂部。如果堆?!跋蛏稀痹鲩L到更高的地址,它還在pxEndOfStack
存儲(chǔ)堆棧的結(jié)束的指針來檢查堆棧溢出。如果堆?!跋蛳隆痹鲩L到更低的地址,那么通過比較當(dāng)前堆棧的頂部與pxStack
中的堆內(nèi)存起始位置來檢查溢出。
TCB在uxPriority
和uxBasePriority
中存儲(chǔ)任務(wù)的初始優(yōu)先級(jí)。一個(gè)任務(wù)在它創(chuàng)建的時(shí)候被賦予優(yōu)先級(jí),同時(shí)任務(wù)的優(yōu)先級(jí)是可以被改變的。如果FreeRTOS實(shí)現(xiàn)了優(yōu)先級(jí)繼承,那么當(dāng)任務(wù)臨時(shí)提升到“繼承的”優(yōu)先級(jí)時(shí),它使用uxBasePriority
去記住原來的優(yōu)先級(jí)。(優(yōu)先級(jí)繼承,請(qǐng)參見下面關(guān)于互斥的討論。)
每個(gè)任務(wù)有兩個(gè)清單項(xiàng)目給FreeRTOS操作系統(tǒng)的各種調(diào)度列表使用。當(dāng)一個(gè)任務(wù)被插入到FreeRTOS的一個(gè)列表中,不會(huì)直接向TCB插入一個(gè)指針。取而代之的是,它向TCB的xGenericListItem
或xEventListItem
插入一個(gè)指針。這些xListItem
變量,比起若是僅僅獲得一個(gè)指向TCB的指針來說,讓FreeRTOS的列表變得更加靈活。
任務(wù)可以在以下四種狀態(tài)之一:運(yùn)行,準(zhǔn)備運(yùn)行,掛起或阻塞。你可能希望每個(gè)任務(wù)都有一個(gè)變量來告訴FreeRTOS它正處于什么狀態(tài),但事實(shí)上并非如此。相反,F(xiàn)reeRTOS通過把任務(wù)放入相應(yīng)的列表:就緒列表,掛起列表等,隱式地跟蹤任務(wù)狀態(tài)。隨著任務(wù)的變化,從一個(gè)狀態(tài)到另一個(gè),F(xiàn)reeRTOS的只是簡單的將它從一個(gè)列表移動(dòng)到另一個(gè)。
任務(wù)設(shè)置
我們已經(jīng)觸及如何利用pxReadyTasksLists
數(shù)組來選擇和調(diào)度一個(gè)任務(wù)的;現(xiàn)在讓我們來看一看一個(gè)任務(wù)最初是如何被創(chuàng)建的。當(dāng)xTaskCreate()
函數(shù)被調(diào)用的時(shí)候,一個(gè)任務(wù)被創(chuàng)建。FreeRTOS為一個(gè)任務(wù)新分配一個(gè)TCB對(duì)象,來記錄它的名稱,優(yōu)先級(jí),和其他細(xì)節(jié),接著分配用戶請(qǐng)求的總的堆棧(假設(shè)有足夠使用的內(nèi)存)和在TCB的pxStack
成員中記錄堆內(nèi)存的開始。
堆棧被初始化看起來就像一個(gè)已經(jīng)在運(yùn)行的新任務(wù)被上下文切換所中斷。這就是任務(wù)調(diào)度處理最新創(chuàng)建的任務(wù)的方法,同樣也是處理運(yùn)行了一段時(shí)間的任務(wù)的方法;任務(wù)調(diào)度在不需要任何特殊(case)代碼的情況下去處理新的任務(wù)。
任務(wù)的堆棧建立看起來像是它通過上下文切換來被中斷,這個(gè)方法是取決于FreeRTOS正在運(yùn)行的架構(gòu),但這個(gè)ARM Cortex-M3處理器的實(shí)現(xiàn)是一個(gè)很好的例子:
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;
}
當(dāng)一個(gè)任務(wù)被中斷的時(shí)候,ARM Cortex-M3處理器就壓寄存器入堆棧。pxPortInitialiseStack()
修改堆棧使之看來像是即便任務(wù)實(shí)際上還未開始運(yùn)行,寄存器就已經(jīng)被壓入了。已知的值被存儲(chǔ)到堆棧,賦給ARM寄存器xPSR
,PC
,LR
,和R0
。剩余的寄存器R1-R12獲得由棧頂指針遞減分配給它們的寄存器空間,但沒有具體的數(shù)據(jù)存儲(chǔ)在這些寄存器堆棧內(nèi)。ARM架構(gòu)告訴我們那些寄存器在復(fù)位的時(shí)候未被定義,所以一個(gè)(非弱智的)程序?qū)⒉灰蕾囉谝阎闹怠?/p>
堆棧準(zhǔn)備好后,任務(wù)幾乎是同時(shí)準(zhǔn)備運(yùn)行。首先,F(xiàn)reeRTOS禁用中斷:我們將開始使用就緒列表和其他任務(wù)調(diào)度的數(shù)據(jù)結(jié)構(gòu),同時(shí)我們不希望它們被其他人背著我們私底下修改。
如果這是被創(chuàng)建的第一個(gè)任務(wù),F(xiàn)reeRTOS將初始化調(diào)度的任務(wù)列表。FreeRTOS操作系統(tǒng)的調(diào)度有一個(gè)就緒列表的數(shù)組,pxReadyTasksLists []
,為每一個(gè)可能的優(yōu)先級(jí)提供一個(gè)就緒列表。FreeRTOS也有一些其他的列表用來跟蹤任務(wù)的掛起,終止和延時(shí)?,F(xiàn)在這些也都是初始化的。
任何第一次初始化完成后,新的任務(wù)以它指定的優(yōu)先級(jí)被加入就緒列表。中斷被重新啟用,同時(shí)新任務(wù)的創(chuàng)建完成。
任務(wù)之后,最常用的FreeRTOS數(shù)據(jù)結(jié)構(gòu)是列表。FreeRTOS使用列表結(jié)構(gòu)來跟蹤調(diào)度任務(wù),并執(zhí)行隊(duì)列。
圖3.4:系統(tǒng)節(jié)拍計(jì)時(shí)器下的FreeRTOS就緒列表全貌
pxReadyTasksLists[]
列出了在vTaskSwitchContext()中已經(jīng)操縱完成的內(nèi)容,是如何使用pxIndex
的一個(gè)很好的例子。讓我們假設(shè)我們僅有一個(gè)優(yōu)先級(jí),優(yōu)先級(jí)0,并且有三個(gè)任務(wù)在此優(yōu)先級(jí)上。這與我們之前看到的基本就緒列表圖相似,但這一次我們將包括所有的數(shù)據(jù)結(jié)構(gòu)和字段。
就如你在圖3.3中所見,pxCurrentTCB
顯示我們當(dāng)前正在運(yùn)行任務(wù)B。下一個(gè)時(shí)刻,vTaskSwitchContext()
運(yùn)行,它調(diào)用listGET_OWNER_OF_NEXT_ENTRY()
載入下一個(gè)任務(wù)來運(yùn)行。如圖3.4所示,這個(gè)函數(shù)使用pxIndex->pxNext
找出下一個(gè)任務(wù)是任務(wù)C,并且pxIndex
指向任務(wù)C的列表元素,同時(shí)pxCurrentTCB
指向任務(wù)C的TCB。
請(qǐng)注意,每個(gè)struct xlistitem
對(duì)象實(shí)際上都是來自相關(guān)TCB的xGenericListItem
對(duì)象。
FreeRTOS允許任務(wù)使用隊(duì)列來互相間通信和同步。中斷服務(wù)程序(ISRs)同樣使用隊(duì)列來通信和同步。
基本隊(duì)列數(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;
這是一個(gè)頗為標(biāo)準(zhǔn)的隊(duì)列,不但包括了頭部和尾部指針,而且指針指向我們剛剛讀過或者寫過的位置。
當(dāng)剛剛創(chuàng)建一個(gè)隊(duì)列,用戶指定了隊(duì)列的長度和需要隊(duì)列跟蹤的項(xiàng)目大小。pcHead
和pcTail
被用來跟蹤隊(duì)列的內(nèi)部存儲(chǔ)器。加入一個(gè)項(xiàng)目到隊(duì)列就對(duì)隊(duì)列內(nèi)部存儲(chǔ)器進(jìn)行一次深拷貝。
FreeRTOS用深拷貝替代在項(xiàng)目中存放一個(gè)指針是因?yàn)橛锌赡茼?xiàng)目插入的生命周期要比隊(duì)列的生命周期短。例如,試想一個(gè)簡單的整數(shù)隊(duì)列使用局部變量,跨幾個(gè)函數(shù)調(diào)用的插入和刪除。如果這個(gè)隊(duì)列在局部變量里存儲(chǔ)這些整數(shù)的指針,當(dāng)整數(shù)的局部變量離開作用域時(shí)指針將會(huì)失效,同時(shí)局部變量的存儲(chǔ)空間將被新的數(shù)值使用。
什么需要用戶選擇使用隊(duì)列。若內(nèi)容很少,用戶可以把復(fù)制的內(nèi)容進(jìn)行排列,就像上圖中簡單整數(shù)的例子,或者,若內(nèi)容很多,用戶可以排列內(nèi)容的指針。請(qǐng)注意,在這兩種情況下FreeRTOS都是在做深拷貝:如果用戶選擇排列復(fù)制的內(nèi)容,那么這個(gè)隊(duì)列存儲(chǔ)了每項(xiàng)內(nèi)容的一份深拷貝;如果用戶選擇了排列指針,隊(duì)列存儲(chǔ)了指針的一份深拷貝。當(dāng)然,用戶在隊(duì)列里存儲(chǔ)了指針,那么用戶有責(zé)任管理與內(nèi)存相關(guān)的指針。隊(duì)列并不關(guān)心你存儲(chǔ)了什么樣的數(shù)據(jù),它只需要知道數(shù)據(jù)的大小。
FreeRTOS支持阻塞和非阻塞隊(duì)列的插入和移除。非阻塞隊(duì)列操作會(huì)立即返回"隊(duì)列的插入是否完成?"或者 "隊(duì)列的移除是否完成?"的狀態(tài)。阻塞操作則根據(jù)特定的超時(shí)。一個(gè)任務(wù)可以無限期地阻塞或者在有限時(shí)間里阻塞。
一個(gè)阻塞任務(wù)——叫它任務(wù)A——將保持阻塞只要它的插入/移除操作沒有完成,并且它的超時(shí)(如果存在)沒有過期。如果一個(gè)中斷或者另一個(gè)任務(wù)編輯了這個(gè)隊(duì)列以便任務(wù)A的操作能夠完成,任務(wù)A將被解除阻塞。如果此時(shí)任務(wù)A的隊(duì)列操作仍然是允許的,那么它實(shí)際上會(huì)執(zhí)行操作,于是任務(wù)A會(huì)完成它的隊(duì)列操作,并且返回“成功”的狀態(tài)。不過,任務(wù)A正在執(zhí)行的那個(gè)時(shí)間,有可能同時(shí)有一個(gè)高優(yōu)先級(jí)任務(wù)或者中斷也在同一個(gè)隊(duì)列上執(zhí)行另一個(gè)操作,這會(huì)阻止任務(wù)A正在執(zhí)行的操作。在這種情況下任務(wù)A將檢查它的超時(shí),同時(shí),如果它未超時(shí)就恢復(fù)阻塞,否則就返回隊(duì)列操作“失敗”的狀態(tài)。
特別需要注意的是,當(dāng)任務(wù)被阻塞在一個(gè)隊(duì)列時(shí),系統(tǒng)保持運(yùn)行所帶來的風(fēng)險(xiǎn);以及當(dāng)任務(wù)被阻塞在一個(gè)隊(duì)列時(shí),有其他任務(wù)或中斷在繼續(xù)運(yùn)行。這種阻塞任務(wù)的方法能不浪費(fèi)CPU周期,使其他任務(wù)和中斷可以有效地使用CPU周期。
FreeRTOS使用xTasksWaitingToSend
列表來保持對(duì)正阻塞在插入隊(duì)列里的任務(wù)的跟蹤。每當(dāng)有一個(gè)元素被移出隊(duì)列,xTasksWaitingToSend
列表就會(huì)被檢查。如果有個(gè)任務(wù)在那個(gè)列表中等待,那個(gè)是未阻塞任務(wù)。同樣的,xTasksWaitingToReceive
保持對(duì)那些正阻塞在移除隊(duì)列里的任務(wù)的跟蹤。每當(dāng)有一個(gè)新元素被插入到隊(duì)列,xTasksWaitingToReceive
列表就會(huì)被檢查。如果有個(gè)任務(wù)在那個(gè)列表中等待,那個(gè)是未阻塞任務(wù)。
信號(hào)燈和互斥
FreeRTOS使用它的隊(duì)列與任務(wù)通信,也在任務(wù)間通信。FreeRTOS也使用它的隊(duì)列來實(shí)現(xiàn)信號(hào)燈與互斥。
有什么區(qū)別?
信號(hào)燈與互斥聽上去像一回事,但它們不是。FreeRTOS同樣地實(shí)現(xiàn)了它們,但本來它們以不同的方式被使用。它們是如何不同地被使用?嵌入式系統(tǒng)宗師Michael Barr說這是在他文章中寫得最好的,“信號(hào)燈與互斥揭秘”:
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.
正確使用的一個(gè)信號(hào)是從一個(gè)任務(wù)向另一個(gè)發(fā)信號(hào)。從每個(gè)使用被保護(hù)共享資源的任務(wù)來看,總是認(rèn)為,一個(gè)互斥意味著獲得和釋放。相比之下,使用信號(hào)燈的任務(wù)不是發(fā)信號(hào)[在FreeRTOS里“發(fā)送”]就是在等信號(hào)[在FreeRTOS里“接收”]——不能同時(shí)。
互斥被用來保護(hù)共享資源。一個(gè)使用共享資源的任務(wù)獲得互斥,接著釋放互斥。當(dāng)有另一個(gè)任務(wù)占據(jù)互斥時(shí),沒有一個(gè)任務(wù)可以獲得這個(gè)互斥。這就是保證,在同一時(shí)刻僅有一個(gè)任務(wù)可以使用共享資源。
一個(gè)任務(wù)向另一個(gè)任務(wù)發(fā)信號(hào)時(shí)使用信號(hào)燈。以下引用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ù)一可能包含當(dāng)“電源”按鈕被按下時(shí),發(fā)布(即,發(fā)信號(hào)或增加信號(hào)量)一個(gè)特定的信號(hào)燈的代碼,并且喚醒顯示屏的任務(wù)二,取決于同一個(gè)信號(hào)燈。在這種情況下,一個(gè)任務(wù)是發(fā)信號(hào)事件的制造者;另一個(gè)是消費(fèi)者。
如果你是在信號(hào)燈和互斥有任何疑問,請(qǐng)查閱Michael的文章。
實(shí)現(xiàn)
FreeRTOS像隊(duì)列一樣來實(shí)現(xiàn)一個(gè)N元素的信號(hào)燈,這樣就可以控制N個(gè)項(xiàng)。它沒有去存儲(chǔ)隊(duì)列每項(xiàng)的任何實(shí)際數(shù)據(jù);信號(hào)燈只關(guān)心有多少在隊(duì)列的uxMessagesWaiting
字段中被跟蹤的隊(duì)列記錄,目前正被占用。當(dāng)FreeRTOS的頭文件semphr.h
調(diào)用它的時(shí)候,它正在做“純同步”。因此這個(gè)隊(duì)列有一個(gè)零字節(jié)的項(xiàng) (uxItemSize == 0
)。每個(gè)信號(hào)燈uxMessagesWaiting
字段遞增或遞減;沒有項(xiàng)或數(shù)據(jù)的復(fù)制是需要的。
同信號(hào)燈一樣,互斥也被實(shí)現(xiàn)為一個(gè)隊(duì)列,不過有幾個(gè)xQUEUE
結(jié)構(gòu)字段被用#defines
重載:
/* Effectively make a union out of the xQUEUE structure. */
#define uxQueueType pcHead
#define pxMutexHolder pcTail
當(dāng)互斥沒有在隊(duì)列中存儲(chǔ)任何數(shù)據(jù)時(shí),它不需要任何內(nèi)部存儲(chǔ)器,同樣pcHead
和pcTail
字段也不需要。FreeRTOS設(shè)置uxQueueType
字段(實(shí)際上的pcHead
字段)為0
來表明,這個(gè)隊(duì)列正在被一個(gè)互斥使用。FreeRTOS使用重載的pcTail
字段來實(shí)現(xiàn)互斥的優(yōu)先級(jí)繼承。
萬一你不熟悉優(yōu)先級(jí)繼承,我將再次引用Michael Barr的話來定義它,這次是來自他的文章,“優(yōu)先級(jí)倒置”:
[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)先級(jí)繼承]要求低優(yōu)先級(jí)任務(wù)繼承任何高優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí),取決于它們共享的資源。這個(gè)優(yōu)先級(jí)的改變應(yīng)當(dāng)在高優(yōu)先級(jí)任務(wù)一開始掛起時(shí)就發(fā)生;資源被釋放時(shí)就結(jié)束。
FreeRTOS使用pxMutexHolder
字段(實(shí)際上是#define
重載的pcTail
字段)來實(shí)現(xiàn)優(yōu)先級(jí)繼承。FreeRTOS記錄在pxMutexHolder
字段里包含一個(gè)互斥的任務(wù)。當(dāng)一個(gè)高優(yōu)先級(jí)任務(wù)正在等待一個(gè)由低優(yōu)先級(jí)任務(wù)取得的互斥,F(xiàn)reeRTOS“更新”低優(yōu)先級(jí)任務(wù)到高優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí),直至這個(gè)互斥再次可用。
我們完成了對(duì)FreeRTOS架構(gòu)的一覽。希望你現(xiàn)在對(duì)于FreeRTOS的任務(wù)是如何執(zhí)行以及通信有一個(gè)好的感覺。如果之前你從未了解過操作系統(tǒng)的內(nèi)在,我希望現(xiàn)在你對(duì)于它們是如何工作的有一個(gè)基本的思路。
顯然,本章沒有覆蓋FreeRTOS架構(gòu)的全部。值得注意的是,我沒有提到的內(nèi)存分配,中斷服務(wù),調(diào)試,或MPU支持。本章也沒有討論如何設(shè)置或使用FreeRTOS。Richard Barry已經(jīng)寫了一本極好的書,使用FreeRTOS實(shí)時(shí)內(nèi)核:實(shí)用指南,這本書就是講這些內(nèi)容的;如果你要使用FreeRTOS的話,我強(qiáng)烈推薦它。
3.8. 致謝
我想感謝Richard Barry創(chuàng)造和維護(hù)了FreeRTOS,并選擇將它開源。在寫這一章的時(shí)候Richard給予了極大的幫助,提供了關(guān)于FreeRTOS的歷史以及非常有價(jià)值的技術(shù)回顧。
也感謝Amy Brown和Greg Wilson共同拉動(dòng)這整個(gè)AOSA的事情。
最后也是最(最多),感謝我的妻子Sarah共同承擔(dān)了我對(duì)本章的研究和寫作。幸運(yùn)的是,她在嫁給我的時(shí)候就知道我是一名極客。
更多建議: