一:架構概述前端
FreeRTOS是一個相對較小的應用程序。最小化的FreeRTOS內核僅包括3個(.c)文件和少數頭文件,總共不到9000行代碼,還包括了註釋和空行。一個典型的編譯後(二進制)代碼映像小於10KB。算法
FreeRTOS的代碼能夠分解爲三個主要區塊:任務,通信,和硬件接口。數組
●任務:大約有一半的FreeRTOS的核心代碼用來處理多數操做系統首要關注的問題:任務。任務是給定優先級的用戶定義的C函數。task.c和task.h完成了全部有關建立,調度,和維護任務的繁重工做。數據結構
●通信:任務很重要,不過任務間能夠互相通信則更爲重要!它給咱們帶來FreeRTOS的第二項任務:通信。大約40%的FreeRTOS核心代碼是用來處理通信的。queue.c和queue.h是負責處理FreeRTOS的通信的。任務和中斷使用隊列互相發送數據,而且使用信號燈和互斥來發送臨界資源的使用狀況。架構
●硬件接口:接近9000行的代碼拼湊起基本的FreeRTOS,是硬件無關的;相同的代碼都可以運行,不論FreeRTOS是運行在不起眼的8051,仍是最新、最炫的ARM內核上。大約有6%的FreeRTOS的核心代碼,在硬件無關的FreeRTOS內核與硬件相關的代碼間扮演着墊片的角色。咱們將在下個部分討論硬件相關的代碼。函數
二:硬件注意事項oop
硬件無關的FreeRTOS層在硬件相關層之上。硬件相關層聲明瞭你選擇什麼樣的芯片架構。圖3.1顯示了FreeRTOS的各層。post
圖3.1:FreeRTOS的軟件層網站
FreeRTOS包含全部你須要用來啓動很運行系統的硬件無關以及硬件相關的代碼。它支持許多編譯器(CodeWarrior,GCC,IAR等)也支持許多處理器架構(ARM7,ARM Cortex-M3,PICs各系列,Silicon Labs 8051, x86等)。請參閱FreeRTOS網站,能夠看處處理器和編譯器支持列表。this
FreeRTOS是高可配置設計。FreeRTOS能夠被編譯成爲適合單CPU,極簡RTOS,只之支持少數任務的操做系統,也能夠被編譯成爲適合多核功能強大的結合了TCP/IP,文件系統,和USB的怪獸。
配置選項能夠經過設置不一樣的#defines,在FreeRTOSConfig.h文件裏選擇。時鐘速度,堆大小,互斥,和API子集,連同其餘許多選項,均可以在這個文件中配置。這裏是幾個例子,設置了任務優先級的最大數量,CPU的頻率,系統節拍器的頻率,最小的堆棧大小和總的堆大小:
#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架構,硬件相關代碼分佈在多個文件中。舉例來講,若是你使用ARM Cortex-M3芯片,IAR編譯器工做,那麼硬件相關的代碼就存在FreeRTOS/Source/portable/IAR/ARM_CM3/目錄下。portmacro.h文件聲明瞭全部硬件特定功能,port.c和portasm.s 包含了全部實際硬件相關的代碼。硬件無關的頭文件portable.h在編譯的時候用#include's引入正確的portmacro.h文件。FreeRTOS使用#define'd調用在portmacro.h中聲明的硬件特定功能。
讓咱們來看一個例子,FreeRTOS是如何調用一個硬件相關功能的。硬件無關的文件tasks.c經常須要插入一個代碼的臨界區,以防止搶佔。在不一樣架構上,插入一個臨界區的表現也不一樣,而且硬件無關的task.c不須要了解硬件相關的細節。因此task.c調用全局宏指令portENTER_CRITICAL(), 樂得忽略它其實是如何作到的。假設咱們使用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文件是一個硬件相關的文件,同時包含了對IAR編譯器和Cortex-M3芯片認識的代碼文件。vPortEnterCritical()函數利用這些硬件特定的知識進入臨界區,又返回到與硬件無關的task.c中。
protmacro.h文件也定義了一個數據類型的基本架構。這個數據類型中包括了基本整型變量,指針,以及系統時鐘節拍器的數據類型,在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
這樣使用數據類型的方法,和函數透太小層的#defines,看上去略微有點複雜,不過它容許了FreeRTOS可以被從新編譯在一個徹底不一樣的系統架構上,僅僅只須要經過修改這些硬件相關的文件。同時,若是你想要讓FreeRTOS運行在一個當前還沒有被支持的架構上,你只僅僅須要去實現硬件相關的功能,這要比在FreeRTOS上去實現硬件無關的部分,要少得多。
就如同咱們已經見到的,FreeRTOS用C的預處理宏#define來實現硬件相關的功能。FreeRTOS也一樣用#define來應對大量的硬件無關的代碼。對於非嵌入式應用程序這樣頻繁使用#define是一個嚴重的錯誤,不過在許多小型嵌入式系統中這點開銷比起「實時」所提供的功能來講就微不足道了。
三:任務調度
任務優先級和就緒列表
全部任務都有一個用戶指定優先級,從0(最低優先級)到 configMAX_PRIORITIES-1的編譯時間值(最高優先級)。例如,若是configMAX_PRIORITIES設置爲5,當FreeRTOS使用5個優先等級時:0(最低優先級),1,2,3,和4(最高優先級)。
FreeRTOS使用一個「就緒列表」來跟蹤全部已經準備好運行的任務。它像一個任務列表數組來實現就緒列表,以下所示:
static xList pxReadyTasksLists[ configMAX_PRIORITIES ]; /* Prioritised ready tasks. */
pxReadyTasksLists[0]是全部準備好的優先級爲0的任務列表,pxReadyTasksLists[1]是全部準備好的優先級爲1的任務列表,以此類推,直到pxReadyTasksLists[configMAX_PRIORITIES-1]。
系統節拍器(時鐘)
FreeRTOS系統的心跳就是被稱爲系統節拍器(時鐘)。FreeRTOS配置這個系統生成一個按期的節拍(時鐘)中斷。用戶能夠配置的節拍中斷頻率,一般是在毫秒範圍。vTaskSwitchContext()函數在每次的節拍中斷釋放的時候被調用。vTaskSwitchContext()選擇優先級最高的就緒任務並將它賦予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 ] ) );
在當型循環(while loop)開始以前,uxTopReadyPriority就被確保大於或等於優先級最高的就緒任務。while()循環從優先級uxTopReadyPriority開始,循環走下去從pxReadyTasksLists[]數組裏找到就緒任務優先級最高的那個。接着listGET_OWNER_OF_NEXT_ENTRY()就搶佔那個就緒列表中優先級最高的下一個就緒任務。
如今pxCurrentTCB指向了優先級最高的任務,而且當vTaskSwitchContext()返回硬件相關代碼時開始運行那個任務。
那九行代碼是FreeRTOS的絕對核心。其他FreeRTOS的8900+行代碼都是用來確保那九行代碼,全都是用來保持優先級最高任務的運行的。
圖3.2是一個大體的就緒列表看起來像什麼的圖。這個例子有三個優先級,有一個優先級爲0的任務,沒有優先級爲1的任務,和三個優先級爲2的任務。這張圖是準確的,但不完整的;它的少掉一些細節,咱們稍後將補充。
圖3.2:FreeRTOS的就緒列表的基本視圖
如今咱們有了大體概述的方式,讓咱們去深究它的細節。咱們將着眼於三個主要FreeRTOS的數據結構:任務,列表和隊列。
3.4. 任務
全部操做系統的主要工做是運行和協調用戶任務。像多數操做系統同樣,FreeRTOS中的基本工做單元是任務。FreeRTOS的使用任務控制塊(TCB)來表示每一個任務。
任務控制塊(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裏存儲當前堆棧的頂部。若是堆棧「向上」增加到更高的地址,它還在pxEndOfStack存儲堆棧的結束的指針來檢查堆棧溢出。若是堆棧「向下」增加到更低的地址,那麼經過比較當前堆棧的頂部與pxStack中的堆內存起始位置來檢查溢出。
TCB在uxPriority和uxBasePriority中存儲任務的初始優先級。一個任務在它建立的時候被賦予優先級,同時任務的優先級是能夠被改變的。若是FreeRTOS實現了優先級繼承,那麼當任務臨時提高到「繼承的」優先級時,它使用uxBasePriority去記住原來的優先級。(優先級繼承,請參見下面關於互斥的討論。)
每一個任務有兩個清單項目給FreeRTOS操做系統的各類調度列表使用。當一個任務被插入到FreeRTOS的一個列表中,不會直接向TCB插入一個指針。取而代之的是,它向TCB的xGenericListItem或xEventListItem插入一個指針。這些xListItem變量,比起如果僅僅得到一個指向TCB的指針來講,讓FreeRTOS的列表變得更加靈活。
任務能夠在如下四種狀態之一:運行,準備運行,掛起或阻塞。你可能但願每一個任務都有一個變量來告訴FreeRTOS它正處於什麼狀態,但事實上並不是如此。相反,FreeRTOS經過把任務放入相應的列表:就緒列表,掛起列表等,隱式地跟蹤任務狀態。隨着任務的變化,從一個狀態到另外一個,FreeRTOS的只是簡單的將它從一個列表移動到另外一個。
任務設置
咱們已經觸及如何利用pxReadyTasksLists數組來選擇和調度一個任務的;如今讓咱們來看一看一個任務最初是如何被建立的。當xTaskCreate()函數被調用的時候,一個任務被建立。FreeRTOS爲一個任務新分配一個TCB對象,來記錄它的名稱,優先級,和其餘細節,接着分配用戶請求的總的堆棧(假設有足夠使用的內存)和在TCB的pxStack成員中記錄堆內存的開始。
堆棧被初始化看起來就像一個已經在運行的新任務被上下文切換所中斷。這就是任務調度處理最新建立的任務的方法,一樣也是處理運行了一段時間的任務的方法;任務調度在不須要任何特殊(case)代碼的狀況下去處理新的任務。
任務的堆棧創建看起來像是它經過上下文切換來被中斷,這個方法是取決於FreeRTOS正在運行的架構,但這個ARM Cortex-M3處理器的實現是一個很好的例子:
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;
}
當一個任務被中斷的時候,ARM Cortex-M3處理器就壓寄存器入堆棧。pxPortInitialiseStack()修改堆棧使之看來像是即使任務實際上還未開始運行,寄存器就已經被壓入了。已知的值被存儲到堆棧,賦給ARM寄存器xPSR,PC,LR,和R0。剩餘的寄存器R1-R12得到由棧頂指針遞減分配給它們的寄存器空間,但沒有具體的數據存儲在這些寄存器堆棧內。ARM架構告訴咱們那些寄存器在復位的時候未被定義,因此一個(非弱智的)程序將不依賴於已知的值。
堆棧準備好後,任務幾乎是同時準備運行。首先,FreeRTOS禁用中斷:咱們將開始使用就緒列表和其餘任務調度的數據結構,同時咱們不但願它們被其餘人揹着咱們私底下修改。
若是這是被建立的第一個任務,FreeRTOS將初始化調度的任務列表。FreeRTOS操做系統的調度有一個就緒列表的數組,pxReadyTasksLists [],爲每個可能的優先級提供一個就緒列表。FreeRTOS也有一些其餘的列表用來跟蹤任務的掛起,終止和延時。如今這些也都是初始化的。
任何第一次初始化完成後,新的任務以它指定的優先級被加入就緒列表。中斷被從新啓用,同時新任務的建立完成。
3.5. 列表
任務以後,最經常使用的FreeRTOS數據結構是列表。FreeRTOS使用列表結構來跟蹤調度任務,並執行隊列。
圖3.3:就緒列表全貌
這個FreeRTOS的列表是一個有着幾個有趣的補充的標準循環雙鏈表。下面就是列表元素:
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). */
};
每一個元素持有一個數字,xItemValue,這一般是一個被跟蹤的任務優先級或者是一個調度事件的計時器值。列表保存從高到低的優先級指令,這意味着最高的優先級xItemValue(最大數)在列表的最前端,而最低的優先級xItemValue(最小數)在列表的末尾。
pxNext和pxPrevious指針是標準鏈表指針。pvOwner 列表元素全部者的指針。這一般是任務的TCB對象的指針。pvOwner被用來在vTaskSwitchContext()中加快任務切換:當最高優先級任務元素在pxReadyTasksLists[]中被發現,這個列表元素的pvOwner指針直接鏈接到須要任務調度的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位數時,它等於xItemValue變量的最大值:0xffff,portTickType是32位數時爲0xffffffff。其餘的列表元素也可使用相同的值;插入算法保證了xListEnd老是列表項中最後一個值。
自列表從高到低排序後,xListEnd被用做列表開始的記號。而且,自循環開始,xListEnd也被用做列表結束的記號。
你也許能夠用一個單獨的for()循環或者是函數調用來訪問大多數「傳統的」列表,去作全部的工做,就像這樣:
for (listPtr = listStart; listPtr != NULL; listPtr = listPtr->next) {
// Do something with listPtr here...
}
FreeRTOS常常須要經過多個for()和while()循環,也包括函數調用來訪問列表,所以它使用操縱pxIndex指針的列表函數來遍歷這個列表。這個列表函數listGET_OWNER_OF_NEXT_ENTRY()執行pxIndex = pxIndex->pxNext;而且返回pxIndex。(固然它也會正確檢測列尾環繞。)這種,當執行遍歷的時候使用pxIndex,由列表本身負責跟蹤「在哪裏」的方法,使FreeRTOS能夠休息而不用關心這方面的事。
圖3.4:系統節拍計時器下的FreeRTOS就緒列表全貌
pxReadyTasksLists[]列出了在vTaskSwitchContext()中已經操縱完成的內容,是如何使用pxIndex的一個很好的例子。讓咱們假設咱們僅有一個優先級,優先級0,而且有三個任務在此優先級上。這與咱們以前看到的基本就緒列表圖類似,但這一次咱們將包括全部的數據結構和字段。
就如你在圖3.3中所見,pxCurrentTCB顯示咱們當前正在運行任務B。下一個時刻,vTaskSwitchContext()運行,它調用listGET_OWNER_OF_NEXT_ENTRY()載入下一個任務來運行。如圖3.4所示,這個函數使用pxIndex->pxNext找出下一個任務是任務C,而且pxIndex指向任務C的列表元素,同時pxCurrentTCB指向任務C的TCB。
請注意,每一個struct xlistitem對象實際上都是來自相關TCB的xGenericListItem對象。
3.6. 隊列
FreeRTOS容許任務使用隊列來互相間通訊和同步。中斷服務程序(ISRs)一樣使用隊列來通訊和同步。
基本隊列數據結構以下:
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;
這是一個頗爲標準的隊列,不但包括了頭部和尾部指針,並且指針指向咱們剛剛讀過或者寫過的位置。
當剛剛建立一個隊列,用戶指定了隊列的長度和須要隊列跟蹤的項目大小。pcHead和pcTail被用來跟蹤隊列的內部存儲器。加入一個項目到隊列就對隊列內部存儲器進行一次深拷貝。
FreeRTOS用深拷貝替代在項目中存放一個指針是由於有可能項目插入的生命週期要比隊列的生命週期短。例如,試想一個簡單的整數隊列使用局部變量,跨幾個函數調用的插入和刪除。若是這個隊列在局部變量裏存儲這些整數的指針,當整數的局部變量離開做用域時指針將會失效,同時局部變量的存儲空間將被新的數值使用。
什麼須要用戶選擇使用隊列。若內容不多,用戶能夠把複製的內容進行排列,就像上圖中簡單整數的例子,或者,若內容不少,用戶能夠排列內容的指針。請注意,在這兩種狀況下FreeRTOS都是在作深拷貝:若是用戶選擇排列複製的內容,那麼這個隊列存儲了每項內容的一份深拷貝;若是用戶選擇了排列指針,隊列存儲了指針的一份深拷貝。固然,用戶在隊列裏存儲了指針,那麼用戶有責任管理與內存相關的指針。隊列並不關心你存儲了什麼樣的數據,它只須要知道數據的大小。
FreeRTOS支持阻塞和非阻塞隊列的插入和移除。非阻塞隊列操做會當即返回"隊列的插入是否完成?"或者 "隊列的移除是否完成?"的狀態。阻塞操做則根據特定的超時。一個任務能夠無限期地阻塞或者在有限時間裏阻塞。
一個阻塞任務——叫它任務A——將保持阻塞只要它的插入/移除操做沒有完成,而且它的超時(若是存在)沒有過時。若是一箇中斷或者另外一個任務編輯了這個隊列以便任務A的操做可以完成,任務A將被解除阻塞。若是此時任務A的隊列操做仍然是容許的,那麼它實際上會執行操做,因而任務A會完成它的隊列操做,而且返回「成功」的狀態。不過,任務A正在執行的那個時間,有可能同時有一個高優先級任務或者中斷也在同一個隊列上執行另外一個操做,這會阻止任務A正在執行的操做。在這種狀況下任務A將檢查它的超時,同時,若是它未超時就恢復阻塞,不然就返回隊列操做「失敗」的狀態。
特別須要注意的是,當任務被阻塞在一個隊列時,系統保持運行所帶來的風險;以及當任務被阻塞在一個隊列時,有其餘任務或中斷在繼續運行。這種阻塞任務的方法能不浪費CPU週期,使其餘任務和中斷能夠有效地使用CPU週期。
FreeRTOS使用xTasksWaitingToSend列表來保持對正阻塞在插入隊列裏的任務的跟蹤。每當有一個元素被移出隊列,xTasksWaitingToSend列表就會被檢查。若是有個任務在那個列表中等待,那個是未阻塞任務。一樣的,xTasksWaitingToReceive保持對那些正阻塞在移除隊列裏的任務的跟蹤。每當有一個新元素被插入到隊列,xTasksWaitingToReceive列表就會被檢查。若是有個任務在那個列表中等待,那個是未阻塞任務。
信號燈和互斥
FreeRTOS使用它的隊列與任務通訊,也在任務間通訊。FreeRTOS也使用它的隊列來實現信號燈與互斥。
有什麼區別?
信號燈與互斥聽上去像一回事,但它們不是。FreeRTOS一樣地實現了它們,但原本它們以不一樣的方式被使用。它們是如何不一樣地被使用?嵌入式系統宗師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.
正確使用的一個信號是從一個任務向另外一個發信號。從每一個使用被保護共享資源的任務來看,老是認爲,一個互斥意味着得到和釋放。相比之下,使用信號燈的任務不是發信號[在FreeRTOS裏「發送」]就是在等信號[在FreeRTOS裏「接收」]——不能同時。
互斥被用來保護共享資源。一個使用共享資源的任務得到互斥,接着釋放互斥。當有另外一個任務佔據互斥時,沒有一個任務能夠得到這個互斥。這就是保證,在同一時刻僅有一個任務可使用共享資源。
一個任務向另外一個任務發信號時使用信號燈。如下引用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.
舉例來講,任務一可能包含當「電源」按鈕被按下時,發佈(即,發信號或增長信號量)一個特定的信號燈的代碼,而且喚醒顯示屏的任務二,取決於同一個信號燈。在這種狀況下,一個任務是發信號事件的製造者;另外一個是消費者。
若是你是在信號燈和互斥有任何疑問,請查閱Michael的文章。
實現
FreeRTOS像隊列同樣來實現一個N元素的信號燈,這樣就能夠控制N個項。它沒有去存儲隊列每項的任何實際數據;信號燈只關心有多少在隊列的uxMessagesWaiting字段中被跟蹤的隊列記錄,目前正被佔用。當FreeRTOS的頭文件semphr.h調用它的時候,它正在作「純同步」。所以這個隊列有一個零字節的項 (uxItemSize == 0)。每一個信號燈uxMessagesWaiting字段遞增或遞減;沒有項或數據的複製是須要的。
同信號燈同樣,互斥也被實現爲一個隊列,不過有幾個xQUEUE結構字段被用#defines重載:
/* Effectively make a union out of the xQUEUE structure. */
#define uxQueueType pcHead
#define pxMutexHolder pcTail
當互斥沒有在隊列中存儲任何數據時,它不須要任何內部存儲器,一樣pcHead和pcTail字段也不須要。FreeRTOS設置uxQueueType字段(實際上的pcHead字段)爲0來代表,這個隊列正在被一個互斥使用。FreeRTOS使用重載的pcTail字段來實現互斥的優先級繼承。
萬一你不熟悉優先級繼承,我將再次引用Michael Barr的話來定義它,此次是來自他的文章,「優先級倒置」:
[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.
[優先級繼承]要求低優先級任務繼承任何高優先級任務的優先級,取決於它們共享的資源。這個優先級的改變應當在高優先級任務一開始掛起時就發生;資源被釋放時就結束。
FreeRTOS使用pxMutexHolder字段(其實是#define重載的pcTail字段)來實現優先級繼承。FreeRTOS記錄在pxMutexHolder字段裏包含一個互斥的任務。當一個高優先級任務正在等待一個由低優先級任務取得的互斥,FreeRTOS「更新」低優先級任務到高優先級任務的優先級,直至這個互斥再次可用。