基礎
教材
-
Girogio C. Buttazzo, Hard Real-Time Computing Systems, Third
edition, Springer, 2011.
-
盧有亮, 嵌入式實時作業系統-μC/OS, 第二版, 電子工業出版社,
2014
讨論内容
中文教材:
- 基本概念:硬體(不考),任務和限制,定義排程的問題,排程的異常
- 實時任務排程和排程分析:非周期任務排程,周期任務排程
- μC/OS:任務排程,中斷和時間管理,事件管理,消息管理,記憶體管理
英文教材:
- 資源控制協定:優先級反轉,非搶占協定,優先級繼承協定,優先級封頂協定,對協定進行分析
- 過載處理:處理非周期過載,處理溢出,處理恒定過載
概念
軟實時,硬實時,準實時
嵌入式應用
實時:定量(quantitative),定性(qualitative)
實時系統:給定的時間限制下面完成任務的計算機系統
實時作業系統:對外界輸入有限時響應,根據實時進行設計,幫助任務滿足時間要求
安全與可用(safety & reliability)
失敗安全狀态(fail safe state)
安全關鍵系統,任何失敗都會對系統造成嚴重損壞,沒有失敗安全狀态,隻能通過可靠性提高來保證安全
設計高可靠系統:
- 錯誤避免
- 錯誤檢測和移除
- 容錯
- 硬體
- built in self test 自測試電路
- 三模備援:投票
- 軟體
- N版本程式:三模備援的思想
- 恢複子產品
課程目标
- 研究學習時間關鍵計算系統用得到的算法
- 設計軟體使得控制應用可預測
任務模型
任務Task:到達時間ai,開始時間si,計算時間ci,結束時間fi,響應時間fi - si
就緒隊列ready queue
搶占preemption
排程schedule
任務狀态task states
實時任務real-time task
- 截止時間deadline:絕對時間di
- 可行feasible:fi < di
- 裕量slack:di - fi or 延遲lateness:fi - di
作業job:任務的執行個體
啟動模式Activation Mode :
- 時間驅動time driven:周期任務periodic tasks
- 事件驅動event driven:非周期任務aperiodic tasks,通常是通過系統調用
周期任務:
- 使用率因子Ui :
- 啟動時間:
- 絕對截止時間:,often Di = Ti
非周期任務:
零星任務Sporadic:
重要程度:硬實時HARD task,準實時FIRMtask,軟實時SOFT task
抖動Jitter:衡量周期事件上時間的變化
- 絕對absolute:
- 相對relative:
- 類型:結束時間抖動finishing-time jitter,啟動時間抖動start-time jitter, 完成時間抖動completion-time jitter(I/O jitter)
參數分類parameters:
- 指定參數known offline:計算時間Ci,周期Ti,相對截止時間Di
- 運作時參數known at run time:到達時間ai, 開始時間si,結束時間fi,響應時間Ri,裕量或延遲,抖動
空閑IDLE
任務限制
類型
- 時間限制Timing constraints
- 次序限制Precedence constraints
- 資源限制Resource constraints
并發Concurrency
并發與I/O
周期任務:
- FIFO
- 單調速率RM。周期越短,頻率越高,優先級越高。
沖突:互斥信号量,信号量
時間異常
先後次序
處理器速度快!=實時性能好 fast !=realtime
延遲delay
Semaphores Semaphores
實時作業系統基礎
- 實時作業系統首先是多任務作業系統
- 多級中斷
- 優先級排程機制
任務
多任務
任務狀态

可重入與不可重入
函數可重入是指一個函數可以被多個任務調用, 而不需要擔心在
任務切換的過程中, 代碼的執行會産生錯誤的結果
核心
核心是作業系統最核心的部分, 其主要功能就是進行任務排程
基于優先級的排程算法
在μC/OS-II中, 可以同時有64個就緒任務, 每個任務都有各自的優先級。 優先級用無符号整數來表示, 從0到63,數字越大則優先級越低。
μC/OS總是排程就緒了的、 優先級最高的任務獲得CPU的控制權, 不管這個任務是什麼, 執行什麼樣的功能, 也不管該任務是否已經等了很久。
不可剝奪和可剝奪核心
其實就是任務是否可搶占
同步和通信
同步
有時候一個任務完成的前提是需要另一個任務給出一個結果, 任務之間的這種制約性的合作運作機制叫做任務間的同步。
互斥
共享資源稱為臨界資源(Critical Resource) 。這種通路共享資源的排他性就是互斥。
臨界區
每個任務中通路共享資源的那段程式稱為臨界區(Critical Section) , 因為共享資源的通路是要互斥的。 在臨界區不允許任務切換, 這是最根本的原則。
任務事件
信号量:建立create,請求pend,釋放post
互斥信号量
事件标志組
事件标志組管理的條件組合可以是多個事件都發生,也可以是多個事件中有任何一個事件發生。 尤其特别的是, 還可以是多個事件都沒有發生或多個事件中有任何一個事件沒有發生。
消息郵箱和消息隊列
消息隊列實質上是消息郵箱的隊列
中斷和時鐘
記憶體管理
μC/OS-II中, 采用分區的方式管理記憶體, 即将連續的大塊記憶體按分區來管理, 每個系統中有數個這樣的分區,每個分區又包含數個記憶體塊, 每個記憶體塊大小相同。
1.什麼是作業系統, 什麼是實時作業系統, 實時作業系統
應該具有哪些特性?
§ 2.什麼是任務, 任務和程式有什麼差別? 任務都有哪些狀
态?
§ 3.編寫一個可重入函數, 實作将整數轉換字元串。 說明為
什麼該函數是可重入的。
§ 4.什麼是不可剝奪核心和可剝奪核心, μC/OS為什麼采用
可剝奪核心?
§ 5.作業系統中的事件管理都包括哪些, 并一一加以論述。
任務管理
任務管理資料結構
任務管理的資料結構包括任務控制塊, 任務空閑連結清單和任務就緒連結清單, 任務優先級指針表, 任務堆棧等, 是μC/OS-II核心的核心部分之一。
任務控制塊
任務控制塊是任務管理的核心資料結構, 作業系統在啟動的時候, 首先要在記憶體中建立一定是數量的任務控制塊。 任務控制塊的最大數量等于作業系統能同時管理的最多任務數 。
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
#if OS_TASK_CREATE_EXT_EN > 0u
void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
INT16U OSTCBId; /* Task ID (0..65535) */
#endif
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
#if (OS_EVENT_EN)
OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
#endif
#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks */
#endif
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
#endif
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
#endif
INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
INT8U OSTCBStatPend; /* Task PEND status */
INT8U OSTCBPrio; /* Task priority (0 == highest) */
INT8U OSTCBX; /* 任務優先級低3位 */
INT8U OSTCBY; /* 任務優先級右移3位,相當于優先級除以8 */
OS_PRIO OSTCBBitX; /* 任務優先級在對應的任務就緒表中的位置 */
OS_PRIO OSTCBBitY; /* 任務在優先級組表中的位置 */
#if OS_TASK_DEL_EN > 0u
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
#endif
#if OS_TASK_PROFILE_EN > 0u
INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_EN > 0u
INT8U *OSTCBTaskName;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
INT32U deadline;
struct os_tcb *rdyList;
} OS_TCB;
任務控制塊實體的聲明如下:
OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]
OS_MAX_TASKS為最多的使用者任務數,OS_N_SYS_TASKS為系統任務數, 一般情況下為2。ucos_ii最多可以有64個任務
空閑連結清單和就緒連結清單
μC/OS将任務控制塊劃分為兩個連結清單, 就緒連結清單和空閑連結清單
系統有一個OSTCBFreeList指針指向第一個空閑連結清單,空閑連結清單隻用到next,是單向連結清單。每當取走一個空閑連結清單項,指針後移。空閑表項加入OSTCBList且prev指向之前OSTCBList,OSTCBList指向的是最後一個表項。
任務優先級指針表
任務優先級指針表也就是任務優先級指針數組, 在μC/OS-II任務管理中頻繁使用,代碼中随處可見。 它是用來擷取某優先級的任務的任務控制塊位址。 它的定義為:
OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1]
OS_LOWEST_PRIO為最低優先級的任務的優先級, 因為低優先級的任務數值最大, 而任務優先級是從0開始的, 是以其實OS_LOWEST_PRIO + 1就是任務的數量。
任務堆棧
如果堆棧是向下增長, 也就是從高位址向低位址增長, 那麼在任務剛開始建立後, 堆棧是空的。 相反, 如果堆棧是向上增長的, 棧頂在為TaskStk[0]。
任務就續表和就緒組
簡單的對應關系,空閑任務63,統計任務62。
設定任務就緒:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];
INT8U const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
擷取最高優先級
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)(y<<3) + OSUnMapTbl[OSRdyTbl[y]]);
INT8U const OSUnMapTbl[] = {...};
任務控制塊初始化
INT8U OS_TCBInit (INT8U prio, //被建立任務的優先級
OS_STK *ptos, //任務堆棧棧頂的位址
OS_STK *pbos, //任務堆棧棧底的位址,如果不使用擴充,即不進行堆棧檢查,就傳NULL
INT16U id, //任務的ID,16位
INT32U stk_size, //堆棧的大小
void *pext, //任務控制塊的擴充塊的位址
INT16U opt); //其他選項
INT8U OS_TCBInit (INT8U prio,
OS_STK *ptos,
OS_STK *pbos,
INT16U id,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT8U i;
#endif
OS_ENTER_CRITICAL(); //進入臨界區,關中斷
ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list */
if (ptcb != (OS_TCB *)0) {
OSTCBFreeList = ptcb->OSTCBNext; /* Update pointer to free TCB list */
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr = ptos; /* Load Stack pointer in TCB */
ptcb->OSTCBPrio = prio; /* Load task priority into TCB */
ptcb->OSTCBStat = OS_STAT_RDY; /* Task is ready to run */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
ptcb->OSTCBDly = 0u; /* Task is not delayed */
#if OS_TASK_CREATE_EXT_EN > 0u
ptcb->OSTCBExtPtr = pext; /* Store pointer to TCB extension */
ptcb->OSTCBStkSize = stk_size; /* Store stack size */
ptcb->OSTCBStkBottom = pbos; /* Store pointer to bottom of stack */
ptcb->OSTCBOpt = opt; /* Store task options */
ptcb->OSTCBId = id; /* Store task ID */
#else
pext = pext; /* Prevent compiler warning if not used */
stk_size = stk_size;
pbos = pbos;
opt = opt;
id = id;
#endif
#if OS_TASK_DEL_EN > 0u
ptcb->OSTCBDelReq = OS_ERR_NONE;
#endif
#if OS_LOWEST_PRIO <= 63u /* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
#else /* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 4u) & 0xFFu);
ptcb->OSTCBX = (INT8U) (prio & 0x0Fu);
#endif
/* Pre-compute BitX and BitY */
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
#if (OS_EVENT_EN)
ptcb->OSTCBEventPtr = (OS_EVENT *)0; /* Task is not pending on an event */
#if (OS_EVENT_MULTI_EN > 0u)
ptcb->OSTCBEventMultiPtr = (OS_EVENT **)0; /* Task is not pending on any events */
#endif
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u) && (OS_TASK_DEL_EN > 0u)
ptcb->OSTCBFlagNode = (OS_FLAG_NODE *)0; /* Task is not pending on an event flag */
#endif
#if (OS_MBOX_EN > 0u) || ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u))
ptcb->OSTCBMsg = (void *)0; /* No message received */
#endif
#if OS_TASK_PROFILE_EN > 0u
ptcb->OSTCBCtxSwCtr = 0uL; /* Initialize profiling variables */
ptcb->OSTCBCyclesStart = 0uL;
ptcb->OSTCBCyclesTot = 0uL;
ptcb->OSTCBStkBase = (OS_STK *)0;
ptcb->OSTCBStkUsed = 0uL;
#endif
#if OS_TASK_NAME_EN > 0u
ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
#if OS_TASK_REG_TBL_SIZE > 0u /* Initialize the task variables */
for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) {
ptcb->OSTCBRegTbl[i] = 0u;
}
#endif
OSTCBInitHook(ptcb);
OSTaskCreateHook(ptcb); /* Call user defined hook */
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB *)0) {
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NO_MORE_TCB);
}
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB )0) {
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; / Make task ready to run /
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; / Increment the #tasks counter */
OS_EXIT_CRITICAL();
作業系統初始化
OSInit()
作業系統初始化函數OS_INIT是作業系統在開始運作的最初, 對全局變量、 任務控制塊、 就緒表、 事件及消息隊列等重要資料結構進行的初始化操作,并建立空閑任務、 統計任務等系統任務。 該函數必須在建立使用者對象及調用OSStart()啟動實時任務排程之前運作
void OSInit (void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
/*...*/
}
OS_InitMisc()
實作對作業系統一些混雜的全局變量的初始化
static void OS_InitMisc (void)
{
#if OS_TIME_GET_SET_EN > 0u
OSTime = 0uL; /* Clear the 32-bit system clock */
#endif
OSIntNesting = 0u; /* Clear the interrupt nesting counter */
OSLockNesting = 0u; /* Clear the scheduling lock counter */
OSTaskCtr = 0u; /* Clear the number of tasks */
OSRunning = OS_FALSE; /* Indicate that multitasking not started */
OSCtxSwCtr = 0u; /* Clear the context switch counter */
OSIdleCtr = 0uL; /* Clear the 32-bit idle counter */
#if OS_TASK_STAT_EN > 0u
OSIdleCtrRun = 0uL;
OSIdleCtrMax = 0uL;
OSStatRdy = OS_FALSE; /* Statistic task is not ready */
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
OSSafetyCriticalStartFlag = OS_FALSE; /* Still allow creation of objects */
#endif
}
OS_InitRdyList()
對就緒表進行初始化的工作
static void OS_InitRdyList (void)
{
INT8U i;
OSRdyGrp = 0u; /* Clear the ready list */
for (i = 0u; i < OS_RDY_TBL_SIZE; i++) {
OSRdyTbl[i] = 0u;
}
OSPrioCur = 0u;
OSPrioHighRdy = 0u;
OSTCBHighRdy = (OS_TCB *)0;
OSTCBCur = (OS_TCB *)0;
}
OS_InitTCBList()
控制塊連結清單初始化
static void OS_InitTCBList (void)
{
INT8U ix;
INT8U ix_next;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */
for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++) { /* Init. list of free TCBs */
ix_next = ix + 1u;
ptcb1 = &OSTCBTbl[ix];
ptcb2 = &OSTCBTbl[ix_next];
ptcb1->OSTCBNext = ptcb2;
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
ptcb1 = &OSTCBTbl[ix];
ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB */
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSTCBList = (OS_TCB *)0; /* TCB lists initializations */
OSTCBFreeList = &OSTCBTbl[0];
}
OS_InitTaskIdle()
建立作業系統空閑任務
static void OS_InitTaskIdle (void)
{
#if OS_TASK_NAME_EN > 0u
INT8U err;
#endif
#if OS_TASK_CREATE_EXT_EN > 0u
#if OS_STK_GROWTH == 1u
(void)OSTaskCreateExt(OS_TaskIdle,
(void *)0, /* No arguments passed to OS_TaskIdle() */
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Top-Of-Stack */
OS_TASK_IDLE_PRIO, /* Lowest priority level */
OS_TASK_IDLE_ID,
&OSTaskIdleStk[0], /* Set Bottom-Of-Stack */
OS_TASK_IDLE_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */
#else
(void)OSTaskCreateExt(OS_TaskIdle,
(void *)0, /* No arguments passed to OS_TaskIdle() */
&OSTaskIdleStk[0], /* Set Top-Of-Stack */
OS_TASK_IDLE_PRIO, /* Lowest priority level */
OS_TASK_IDLE_ID,
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Bottom-Of-Stack */
OS_TASK_IDLE_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */
#endif
#else
#if OS_STK_GROWTH == 1u
(void)OSTaskCreate(OS_TaskIdle,
(void *)0,
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
OS_TASK_IDLE_PRIO);
#else
(void)OSTaskCreate(OS_TaskIdle,
(void *)0,
&OSTaskIdleStk[0],
OS_TASK_IDLE_PRIO);
#endif
#endif
#if OS_TASK_NAME_EN > 0u
OSTaskNameSet(OS_TASK_IDLE_PRIO, (INT8U *)(void *)"uC/OS-II Idle", &err);
#endif
}
任務建立
任務建立函數分兩種,一種是基本的建立函數OSTaskCreate,另一種是擴充的任務建立函數OSTaskCreateExt。
任務建立代碼解析
建立任務OS_TaskCreat
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
堆棧初始化函數OSTaskStkInit的一個版本
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
INT32U *stk; //console �¼Ĵ���Ϊ32λ��
opt = opt; /* 'opt' is not used, prevent warning */
stk = (INT32U *)ptos; /* Load stack pointer */
*--stk = (INT32U)pdata; /* Simulate call to function with argument */
*--stk = (INT32U)0X00000000;
*--stk = (INT32U)task; /* Put pointer to task on top of stack */
*--stk = (INT32U)0x00000202; /* EFL = 0X00000202 */
*--stk = (INT32U)0xAAAAAAAA; /* EAX = 0xAAAAAAAA */
*--stk = (INT32U)0xCCCCCCCC; /* ECX = 0xCCCCCCCC */
*--stk = (INT32U)0xDDDDDDDD; /* EDX = 0xDDDDDDDD */
*--stk = (INT32U)0xBBBBBBBB; /* EBX = 0xBBBBBBBB */
*--stk = (INT32U)0x00000000; /* ESP = 0x00000000 esp�������⣬��Ϊ */
*--stk = (INT32U)0x11111111; /* EBP = 0x11111111 */
*--stk = (INT32U)0x22222222; /* ESI = 0x22222222 */
*--stk = (INT32U)0x33333333; /* EDI = 0x33333333 */
return ((OS_STK *)stk);
}
OS_TaskCreat的流程 :在編譯環境的初始化 (虛拟機初始化),OS初始化,之後進行,建立完任務後,OSStart()
建立任務OS_TaskCreatExt
與OS_TaskCreat主要的差別就是堆棧的清理
INT8U OSTaskCreateExt (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
#if (OS_TASK_STAT_STK_CHK_EN > 0u)
OS_TaskStkClr(pbos, stk_size, opt); /* Clear the task stack (if needed) */
#endif
psp = OSTaskStkInit(task, p_arg, ptos, opt); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find HPT if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* Make this priority avail. to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
OS_TaskCreatExt的流程
任務删除
删除任務是建立任務的逆過程,
任務建立設定就緒表, 就緒組, 任務删除則取消設定;
任務建立将任務控制塊從空閑連結清單移到就緒連結清單; 删除操作則相反。
INT8U OSTaskDel (INT8U prio)
{
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FLAG_NODE *pnode;
#endif
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSIntNesting > 0u) { /* See if trying to delete from ISR */
return (OS_ERR_TASK_DEL_ISR);
}
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to delete idle task */
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
/*$PAGE*/
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if requesting to delete self */
prio = OSTCBCur->OSTCBPrio; /* Set priority to delete to current */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to delete must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if (ptcb == OS_TCB_RESERVED) { /* Must not be assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
OSRdyTbl[ptcb->OSTCBY] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (OSRdyTbl[ptcb->OSTCBY] == 0u) { /* Make task not ready */
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
#if (OS_EVENT_EN)
if (ptcb->OSTCBEventPtr != (OS_EVENT *)0) {
OS_EventTaskRemove(ptcb, ptcb->OSTCBEventPtr); /* Remove this task from any event wait list */
}
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) { /* Remove this task from any events' wait lists*/
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
}
#endif
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
pnode = ptcb->OSTCBFlagNode;
if (pnode != (OS_FLAG_NODE *)0) { /* If task is waiting on event flag */
OS_FlagUnlink(pnode); /* Remove from wait list */
}
#endif
ptcb->OSTCBDly = 0u; /* Prevent OSTimeTick() from updating */
ptcb->OSTCBStat = OS_STAT_RDY; /* Prevent task from being resumed */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
if (OSLockNesting < 255u) { /* Make sure we don't context switch */
OSLockNesting++;
}
OS_EXIT_CRITICAL(); /* Enabling INT. ignores next instruc. */
OS_Dummy(); /* ... Dummy ensures that INTs will be */
OS_ENTER_CRITICAL(); /* ... disabled HERE! */
if (OSLockNesting > 0u) { /* Remove context switch lock */
OSLockNesting--;
}
OSTaskDelHook(ptcb); /* Call user defined hook */
OSTaskCtr--; /* One less task being managed */
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* Clear old priority entry */
if (ptcb->OSTCBPrev == (OS_TCB *)0) { /* Remove from TCB chain */
ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0;
OSTCBList = ptcb->OSTCBNext;
} else {
ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext;
ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev;
}
ptcb->OSTCBNext = OSTCBFreeList; /* Return TCB to free TCB list */
OSTCBFreeList = ptcb;
#if OS_TASK_NAME_EN > 0u
ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
#endif
OS_Dummy();
中斷一定的響應時間,用于把任務控制塊還回空閑連結清單裡,把相應的就緒組和就續表去掉就緒狀态。
**請求删除
通知對方任務, 告訴它要删除你了, 請任務自己删除自己是一種更好的做法。 因為這麼做, 任務可以在删除自己之前先放棄自己使用的資源, 如緩沖區、 信号量、 郵箱、 隊列等。
OsTaskDelReq名稱雖然是請求, 卻是集請求和響應于一段代碼的。 該代碼的功能是:
- 請求删除某任務
- 檢視是否有任務要删除自己
INT8U OSTaskDelReq (INT8U prio)
{
INT8U stat;
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to delete idle task */
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
if (prio == OS_PRIO_SELF) { /* See if a task is requesting to ... */
OS_ENTER_CRITICAL(); /* ... this task to delete itself */
stat = OSTCBCur->OSTCBDelReq; /* Return request status to caller */
OS_EXIT_CRITICAL();
return (stat);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to delete must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* Task must already be deleted */
}
if (ptcb == OS_TCB_RESERVED) { /* Must NOT be assigned to a Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ; /* Set flag indicating task to be DEL. */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
#endif
任務挂起和恢複
OSTaskSuspend将任務阻塞, 也就是被剝奪CPU的使用權而暫時終止運作, 轉到阻塞狀态。 通過OSTaskSuspend将任務轉到阻塞态被稱為挂起任務。被挂起的任務不能運作, 直到其他任務以該任務的優先級作為參數調用OSTaskResume來恢複它, 才能将該任務的狀态重新設定為就緒狀态。
suspend為阻塞态,挂起态通過中斷到達
任務挂起
#if OS_TASK_SUSPEND_EN > 0u
INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if suspend SELF */
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */
self = OS_TRUE;
} else {
self = OS_FALSE; /* No suspending another task */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */
OS_EXIT_CRITICAL();
if (self == OS_TRUE) { /* Context switch only if SELF */
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
#endif
有一個檢查是否·正在·挂起自己這一步
然後清除就續表相關位,注意如果一個就緒組對應的所有就續表都清零的話,就緒組相關位也要清除,有一個判斷的過程。
任務恢複
INT8U OSTaskResume (INT8U prio)
{
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Make sure task priority is valid */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_RESUME_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) != OS_STAT_RDY) { /* Task must be suspended */
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_SUSPEND; /* Remove suspension */
if (ptcb->OSTCBStat == OS_STAT_RDY) { /* See if task is now ready */
if (ptcb->OSTCBDly == 0u) {
OSRdyGrp |= ptcb->OSTCBBitY; /* Yes, Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); /* Find new highest priority task */
}
} else {
OS_EXIT_CRITICAL();
}
} else { /* Must be pending on event */
OS_EXIT_CRITICAL();
}
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_SUSPENDED);
}
取消挂起标志,記得如果任務正常,而且沒有延時,就要進入就緒了。
任務排程和多任務的啟動
任務級排程執行的功能是如果正在運作的任務不是最高優先級任務或者即将被阻塞, 需要選擇一個優先級最高的就緒任務運作
OS_Sched
OS_Sched中首先判斷是不是具有任務排程的條件即:
- 無中斷
- 判斷排程器沒有上鎖,滿足條件後, 調用OS_SchedNew() 給全局變量OSPrioHighRdy指派, 即給最高優先級任務指派
- 如果優先級最高的就緒任務不是目前在運作的任務, 調用OS_Task_SW() 進行任務切換
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0u) { /* ... scheduler is not locked */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
OS_Schednew
static void OS_SchedNew (void)
{
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}
***OS_TASK_SW
在OS_Task_SW中, 首先将CPU寄存器中的内容壓入被換出的任務堆棧中, 然後将被換入的任務的堆棧内容放入到CPU寄存器, 在排程結束恢複時, 從控制器中取出上次儲存的任務的位址, 完成了一次任務級排程。
#define OS_TASK_SW() OSCtxSw()
void OSCtxSw(void)
{
_asm{
lea eax, nextstart ;任務切換回來後從nextstart開始
push eax
pushfd ;标志寄存器的值
pushad ;儲存EAX -- EDI
mov ebx, [OSTCBCur]
mov [ebx], esp ;把堆棧入口的位址儲存到目前TCB結構中
}
OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;
_asm{
mov ebx, [OSTCBCur]
mov esp, [ebx] ;得到OSTCBHighRdy的esp
popad ;恢複所有通用寄存器,共8個
popfd ;恢複标志寄存器
ret ;跳轉到指定任務運作
}
nextstart: //任務切換回來的運作位址
return;
}
外部中斷
如果正在運作的任務沒有關閉中斷,在中斷到來的時候, 作業系統響應中斷, 進入中斷服務程式。這時候任務的運作環境還沒有儲存, 是以需要将任務的運作環境儲存。這時候任務由于中斷的到來而進入挂起态。
中斷進入
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++; /* Increment ISR nesting level */
}
}
}
中斷退出
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0u) { /* ... and not locked. */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
時鐘中斷OSTimeTick()
通過OSTimeTick()調用中斷
void OSTimeTick (void)
{
OS_TCB *ptcb;
BOOLEAN step;
OSTimeTickHook(); /*調用使用者鈎子函數,預設是空函數 */
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
#if OS_TICK_STEP_EN > 0u
switch (OSTickStepState) { /* Determine whether we need to process a tick */
case OS_TICK_STEP_DIS: /* Yes, stepping is disabled */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */
step = OS_FALSE; /* .. OSTickStepState to OS_TICK_STEP_ONCE */
break;
case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */
step = OS_TRUE; /* ... step command from uC/OS-View */
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* Invalid case, correct situation */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* Return if waiting for step command */
return;
}
#endif
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */
ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
}
}
在OSTimeTick中, 周遊每一個任務的ptcb->OSTCBDly是否設定了時間延時或者時間等待延時,如果延時時間不為0, 則将延時時間片-1, 表明經過了一個時間片,如果時間片為0, 檢查任務是在等待某一事件還是單純地延時,如果任務不是被挂起, 則延時時間到, 更新就緒表, 将任務狀态設定為就緒狀态, 循環進行
多任務啟動
多任務啟動的代碼是核心中的OSStart()函數, 在OSStart之前, 必須已經執行了系統初始化函數OSInit, 并且至少建立了1個以上的任務
- 确認作業系統多任務沒有啟動
- 确定優先級最高的就緒任務的優先級
- 執行高優優先級任務的代碼OSStartHighRdy()
其實就是把最高優先級任務複制到目前任務,然後啟動目前任務
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* Find highest priority's task priority number */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
void OSStartHighRdy(void)
{
OSTaskSwHook();
OSRunning = TRUE;
_asm{
mov ebx, [OSTCBCur] ;OSTCBCur結構的第一個參數就是esp
mov esp, [ebx] ;恢複堆棧
popad ;恢複所有通用寄存器,共8個
popfd ;恢複标志寄存器
ret ;ret 指令相當于pop eip 但保護模式下不容許使用eip
;永遠都不傳回
}
}
特殊任務
空閑任務
空閑任務是μC/OS-II 的系統任務, 因為它占據了最低優先級63, 是以隻有在其他的任務都因為等待事件的發生而被阻塞的時候才能得到運作
-
從空閑任務的代碼可見, 空閑任務除了不停地将空閑計數器OSIdleCtr的值加1
之外, 幾乎什麼都沒有做。
- 當沒有任何其他任務能夠運作的時候, 作業系統就會執行這段代碼
- 而OSTaskIdleHook預設的情況下也隻是一個空函數, 沒有特殊需要我們也不需要去填寫它, 該函數的另一作用就是占據一點時間, 給系統足夠的時間響應中斷。
void OS_TaskIdle (void *p_arg)
{
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook(); /* Call user definable HOOK */
}
}
統計任務
統計任務OS_TaskStat是μC/OS-II的另一個重要的系統任務, 我們可以通過宏設定取消統計任務, 但一般情況下我們不會這麼做, 因為統計任務執行的統計工作是比較重要的。 統計任務的主要功能就是計算CPU的使用率。 如果沒有統計任務, 我們就不可知道多任務環境下系統的運作情況是否良好
涉及到的全局變量
#if OS_TASK_STAT_EN > 0u
OS_EXT INT8U OSCPUUsage; /* Percentage of CPU used */
OS_EXT INT32U OSIdleCtrMax; /* Max. value that idle ctr can take in 1 sec. */
OS_EXT INT32U OSIdleCtrRun; /* Val. reached by idle ctr at run time in 1 sec. */
OS_EXT BOOLEAN OSStatRdy; /* Flag indicating that the statistic task is rdy */
OS_EXT OS_STK OSTaskStatStk[OS_TASK_STAT_STK_SIZE]; /* Statistics task stack */
#endif
初始化函數OSStatInit
void OSStatInit (void)
{
OSTimeDly(2u); /* Synchronize with clock tick */
OS_ENTER_CRITICAL();
OSIdleCtr = 0uL; /* Clear idle counter */
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Determine MAX. idle counter value for 1/10 second */
OS_ENTER_CRITICAL();
OSIdleCtrMax = OSIdleCtr; /* Store maximum idle counter count in 1/10 second */
//printf("空閑計數最大=%d",OSIdleCtrMax);
OSStatRdy = OS_TRUE;
OS_EXIT_CRITICAL();
}
OS_TaskStat
void OS_TaskStat (void *p_arg)
{
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
while (OSStatRdy == OS_FALSE) {
OSTimeDly(2u * OS_TICKS_PER_SEC / 10u); /* Wait until statistic task is ready */
}
OSIdleCtrMax /= 100uL;
if (OSIdleCtrMax == 0uL) {
OSCPUUsage = 0u;
#if OS_TASK_SUSPEND_EN > 0u
(void)OSTaskSuspend(OS_PRIO_SELF);
#else
for (;;) {
OSTimeDly(OS_TICKS_PER_SEC);
}
#endif
}
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /* Obtain the of the idle counter for the past second */
OSIdleCtr = 0uL; /* Reset the idle counter for the next second */
OS_EXIT_CRITICAL();
OSCPUUsage = (INT8U)(100uL - OSIdleCtrRun / OSIdleCtrMax);
OSTaskStatHook(); /* Invoke user definable hook */
#if (OS_TASK_STAT_STK_CHK_EN > 0u) && (OS_TASK_CREATE_EXT_EN > 0u)
OS_TaskStatStkChk(); /* Check the stacks for each task */
#endif
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Accumulate OSIdleCtr for the next 1/10 second */
}
}
中斷和時間管理
中斷管理
同上
核心思路
如果正在運作的任務沒有關閉中斷,在中斷到來的時候, 作業系統響應中斷,進入中斷服務程式。這時候任務的運作環境還沒有儲存, 是以需要将任務的運作環境儲存。這時候任務由于中斷的到來而進入挂起态。
處理流程
時鐘中斷服務
時鐘中斷服務是要依賴于中斷的, 如果是單片機系統,那麼就該設定為定時器中斷。 用定時器中斷的服務程式來完成該功能是恰當的。 對于Windows平台下的虛拟系統, 可以采用定時器觸發來虛拟中斷。
時間管理
時間管理的内容在代碼os_time.c中, 包含了作業系統時間的設定及擷取,對任務的延時, 任務按分秒延時, 取消任務的延時共5個系統調用。
主要資料結構
volatile INT32U OSTime;
任務控制塊 TCB中的OSTCBDly
時間擷取和設定
注意: 因為OSTime 是被保護的全局變量, 在通路的時候必須使用OS_ENTER_CRITICAL()進入臨界區, 保證獨占通路!
OSTimeGet
INT32U OSTimeGet (void)
{
INT32U ticks;
OS_ENTER_CRITICAL();
ticks = OSTime;
OS_EXIT_CRITICAL();
return (ticks);
}
#endif
OSTimeSet
void OSTimeSet (INT32U ticks)
{
OS_ENTER_CRITICAL();
OSTime = ticks;
OS_EXIT_CRITICAL();
}
任務延時函數OSTimeDly
任務延時函數OSTimeDly用于阻塞任務一定時間, 這個時間以參數的形式給出。 如果這個參數的值是N, 那麼在N個時間片(時鐘嘀嗒) 之後, 任務才能回到就緒狀态獲得繼續運作的機會。 如果參數的值是0, 不會阻塞任務。
void OSTimeDly (INT32U ticks)
{
INT8U y;
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return;
}
if (ticks > 0u) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
}
調用OSTimeDly(1) 會阻塞 <=1個時間片
任務按分秒延遲函數OSTimeDlyHMSM
OSTimeDlyHMSM從功能上來說和OSTimeDly并沒有多大的差别,隻是将時間機關進行了轉換, 也就是說轉換了以小時、 分、 秒、 毫秒為機關的時間和以時間片為機關的時間。 OSTimeDlyHMSM的參數分别是延時的小時數hours, 分鐘數minutes, 秒數seconds, 和毫秒數ms
INT8U OSTimeDlyHMSM (INT8U hours,
INT8U minutes,
INT8U seconds,
INT16U ms)
{
INT32U ticks;
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
return (OS_ERR_TIME_DLY_ISR);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return (OS_ERR_SCHED_LOCKED);
}
#if OS_ARG_CHK_EN > 0u
if (hours == 0u) {
if (minutes == 0u) {
if (seconds == 0u) {
if (ms == 0u) {
return (OS_ERR_TIME_ZERO_DLY);
}
}
}
}
if (minutes > 59u) {
return (OS_ERR_TIME_INVALID_MINUTES); /* Validate arguments to be within range */
}
if (seconds > 59u) {
return (OS_ERR_TIME_INVALID_SECONDS);
}
if (ms > 999u) {
return (OS_ERR_TIME_INVALID_MS);
}
#endif
/* Compute the total number of clock ticks required.. */
/* .. (rounded to the nearest tick) */
ticks = ((INT32U)hours * 3600uL + (INT32U)minutes * 60uL + (INT32U)seconds) * OS_TICKS_PER_SEC
+ OS_TICKS_PER_SEC * ((INT32U)ms + 500uL / OS_TICKS_PER_SEC) / 1000uL;
OSTimeDly(ticks);
return (OS_ERR_NONE);
}
延時恢複函數
阻塞狀态的任務即便任務的延時時間沒到, 還是可以通過OSTimeDlyResume恢複該任務到就緒态。 對于因等待事件發生而阻塞的, 且設定了逾時timeout時間的任務, 也可以時候OSTimeDlyResume來恢複。 對這些任務使用了OSTimeDlyResume, 就好像已經等待逾時了一樣! 但是, 采用OSTaskSuspend挂起的任務, 是不允許采用OSTimeDlyResume來恢複。
INT8U OSTimeDlyResume (INT8U prio)
{
OS_TCB *ptcb;
if (prio >= OS_LOWEST_PRIO) {
return (OS_ERR_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio]; /* Make sure that task exist */
if (ptcb == (OS_TCB *)0) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb->OSTCBDly == 0u) { /* See if task is delayed */
OS_EXIT_CRITICAL();
return (OS_ERR_TIME_NOT_DLY); /* Indicate that task was not delayed */
}
ptcb->OSTCBDly = 0u; /* Clear the time delay */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
OS_Sched(); /* See if this is new highest priority */
} else {
OS_EXIT_CRITICAL(); /* Task may be suspended */
}
return (OS_ERR_NONE);
}
事件管理
μC/OS-II作為實時多任務作業系統, 是事件驅動的, 支援信号量, 消息等機制。
事件主要包括信号量和互斥信号量, 而事件的組合可以用事件标志組來管理 。
資料結構
事件控制塊ECB
事件控制塊ECB在事件管理中占據着舉足輕重的作用。 雖然事件控制塊ECB并沒有任務控制塊TCB的内容豐富, 但是在事件進行中仍然是核心的資料結構, 頻繁被通路。
typedef struct os_event {
INT8U OSEventType; /* Type of event control block (see OS_EVENT_TYPE_xxxx) */
void *OSEventPtr; /* Pointer to message or queue structure */
INT16U OSEventCnt; /* Semaphore Count (not used if other EVENT type) */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
} OS_EVENT;
OS_EVENT_TYPE
#define OS_EVENT_TYPE_UNUSED 0u
#define OS_EVENT_TYPE_MBOX 1u
#define OS_EVENT_TYPE_Q 2u
#define OS_EVENT_TYPE_SEM 3u
#define OS_EVENT_TYPE_MUTEX 4u
#define OS_EVENT_TYPE_FLAG 5u
事件等待組和事件等待表
當事件發生的時候, 作業系統會找到優先級最高的等待事件發生的任務,并将該任務就緒, 然後在事件等待組和事件等待表中取消該任務的标記。
事件控制塊空閑連結清單
事件管理中, 将空閑的事件塊連結為一個單向的連結清單, 事件控制塊空閑連結清單。這個連結清單的形式和任務塊TCB的空閑連結清單的形式是完全相同的。
當建立一個事件的時候, 要在事件控制塊ECB空閑連結清單查找是否有空閑的ECB可用。 如果有, 就從連結清單中取出配置設定給事件
事件空閑連結清單指針OSEventFreeList的定義為:
OS_EVENT *OSEventFreeList;
管理程式
事件控制塊ECB初始化
- ECB初始化函數OS_InitEventList首先清空了所有的ECB塊, 也就是清空了事件表。
- 然後從0到OS_MAX_EVENTS - 1u)循環對除最後一個ECB塊之外的所有ECB塊進行初始化, 并順便建構了單向的連結清單。
-
循環結束後最後一個ECB 塊OSEventTbl[OS_MAX_EVENTS - 1]進行初始化。 最後一個事件控制塊OSEventTbl[OS_MAX_EVENTS - 1]的OSEventPtr域指向空位址0, 構造完成了如圖4-3所示的空閑事件控制塊
連結清單。
- 然後将ECB空閑連結清單的表頭位址給OSEventFreeList, 初始化完成。
static void OS_InitEventList (void)
{
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u)
#if (OS_MAX_EVENTS > 1u)
INT16U ix;
INT16U ix_next;
OS_EVENT *pevent1;
OS_EVENT *pevent2;
OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl)); /* Clear the event table */
for (ix = 0u; ix < (OS_MAX_EVENTS - 1u); ix++) { /* Init. list of free EVENT control blocks */
ix_next = ix + 1u;
pevent1 = &OSEventTbl[ix];
pevent2 = &OSEventTbl[ix_next];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = pevent2;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
pevent1 = &OSEventTbl[ix];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSEventFreeList = &OSEventTbl[0];
#else
OSEventFreeList = &OSEventTbl[0]; /* Only have ONE event control block */
OSEventFreeList->OSEventType = OS_EVENT_TYPE_UNUSED;
OSEventFreeList->OSEventPtr = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
OSEventFreeList->OSEventName = (INT8U *)"?"; /* Unknown name */
#endif
#endif
#endif
}
事件等待表初始化
當建立一個事件或消息, 如信号量、 郵箱、 消息隊列時, 如信号量的建立函數
OSSemCreate等, 需要對事件等待表進行初始化。
OS_EventWaitListInit
pevent->OSEventGrp = 0u; /* 清空任務等待組*/
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
pevent->OSEventTbl[i] = 0u; /* 采用循環模式清空任務等待表*/
}
設定事件等待
當任務等待事件發生, 并獲得事件控制塊ECB後, 需要在ECB中标記任務在等待事件的發生, 才可以在事件發生時取消任務的阻塞
- 标記。 在ECB中登記本任務, 即在ECB的事件等待表中對應優先級處标記為1, 事件等待組中對應位标記為1。
- 取消标記。 在就緒表和就緒組中取消對該事件就緒的标記, 将就緒表中對應優先級處标記為0, 如果就緒表該任務所在的一組沒有任務就緒, 将就緒組中的對應位标記為0。
void OS_EventTaskWait (OS_EVENT *pevent)
{
INT8U y;
OSTCBCur->OSTCBEventPtr = pevent; /* Store ptr to ECB in TCB */
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; /* Put task in waiting list */
pevent->OSEventGrp |= OSTCBCur->OSTCBBitY;
y = OSTCBCur->OSTCBY; /* Task no longer ready */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) { /* Clear event grp bit if this was only task pending */
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
}
取消事件等待
OS_EventTaskRemove 是與OS_EventTaskWait相反的操作, 當一個任務由于某種原因不再需要等待事件需運作OS_EventTaskRemove 。
void OS_EventTaskRemove (OS_TCB *ptcb,
OS_EVENT *pevent)
{
INT8U y;
y = ptcb->OSTCBY;
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* 在事件等待表中删除事件等待标志 */
if (pevent->OSEventTbl[y] == 0u) { /*若該行已沒有任務等待*/
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY; /*删除事件等待組的事件等待标志*/
}
}
将等待事件的任務就緒
任務因為等待事件而在ECB中登記自己的等待, 當事件發生的時候, 如果該任務是事件等待表中優先級最高的任務, 任務被取消等待而回到就緒狀态。
- 在事件等待表和事件等待組中找到最高優先級的等待任務的優先級。
- 根據優先級查優先級指針表, 找到該任務的任務控制塊TCB指針。
- 對任務控制塊的相關參數進行指派。
- 判斷任務是否被挂起, 如果未被挂起就将任務就緒。 完成從阻塞态到就緒态的轉移。
- 調用OS_EventTaskRemove在ECB的事件等待表中删除該任務。
- 傳回任務的優先級
OS_EventTaskRdy
INT8U OS_EventTaskRdy (OS_EVENT *pevent,
void *pmsg,
INT8U msk,
INT8U pend_stat)
{
OS_TCB *ptcb;
INT8U y;
INT8U x;
INT8U prio;
#if OS_LOWEST_PRIO > 63u
OS_PRIO *ptbl;
#endif
#if OS_LOWEST_PRIO <= 63u
y = OSUnMapTbl[pevent->OSEventGrp]; /* Find HPT waiting for message */
x = OSUnMapTbl[pevent->OSEventTbl[y]];
prio = (INT8U)((y << 3u) + x); /* Find priority of task getting the msg */
#else
if ((pevent->OSEventGrp & 0xFFu) != 0u) { /* Find HPT waiting for message */
y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &pevent->OSEventTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
x = OSUnMapTbl[*ptbl & 0xFFu];
} else {
x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
}
prio = (INT8U)((y << 4u) + x); /* Find priority of task getting the msg */
#endif
ptcb = OSTCBPrioTbl[prio]; /* Point to this task's OS_TCB */
ptcb->OSTCBDly = 0u; /* Prevent OSTimeTick() from readying task */
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
ptcb->OSTCBMsg = pmsg; /* Send message directly to waiting task */
#else
pmsg = pmsg; /* Prevent compiler warning if not used */
#endif
ptcb->OSTCBStat &= (INT8U)~msk; /* Clear bit associated with event type */
ptcb->OSTCBStatPend = pend_stat; /* Set pend status of post or abort */
/* See if task is ready (could be susp'd) */
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY; /* Put task in the ready to run list */
OSRdyTbl[y] |= ptcb->OSTCBBitX;
}
OS_EventTaskRemove(ptcb, pevent); /* Remove this task from event wait list */
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) { /* Remove this task from events' wait lists */
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
ptcb->OSTCBEventPtr = (OS_EVENT *)pevent;/* Return event as first multi-pend event ready*/
}
#endif
return (prio);
}
信号量管理
信号量的建立OSSemCreate
信号量在作業系統初始化的時候并不存在。 這時作業系統中的事件管理資料結構事件控制塊ECB為全空, 所有的事件控制塊都在ECB空閑連結清單中排隊。 信号量的建立函數OSSemCreate将使用一個并配置一個ECB, 使其具備信号量的屬性。
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* Get next free event control block */
if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* Get an event control block */
pevent->OSEventType = OS_EVENT_TYPE_SEM;
pevent->OSEventCnt = cnt; /* Set semaphore value */
pevent->OSEventPtr = (void *)0; /* Unlink from ECB free list */
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); /* Initialize to 'nobody waiting' on sem. */
}
return (pevent);
}
信号量的建立OSSemCreate
假設信号量值為5, 則指派後的ECB應該如圖4-4所示。
信号量的删除OSSemDel
信号量如果不再使用了就應該盡快删除, 否則很快系統就沒有可用的事件塊可用。信号量的删除函數是OSSemDel。 删除信号量比建立一個信号量更複雜 。
OS_EVENT *OSSemDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on semaphore */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete semaphore only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the semaphore */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
請求信号量OSSemPend
請求信号量也稱為等待信号量。 等待信号量的參數為3個, 分别是ECB的指針pevent, 32位無符号整數逾時時間timeout, 和用來傳回結果的指向整型的指針perr
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0u) { /* If sem. is positive, resource available ... */
pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return;
}
/* Otherwise, must wait until event occurs */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* Resource not available, pend on semaphore */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Store pend timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
OS_EXIT_CRITICAL();
}
OS_EventTaskWait(pevent);
作用就是标記與取消标記,在事件等待表中進行标記,在就續表、組進行取消标記。
送出信号量
當任務A獲得信号量之後将信号量數字減1, 然後就可以通路資源R。 這時, 如果信号量的值為0, 任務B如果也要通路資源R, 必須等待信号量, 是以将任務B阻塞。 任務A在對資源的通路完成之後, 應将信号量喚醒。 因為資源已經可以被其他的任務通路了, 是以應該将任務B喚醒, 使任務B就緒。
當通路資源的任務有2個以上, 資源R可同時被N個任務通路, 是以信号量的值在最開始建立的時候應該等于N。 當任務A通路信号量, 信号量值變為N-1,任務B又通路, 信号量等于N-2, 當第M個任務通路, 信号量等于N-M。 當N-M=0的時候,也就是當N=M的時候, 當第N+1也要通路該資源R,第N+1個任務必須等待。
當任何一個任務(例如第2個)通路資源完成, 應該喚醒第N+1個任務讓其通路資源。 當第N+1個任務通路完成之後, 因為沒有其他的任務等待信号量, 隻需簡單地将信号量值加 1
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting for semaphore */
/* Ready HPT waiting on event */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */
pevent->OSEventCnt++; /* Increment semaphore count to register event */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_ERR_SEM_OVF);
}
無等待請求信号量
在中斷服務程式和有些使用者任務中, 需要無等待的請求信号量。 也就是說, 使用信号量請求資源, 當沒有可用的資源, 信号量為0的時候, 并不阻塞自己, 而是繼續執行其他代碼。 OSSemAccept就是無等待的請求信号量函數 。
- 進行參數檢查
- 将信号量的值指派給局部變量cnt, 如果cnt > 0說明資源有效或信号量有效, 是以将信号量的值減1然後傳回cnt, 可以執行通路資源的代碼了。 如果函數傳回值為0,不能執行通路資源的代碼。
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
return (0u);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0u) { /* See if resource is available */
pevent->OSEventCnt--; /* Yes, decrement semaphore and notify caller */
}
OS_EXIT_CRITICAL();
return (cnt); /* Return semaphore count */
}
放棄等待信号量
放棄等待信号量并非放棄本任務對信号量的等待。如果是放棄本任務對信号量的等待, 那麼本任務在等待信号量, 那麼本任務應該處于阻塞狀态, 一個處于阻塞狀态的任務得不到運作, 怎麼能執行放棄等待信号量的代碼呢? 是以, 一定是放棄其他任務對一個信号量的等待。
- 參數檢查, 如果ECB指針無效或ECB的類型不是信号量類型, 傳回參數檢查錯誤資訊。
- 如果pevent->OSEventGrp為0說明沒有任務等待信号量, 傳回0。
- 否則根據參數opt(選項)進行分支轉移, 如果為OS_PEND_OPT_BROADCAST,使用while語句循環地将等待該信号量的每個任務用OS_EventTaskRdy來取消等待并使其就緒( 除非任務還被挂起) ; 如果為其他值則隻将最高優先級的任務取消等待并就緒之。 兩種情況下都傳回取消等待信号量的任務數
INT8U OSSemPendAbort (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
INT8U nbr_tasks;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return (0u);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting on semaphore? */
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: /* Do we need to abort ALL waiting tasks? */
while (pevent->OSEventGrp != 0u) { /* Yes, ready ALL tasks waiting on semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE:
default: /* No, ready HPT waiting on semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (0u); /* No tasks waiting on semaphore */
}
信号量的設定
作業系統提供了直接設定信号量值的函數OSSemSet。 一般情況下無需使用該函數設定信号量的值, 應該在信号量建立的時候初始化信号量的值。當一個信号量的值在建立之後為N, 每次有任務請求信号量就将該值減1, 反之将該值加1, 一般情況下是不允許随便修改的。
但是, 在極其特殊的情況下, 因為某種特殊的需要, 例如突然增加了其他的資源,需要修改資源數N, 可采用OSSemSet直接對信号量指派, 但條件是這時沒有任務在等待該信号量。
void OSSemSet (OS_EVENT *pevent,
INT16U cnt,
INT8U *perr)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
OS_ENTER_CRITICAL();
*perr = OS_ERR_NONE;
if (pevent->OSEventCnt > 0u) { /* See if semaphore already has a count */
pevent->OSEventCnt = cnt; /* Yes, set it to the new value specified. */
} else { /* No */
if (pevent->OSEventGrp == 0u) { /* See if task(s) waiting? */
pevent->OSEventCnt = cnt; /* No, OK to set the value */
} else {
*perr = OS_ERR_TASK_WAITING;
}
}
OS_EXIT_CRITICAL();
}
查詢信号量狀态
信号量狀态查詢将ECB中關于信号量的資訊拷貝到另一個資料結構信号量資料OS_SEM_DATA
OS_SEM_DATA
typedef struct os_sem_data {
INT16U OSCnt; /* Semaphore count */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
} OS_SEM_DATA;
OSSemQuery
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指針是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (p_sem_data == (OS_SEM_DATA *)0) { /* Validate 'p_sem_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制塊類型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_sem_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
p_sem_data->OSCnt = pevent->OSEventCnt; /* Get semaphore count */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
互斥信号量管理
事件标志組管理
排程算法
概念
定義
排程
任務集
算法A
可行性feasible
可排程性shedulable
限制constraints
複雜性
NP hard
分類
可搶占/不可搶占
靜态/動态
線上/離線
最佳的/直覺的
Graham's Notation
處理器數 number of processors
任務限制 constraints on tasks
遵循優化準則optimality criterion
經典排程政策
FCFS(First Come First Served)
響應時間取決到達時間
SJF(Shortest Job First)
靜态
不适合實時
Priority Scheduling
不适合實時
會産生饑餓,可以通過aging解決
Round Robin
時間片輪轉
不适合實時
Multi-Level Scheduling
不适合實時
實時排程算法
靜态:相對截止時間Di
動态:絕對截止時間di
Li = fi - di 延遲時間 = 截止時間 - 結束時間
Earliest Due Date
1 | sync | Lmax
同時到達,非搶占,靜态
Earliest Deadline First
1 | preem. | Lmax
搶占式,非同時到達,動态
如果一個任務集不能被一個最優算法排程,那麼這個任務集不能被其他算法排程
非搶占的EDF在不能空閑的算法裡是最佳的
Bratley‘s Algorithm
1 | no-preem | Lmax
對排程樹進行修剪,找到Lmax最小化
Heuristic Search
改為找直覺函數H最小化
H = ri => FCFS
H = Ci => SJF
H = Di => DM
H = di => EDF
Composite heuristic functions:
H =w1ri + w2Di
H = w1Ci+ w2di
H = w1ri + w2di
通過直覺函數有可能找不到最優,但并不代表不存在最優,有可能是是被前序直覺計算時忽略了
Latest Deadline First
1 | prec, sync | Lmax
先計算截止時間最晚的,放在最後執行
EDF*
1 | prec, preem | Lmax
将先後次序改為時間限制,然後用EDF排程
先将後續任務的開始時間延後,再将前序任務的截止時間提前
周期任務排程
按比例分享算法Proportional Share Algorithm
概念
Ui = required feeding fraction
= GCD(T1, T2)
可行性測試feasibility test: i.e.
問題
有可能GCD公約數取得太小,導緻分割的塊太多,會有很多轉換的開銷,增加運作的時間
循環排程算法
大周期裡有小周期
不需要實時系統核心,開銷比較小
過載後不是很魯棒,不容易擴充
最佳優先級排程
優先級排程
- 配置設定優先級
- 驗證可行性
- 執行任務
Rate Monotonic
Pi 正比 1/Ti (static)
在所有固定優先級算法中T=D(周期=相對截止時間)時是最佳的
Deadline Monotonic
Pi 正比 1/Di (static)
在所有固定優先級算法中D<=T時是最佳的
Earliest Deadline First
Pi 正比 1/dik (dynamic)
di,k = ri,k + Di
在所有算法中是最佳的
關鍵時刻
高優先級任務到來時刻
可行性計算
RM
Up <= 1 是可行性排程的必要不充分條件
最低上界Least Upper Bound
Up <= Ulub 是RM排程的充分條件
并非必要條件,因為在Ulub到1之間,可能存在一部分可行,比如Ulub < A <1 在Ulub到A之間可行,A到1之間不可行。
RM的上界Ulub :
雙曲線上界 Hyperbolic Bound
響應時間分析
響應時間Ri = Ci + Ii
驗證Ri <= Di
幹擾時間分析
EDF
因為EDF跟截止時間相關,是動态的,是以不能用響應時間分析。
處理器需求準則
processor demand criterion
含義:gi(o,L) = 計算次數(時長/周期) * 計算時間,g(o,L) =
-Di+Ti為了保證整數運算,效果就是産生溢出,再向下取整
邊界測試點
bounding test points
- 由于g函數隻會在截止時間到來産生跳變,是以隻要測試邊界點就可以了
- 如果任務同時到達,而且Up < 1,經過超周期後,任務都是重複出現的,最小的超周期為
H = lcm(T1,...,Tn)
從圖像上可以看出來,我們隻需要計算到L*
綜合以上,得到處理器需求測試(Processor Demand Test)
消息管理
本章的消息管理中包括消息郵箱和消息隊列兩方面的内容, 适用于任務之間的資訊交流和同步。從原理上講, 消息管理也應該屬于事件管理的範疇
消息郵箱
本章的消息管理中包括消息郵箱和消息隊列兩方面的内容, 适用于任務之間的資訊交流和同步。從原理上講, 消息管理也應該屬于事件管理的範疇
建立
在系統初始化之後, 并不存在一個消息郵箱。 這時作業系統中的事件管理資料結構事件控制塊ECB為全空, 所有的事件控制塊都在ECB空閑連結清單中排隊。 消息郵箱的建立函數OSMboxCreate将使用一個并配置一個ECB,使其具備消息郵箱的屬性。
OS_EVENT *OSMboxCreate (void *pmsg)
{
OS_EVENT *pevent;
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數*/
return ((OS_EVENT *)0); /* 不允許在中斷服務程式ISR中調用本函數*/
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 取得空閑的事件控制塊ECB*/
if (OSEventFreeList != (OS_EVENT *)0) { /* 是否沒有可用的事件控制塊ECB? */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) {
pevent->OSEventType = OS_EVENT_TYPE_MBOX;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pmsg; /* 在ECB中存儲消息位址*/
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent);
}
return (pevent); /* 傳回事件控制塊位址*/
}
-
檢查是否這中斷服務程式中建立消息郵箱。
同不允許在中斷服務程式中建立信号量一樣, 作業系統μC/OS-II同樣不允許在中斷服務程式中建立消息郵箱。
-
檢查是否有空閑的事件控制塊。
将OSEventFreeList指派給pevent, 如果pevent為空指針, 表示沒有空閑的事件控制塊, 函數傳回。
-
在事件控制塊空閑連結清單中取下表頭。
因為pevent現在已經是用于郵箱的事件控制塊, 讀者可以直接把他了解為一個郵箱。 那麼, 需要執行的操作顯然就是在事件控制塊空閑連結清單中将他删除,這時候OSEventFreeList應該指向第二個ECB。
- 對事件控制塊指派, 指派後的ECB應該如圖5-1所示。
- 傳回ECB位址。
等待
等消息也稱為請求消息。 含義是當消息存在的時候擷取消息, 當消息不存在的時候就放棄對CPU的占有, 直到有消息的時候才被喚醒。 當任務後續的操作離不開消息, 這時任務就不該死死占着CPU不讓其他的任務運作, 就應該去休息,而當消息到來的時候系統會将消息喚醒回就緒态, 任務獲得消息後繼續運作。
void *OSMboxPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
void *pmsg;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *)0);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pmsg = pevent->OSEventPtr;
if (pmsg != (void *)0) { /* See if there is already a message */
pevent->OSEventPtr = (void *)0; /* 清郵箱 */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); /* 傳回獲得的消息(或空指針) */
}
OSTCBCur->OSTCBStat |= OS_STAT_MBOX; /* Message not available, task will pend */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready to run */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
pmsg = OSTCBCur->OSTCBMsg;
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* 清除獲得的消息 */
OS_EXIT_CRITICAL();
return (pmsg); /* 傳回獲得的消息 */
}
發送
當一個任務因為等待消息而被阻塞的時候, 隻有當其他任務發出了消息, 被阻塞的任務才能被恢複到就緒态, 進而獲得消息後繼續運作。 阻塞的函數在前一節分析過了, 發消息的函數為OSMboxPost, 參數是消息類型的ECB的指針,以及消息的位址
INT8U OSMboxPost (OS_EVENT *pevent,
void *pmsg)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (pmsg == (void *)0) { /* Make sure we are not posting a NULL pointer */
return (OS_ERR_POST_NULL_PTR);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task pending on mailbox */
/* Ready HPT waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_MBOX, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventPtr != (void *)0) { /* Make sure mailbox doesn't already have a msg */
OS_EXIT_CRITICAL();
return (OS_ERR_MBOX_FULL);
}
pevent->OSEventPtr = pmsg; /* Place message in mailbox */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
删除
當消息郵箱不再使用了, 就應該盡快歸還給系統, 即将消息占用的ECB歸還給ECB空閑連結清單以備它用。 消息郵箱的删除函數是OSMboxDel。 删除一個消息也要涉及方方面面, 因為可能有任務正在等待這個郵箱中的消息。
#if OS_MBOX_DEL_EN > 0u
OS_EVENT *OSMboxDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效*/
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on mailbox */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete mailbox only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得空閑的事件控制塊ECB */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Mailbox has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the mailbox */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得空閑的事件控制塊ECB */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Mailbox has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
放棄
同放棄對信号量的等待類似, 放棄等待郵箱也絕對不會是放棄本任務對郵箱的等待。放棄等待郵箱函數将放棄的是所有等待某郵箱的任務對該郵箱的等待或等待某郵箱的優先級最高的任務對郵箱的等待
INT8U OSMboxPendAbort (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
INT8U nbr_tasks;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效*/
*perr = OS_ERR_EVENT_TYPE;
return (0u);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting on mailbox? */
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: /* Do we need to abort ALL waiting tasks? */
while (pevent->OSEventGrp != 0u) { /* Yes, ready ALL tasks waiting on mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE:
default: /* No, ready HPT waiting on mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_ABORT);
nbr_tasks++;
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (0u); /* No tasks waiting on mailbox */
}
- 檢查事件控制塊指針是否有效及事件控制塊類型是否有效。
- 如果pevent->OSEventGrp為0說明沒有任務等待消息郵箱, 取消等待的任務數是0, 傳回0。
- 否則根據參數opt(選項)進行分支轉移,如為OS_PEND_OPT_BROADCAST, 使用while語句循環地将等待該郵箱的每個任務用OS_EventTaskRdy來取消等待并使其就緒( 除非任務還被挂起) ;如果為其他值則隻将最高優先級的任務取消等待并就緒之。
- 傳回取消等待信号量的任務數。
無等待請求
在中斷服務程式和有些使用者任務中, 需要無等待的請求消息郵箱。 也就是說, 到郵箱中取郵件, 如果有郵件就獲得郵件, 如果沒有并不阻塞自己, 而是繼續執行其他代碼。
void *OSMboxAccept (OS_EVENT *pevent)
{
void *pmsg;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效 */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pmsg = pevent->OSEventPtr;
pevent->OSEventPtr = (void *)0; /* 清郵箱 */
OS_EXIT_CRITICAL();
return (pmsg); /* 傳回獲得的消息(或空指針) */
}
首先參數檢查ECB是否有效, 如果有效, 将消息郵箱中郵件的位址OSEventPtr指派給pmsg, 然後清郵箱内容, 傳回獲得的郵件的位址pmsg。這樣, 如果郵箱中有郵件, 那麼傳回郵件的位址, 如果沒有, 傳回值就是空位址。
查詢
INT8U OSMboxQuery (OS_EVENT *pevent,
OS_MBOX_DATA *p_mbox_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制塊指針是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (p_mbox_data == (OS_MBOX_DATA *)0) { /* Validate 'p_mbox_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制塊類型是否有效*/
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_mbox_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_mbox_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
p_mbox_data->OSMsg = pevent->OSEventPtr; /* Get message from mailbox */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
消息隊列
使用消息隊列管理, 就允許使用可以容納多條資訊的大郵箱, 按照先進先出( FIFO) 的原則, 發送和接收郵件。 需要注意的是, 這樣的郵箱不是作業系統提供的, 而是要由使用者任務來提供。 作業系統提供的是對其進行管理的程式。 另外, 郵箱中的内容仍然是郵件的位址。
資料結構
- 消息隊列及其控制塊
void *MessageStorage[size];
typedef struct os_q { /* 隊列控制塊QCB*/
struct os_q *OSQPtr; /*在空閑QCB連結清單中, 訓示下一個QCB*/
void **OSQStart; /*隊列資料的首位址*/
void **OSQEnd; /*隊列資料的末位址+1*/
void **OSQIn; /*訓示下次插入消息的位置 */
void **OSQOut; /* 訓示下次提取消息的位置*/
INT16U OSQSize; /*隊列的最大容量*/
INT16U OSQEntries; /*隊列中目前的消息量*/
} OS_Q;
- 消息控制塊實體
OS_Q OSQTbl[OS_MAX_QS];
-
空閑消息控制塊連結清單
消息控制塊QCB中OSQPtr用來訓示空閑消息控制塊QCB連結清單中的下一個消息控制塊QCB。 OSQFreeList指向該表表頭
初始化
消息隊列初始化函數在作業系統初始化時被調用, 主要用于初始化消息隊列使用的資料結構。 消息隊列初始化函數的名稱為OS_QInit
void OS_QInit (void)
{
#if OS_MAX_QS == 1u
OSQFreeList = &OSQTbl[0]; /* Only ONE queue! */
OSQFreeList->OSQPtr = (OS_Q *)0;
#endif
#if OS_MAX_QS >= 2u
INT16U ix;
INT16U ix_next;
OS_Q *pq1;
OS_Q *pq2;
OS_MemClr((INT8U *)&OSQTbl[0], sizeof(OSQTbl)); /* Clear the queue table */
for (ix = 0u; ix < (OS_MAX_QS - 1u); ix++) { /* Init. list of free QUEUE control blocks */
ix_next = ix + 1u;
pq1 = &OSQTbl[ix];
pq2 = &OSQTbl[ix_next];
pq1->OSQPtr = pq2;
}
pq1 = &OSQTbl[ix];
pq1->OSQPtr = (OS_Q *)0;
OSQFreeList = &OSQTbl[0];
#endif
}
- 将所有QCB全部清為全0。
-
使用for循環将除最後一個消息控制塊
OSQTbl[OS_MAX_QS - 1]之外的所有消息控制塊初始化, 建構了單向的消息隊列空閑連結清單。
-
初始化最後一個QCB, 将消息隊列空閑連結清單完善。
OSQFreeList指向連結清單的表頭
建立
建立消息隊列就是将從ECB空閑連結清單中取下一個事件控制塊ECB來, 将其用于消息隊列管理。 并從QCB空閑連結清單的表頭取下一個消息控制塊QCB, 将其各種屬性進行設定, 用于訓示消息的位置以及提取和插入消息的位置。建立消息隊列的函數名稱為OSQCreate。
OS_EVENT *OSQCreate (void **start,
INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
return ((OS_EVENT *)0); /* 不允許在中斷服務程式ISR中調用本函數 */
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 取得事件控制塊ECB */
if (OSEventFreeList != (OS_EVENT *)0) { /*是否有有效的ECB */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* 是否有有效的ECB */
OS_ENTER_CRITICAL();
pq = OSQFreeList; /* 在空閑QCB連結清單中取一個QCB */
if (pq != (OS_Q *)0) { /* 得到了有效的QCB嗎? */
OSQFreeList = OSQFreeList->OSQPtr; /* 取下該QCB*/
OS_EXIT_CRITICAL();
pq->OSQStart = start; /* 隊列初始化 */
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0u;
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); /* 初始化事件等待表 */
} else {
pevent->OSEventPtr = (void *)OSEventFreeList;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent);
}
- 斷是否在中斷服務程式中調用本函數, 如果是就傳回。
- 取得消息隊列的連結清單首位址送pevent。
- 判斷pevent是否為空指針, 如果是則說明是系統已經沒有空閑的ECB可供使用,填寫錯誤資訊, 傳回空指針。
- 從空閑ECB連結清單取下表頭。
- 空閑QCB連結清單首位址送pq。
- 如果沒有有效的空閑QCB連結清單, 恢複空閑ECB連結清單, 傳回空ECB指針。
- 在空閑QCB連結清單中取一個pq指向的QCB,對其進行初始化。 設定OSQStart為消息指針數組的首位址start。OSQEnd值為&start[size]即消息指針數組( 消息隊列) 中最後一個指針後面的一個位址。 OSQIn和OSQOut也設定為start。 OSQSize的值為size。 OSQEntries為0表示該隊列中還沒有消息。
- 接下來對pevent指向的ECB進行初始化。 OSEventType為OS_EVENT_TYPE_Q表示用于消息隊列管理。 OSEventCnt在這裡沒有用, 設定為0。 OSEventPtr指向QCB,即設定為pq。 調用OS_EventWaitListInit初始化ECB中的事件等待表和事件等待組。
- 傳回ECB指針。
發送
發消息到消息隊列的函數名稱為OSQPost。 參數是事件控制塊ECB的位址pevent和消息的位址pmsg。
INT8U OSQPost (OS_EVENT *pevent,
void *pmsg)
{
OS_Q *pq;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task pending on queue */
/* Ready highest priority task waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
pq = (OS_Q *)pevent->OSEventPtr; /* Point to queue control block */
if (pq->OSQEntries >= pq->OSQSize) { /* Make sure queue is not full */
OS_EXIT_CRITICAL();
return (OS_ERR_Q_FULL);
}
*pq->OSQIn++ = pmsg; /* Insert message into queue */
pq->OSQEntries++; /* Update the nbr of entries in the queue */
if (pq->OSQIn == pq->OSQEnd) { /* Wrap IN ptr if we are at end of queue */
pq->OSQIn = pq->OSQStart;
}
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
- 首先進行參數檢查, 如果參數檢查失敗則傳回。
- 如果有任務等待消息隊列中的消息, 那麼消息隊列現在必然是空的。 不需要将消息存入隊列, 而直接将消息給在等待的優先級最高的消息, 并将其就緒。 執行一次任務排程然後傳回。
- 如果沒有任務等待消息隊列中的消息, 那麼就需要将該消息加入消息隊列。 如果消息隊列是滿的, 不能容納更多的消息, 傳回出錯資訊。 否則, 在消息控制塊QCB的OSQIn所訓示的消息指針數組位置存入該消息, 然後将OSQIn 指向下一個單元以便下次使用。 判斷OSQIn是否到超過了表尾, 如果超過了, 将其指向隊首。 然後傳回。
等待
等待消息隊列的消息是消息隊列管理中的又一核心函數。 如果消息隊列中有消息,那麼就取出消息, 然後傳回; 如果沒有消息, 隻有在ECB中标記自己的等待, 然後阻塞。
等待消息隊列的函數的名稱為OSQPend, 參數是ECB的指針、 等待逾時時間和傳回函數執行資訊的指針的perr。 函數的傳回值是指向消息的指針。
void *OSQPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
void *pmsg;
OS_Q *pq;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) {/* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *)0);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pq = (OS_Q *)pevent->OSEventPtr; /* Point at queue control block */
if (pq->OSQEntries > 0u) { /* See if any messages in the queue */
pmsg = *pq->OSQOut++; /* Yes, extract oldest message from the queue */
pq->OSQEntries--; /* Update the number of entries in the queue */
if (pq->OSQOut == pq->OSQEnd) { /* Wrap OUT pointer if we are at the end of the queue */
pq->OSQOut = pq->OSQStart;
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); /* Return message received */
}
OSTCBCur->OSTCBStat |= OS_STAT_Q; /* Task will have to pend for a message to be posted */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout into TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready to run */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK: /* Extract message from TCB (Put there by QPost) */
pmsg = OSTCBCur->OSTCBMsg;
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* Clear received message */
OS_EXIT_CRITICAL();
return (pmsg); /* Return received message */
}
- 首先進行參數、 ECB類型、 中斷、 排程鎖檢查, 失敗則傳回。
- 取得ECB中的QCB指針, 查詢消息隊列中是否有消息。 如果有消息, 那麼請求直接成功, 取得消息隊列中OSQOut所指的消息, 将OSQOut指向消息隊列中的下一個元素以備下一次的消息提取。 如果OSQOut指向了消息隊列之外, 就指向消息隊列的首位址。 将OSQEntries減1表示消息數量減少了一個。 然後傳回消息的指針。
- 如果沒有消息, 那麼任務就隻有被阻塞。 首先在TCB中的OSTCBStat中添加消息隊列等待标志, 任務延時時間, 初始化等待狀态, 然後調用OS_EventTaskWait添加等待标志和取消就緒标志, 接着調用OS_Sched執行一次任務排程。
- 任務恢複運作後, 根據TCB中的等待狀态OSTCBStatPend決定程式走向。 如果獲得了消息, 進行一些處理後傳回該消息。 如果是退出等待, 或是等待逾時, 分别填寫沒有取得消息的原因, 然後傳回空指針。
删除
當消息隊列不再使用了, 就應該盡快歸還給系統, 即将消息占用的ECB歸還給ECB空閑連結清單以備它用, 将QCB也歸還給空閑QCB連結清單。
OS_EVENT *OSQDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
OS_Q *pq;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 檢視是否在中斷服務程式ISR中調用本函數 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on queue */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete queue only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pq = (OS_Q *)pevent->OSEventPtr; /* Return OS_Q to free list */
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得事件控制塊ECB */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Queue has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the queue */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for queue */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_Q, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pq = (OS_Q *)pevent->OSEventPtr; /* Return OS_Q to free list */
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得事件控制塊ECB */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Queue has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
- 首先進行參數的檢查。
- 根據選項opt決定程式的分支
- 如果opt不是這兩個值當中的一個, 那就是說明是錯誤的選項, 也屬于參數檢查失敗。 是以,标記錯誤資訊perr為OS_ERR_INVALID_OPT後, 直接傳回原來ECB指針pevent。
狀态
消息隊列資料OS_Q_DATA是為傳回消息隊列資訊而提供的, 是以使用者程式如果想了解消息隊列的資訊, 要先建立OS_Q_DATA的執行個體。 然後以消息隊列所在ECB位址及該執行個體的位址為指針為參數調用擷取消息隊列的狀态函數OSQQuery
INT8U OSQQuery (OS_EVENT *pevent,
OS_Q_DATA *p_q_data)
{
OS_Q *pq;
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (p_q_data == (OS_Q_DATA *)0) { /* Validate 'p_q_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_q_data->OSEventGrp = pevent->OSEventGrp; /* Copy message queue wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_q_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0u) {
p_q_data->OSMsg = *pq->OSQOut; /* Get next message to return if available */
} else {
p_q_data->OSMsg = (void *)0;
}
p_q_data->OSNMsgs = pq->OSQEntries;
p_q_data->OSQSize = pq->OSQSize;
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
應用
記憶體管理
嵌入式系統中, 記憶體資源是十分寶貴的, 如果采用記憶體配置設定方式不合理, 經過一段時間的記憶體配置設定和釋放、 再發配和再釋放, 會産生很多零散的記憶體碎塊。 這些零散的空間很難利用, 是以怎樣解決記憶體配置設定過程中産生的碎塊問題是記憶體管理的關鍵問題
μC/OS-II中, 采用分區的方式管理記憶體, 即将連續的大塊記憶體按分區來管理, 每個系統中有數個這樣的分區, 每個分區又包含數個相同的記憶體塊。 這樣, 在配置設定記憶體的時候,根據需要從不同的分區中得到數個記憶體塊, 而在釋放時,這些記憶體塊重新釋放回他們原來所在的分區。 這樣就不會産生記憶體越分越淩亂, 沒有整塊的連續分區的問題了。
資料結構
記憶體控制塊
typedef struct os_mem { /* MEMORY CONTROL BLOCK */
void *OSMemAddr; /* Pointer to beginning of memory partition */
void *OSMemFreeList; /* Pointer to list of free memory blocks */
INT32U OSMemBlkSize; /* Size (in bytes) of each block of memory */
INT32U OSMemNBlks; /* Total number of blocks in this partition */
INT32U OSMemNFree; /* Number of memory blocks remaining in this partition */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* Memory partition name */
#endif
} OS_MEM;
空閑記憶體控制塊
記憶體控制塊實體
OS_MEM OSMemTbl[OS_MAX_MEM_PART];
空閑記憶體控制塊連結清單
OS_MEM *OSMemFreeList;
記憶體分區
記憶體分區與消息隊列在一點上相似, 就是必須由使用者任務來建立。 其實定義一個記憶體分區是相當簡單的一件事情, 其實就是一個二維數組。
如: INT32U MemBuf [10] [20]
MemBuf就可以是一個記憶體分區, 該分區共有800個位元組, 分為10個記憶體塊。使用MCB可以将該分區管理起來, 實作動态配置設定和釋放
記憶體控制塊初始化
void OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1u
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /* Clear the memory partition table */
OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; /* Point to beginning of free list */
#if OS_MEM_NAME_EN > 0u
OSMemFreeList->OSMemName = (INT8U *)"?"; /* Unknown name */
#endif
#endif
#if OS_MAX_MEM_PART >= 2u
OS_MEM *pmem;
INT16U i;
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /* Clear the memory partition table */
for (i = 0u; i < (OS_MAX_MEM_PART - 1u); i++) { /* Init. list of free memory partitions */
pmem = &OSMemTbl[i]; /* Point to memory control block (MCB) */
pmem->OSMemFreeList = (void *)&OSMemTbl[i + 1u]; /* Chain list of free partitions */
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
}
pmem = &OSMemTbl[i];
pmem->OSMemFreeList = (void *)0; /* Initialize last node */
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
OSMemFreeList = &OSMemTbl[0]; /* Point to beginning of free list */
#endif
}
OS_MemClr這個函數很簡單, 将從pdest開始一個位元組一個位元組的将記憶體中的内容清0, 直到清0了size個位元組為止
void OS_MemClr (INT8U *pdest, INT16U size)
{
while (size > 0u) {
*pdest++ = (INT8U)0;
size--;
}
}
可見OS_MemInit, 對記憶體控制塊MCB進行了初始化, 建構了空閑MCB連結清單。但并未執行建立記憶體分區及配置設定記憶體的操作
建立記憶體分區
記憶體分區在作業系統初始化的時候并不存在。 在使用一個記憶體分區之前, 必須先定
義一個二維數組, 但這個二維數組仍未成為記憶體分區。
通過調用函數OSMemCreate, 使用一個MCB對其進行管理, 才成為一個記憶體分區。
OSMemCreate傳回一個指向記憶體控制塊的指針, 供記憶體管理的其他操作函數調用。
OSMemCreate有四個參數:
- 第一個參數是addr, 就是記憶體分區的首位址, 即從哪裡開始建立記憶體分區, 應是作為分區的二維數組的首位址。
- 第二個參數是nblks,表示需要建立的記憶體分區中記憶體塊的總數。
- 第三個參數是INT32U blksize, 表示每一個記憶體塊的大小。
- 第四個參數是指向整數的指針perr, 用來傳回函數運作過程中的資訊。
OS_MEM *OSMemCreate (void *addr,
INT32U nblks,
INT32U blksize,
INT8U *perr)
{
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U loops;
INT32U i;
#if OS_ARG_CHK_EN > 0u
if (addr == (void *)0) { /* Must pass a valid address for the memory part.*/
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (((INT32U)addr & (sizeof(void *) - 1u)) != 0u){ /* Must be pointer size aligned */
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (nblks < 2u) { /* Must have at least 2 blocks per partition */
*perr = OS_ERR_MEM_INVALID_BLKS;
return ((OS_MEM *)0);
}
if (blksize < sizeof(void *)) { /* Must contain space for at least a pointer */
*perr = OS_ERR_MEM_INVALID_SIZE;
return ((OS_MEM *)0);
}
#endif
OS_ENTER_CRITICAL();
pmem = OSMemFreeList; /* Get next free memory partition */
if (OSMemFreeList != (OS_MEM *)0) { /* See if pool of free partitions was empty */
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
OS_EXIT_CRITICAL();
if (pmem == (OS_MEM *)0) { /* See if we have a memory partition */
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
plink = (void **)addr; /* Create linked list of free memory blocks */
pblk = (INT8U *)addr;
loops = nblks - 1u;
for (i = 0u; i < loops; i++) {
pblk += blksize; /* Point to the FOLLOWING block */
*plink = (void *)pblk; /* Save pointer to NEXT block in CURRENT block */
plink = (void **)pblk; /* Position to NEXT block */
}
*plink = (void *)0; /* Last memory block points to NULL */
pmem->OSMemAddr = addr; /* Store start address of memory partition */
pmem->OSMemFreeList = addr; /* Initialize pointer to pool of free blocks */
pmem->OSMemNFree = nblks; /* Store number of free blocks in MCB */
pmem->OSMemNBlks = nblks;
pmem->OSMemBlkSize = blksize; /* Store block size of each memory blocks */
*perr = OS_ERR_NONE;
return (pmem);
}
首先, 當作業系統需要建立記憶體分區時, 調用OSMemCreate()函數, 并通過參數傳遞需要建立記憶體分區的屬性, 包括: 記憶體分區的首位址, 記憶體分區中記憶體塊的數量, 記憶體分區中記憶體塊的大小, 以及傳回資訊代碼的位址。
然後, 檢查判斷是否執行函數參數檢查, 以保證記憶體分區的建立成功。 如果執行函數參數檢查, 則判斷: 記憶體分區位址是否有效, 記憶體分區位址大小是否一緻, 記憶體分區是否至少含有兩個記憶體塊, 每個記憶體塊是否起碼可以容納一個指針。
最後, 執行建立記憶體分區的算法, 建立一個記憶體分區, 并傳回記憶體控制塊的位址, 供系統調用。 記憶體分區建立的算法是: 将記憶體分區首位址加記憶體塊大小得到下一個記憶體塊位址, 然後再加記憶體塊大小得到下一個記憶體塊位址, 如此循環(記憶體塊數量-1) 次, 直至最後一個記憶體塊建立完成。 在其過程中, 通過指針建立記憶體塊的連結表, 并将最後一個記憶體塊内的指針指向空位址。
記憶體塊擷取
建立之後, 形成了一個空閑記憶體塊連結清單, 而該連結清單由記憶體控制塊MCB來管理。應用程式需要從已建立的記憶體分區中申請一個記憶體塊時, 可以調用OSMemGet函數。 該函數有兩個參數, 第一個參數為pmem, 也就是前節建立記憶體分區函數所傳回的記憶體控制塊的指針。 另一個參數為perr, 用于傳回函數執行的信心
void *OSMemGet (OS_MEM *pmem,
INT8U *perr)
{
void *pblk;
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
*perr = OS_ERR_MEM_INVALID_PMEM;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree > 0u) { /* See if there are any free memory blocks */
pblk = pmem->OSMemFreeList; /* Yes, point to next free memory block */
pmem->OSMemFreeList = *(void **)pblk; /* Adjust pointer to new free list */
pmem->OSMemNFree--; /* One less memory block in this partition */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE; /* No error */
return (pblk); /* Return memory block to caller */
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_MEM_NO_FREE_BLKS; /* No, Notify caller of empty memory partition */
return ((void *)0); /* Return NULL pointer to caller */
}
- 首先進行參數檢查, 檢視MCB指針是否有效。
- 判斷是否有空閑的記憶體塊, 如果沒有就設定perr為OS_ERR_MEM_NO_FREE_BLKS, 然後傳回空指針。
- 從連結清單中摘下表頭, 傳回取下的記憶體塊的位址。 這樣就申請到了記憶體塊。
釋放記憶體分區
記憶體塊的釋放, 就是将記憶體塊歸還給空閑記憶體塊連結清單, 也可以了解為歸還給記憶體分區。 記憶體分區釋放函數是OSMemPut。
記憶體塊釋放函數是OSMemPut有兩個參數, 一個是記憶體控制塊MCB的位址, 一個是将釋放的記憶體塊的位址。 OSMemPut傳回整數型的操作過程的資訊号
INT8U OSMemPut (OS_MEM *pmem,
void *pblk)
{
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
return (OS_ERR_MEM_INVALID_PMEM);
}
if (pblk == (void *)0) { /* Must release a valid block */
return (OS_ERR_MEM_INVALID_PBLK);
}
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree >= pmem->OSMemNBlks) { /* Make sure all blocks not already returned */
OS_EXIT_CRITICAL();
return (OS_ERR_MEM_FULL);
}
*(void **)pblk = pmem->OSMemFreeList; /* Insert released block into free block list */
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++; /* One more memory block in this partition */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE); /* Notify caller that memory block was released */
}
- 首先進行參數檢查, 檢視MCB指針是否有效, 記憶體塊指針是否有效。
- 檢查記憶體塊是不是滿的, 判斷條件是是否空閑的塊數大于等于最大塊數。 如果是滿的不能釋放。 這種情況一定是異常引起的。
- 将釋放的塊歸還給空閑記憶體塊連結清單, 插入到表頭然後傳回。
從本節的OSMemPut和上節OSMemGet,很明顯, 實作了記憶體的動态配置設定, 并且一次配置設定和釋放最小是一個記憶體塊, 保證了記憶體中不會存在很多小的碎片。
查詢記憶體分區狀态
INT8U OSMemQuery (OS_MEM *pmem,
OS_MEM_DATA *p_mem_data)
{
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
return (OS_ERR_MEM_INVALID_PMEM);
}
if (p_mem_data == (OS_MEM_DATA *)0) { /* Must release a valid storage area for the data */
return (OS_ERR_MEM_INVALID_PDATA);
}
#endif
OS_ENTER_CRITICAL();
p_mem_data->OSAddr = pmem->OSMemAddr;
p_mem_data->OSFreeList = pmem->OSMemFreeList;
p_mem_data->OSBlkSize = pmem->OSMemBlkSize;
p_mem_data->OSNBlks = pmem->OSMemNBlks;
p_mem_data->OSNFree = pmem->OSMemNFree;
OS_EXIT_CRITICAL();
p_mem_data->OSNUsed = p_mem_data->OSNBlks - p_mem_data->OSNFree;
return (OS_ERR_NONE);
}
記憶體管理執行個體
過載
負載是動态的
定義
過載系數
非周期任務軟式定義 Soft aperiodic tasks
平均計算時間 / 平均周期
周期任務硬式定義 Hard periodic tasks
通用實時應用 Generic RT application
瞬時負載
溢出
執行溢出 Execution Overrun
啟動溢出 Activation Overrun
開銷
高預測性:高成本,适用硬實時
高有效性:容忍過載,适應,處理過載
處理方法
全局 Global Policies
本地 Local Policies
被動 Reactive Methods
- 過載檢測 OverLoad detection
- 異常通知 Exception notification
- 異常處理 Exception handling
- 系統重新開機
- 抛棄任務
- 拒絕最不重要的任務
- 允許性能下降
- 不操作,隻是通知
主動 Proactive Methods
提前預防過載
- 拒絕任務
- 減少計算量
- 降低優先級
- 推遲截止時間
- 跳過一定的作業
- 降低啟動速率,就是增加周期
現有方法
方法分類
- 準入控制
- 降低系統性能
- 資源預留
簡單準入控制Simple Admission Control
帶回報的準入 Admission by Feedback
以上政策比較簡單,但是任務可能錯過截止時間,而且沒有考慮任務重要性
考慮重要性 Taking Value into Account
最重要優先 Highest Value First
如果EDF最優,否則所有任務不能再截止時間完成
魯棒EDF政策 Robust EDF Scheduling
接受,否則拒絕。通過deadline排程,但是通過value拒絕。
如果有任務提前完成,拒絕的任務還可以恢複。
精确計算
分為必須部分和可選部分,完成越多精确性越高
減少功能,相當于代碼減少
跳過作業 FIRM Task Model
每個作業要麼在截止時間之前完成,要麼被整體拒絕。如果藍色任務被丢棄,那麼接下來Si-1個任務都為紅色;如果藍色作業被完成,那麼接下來的作業還是藍色;前Si-1個作業都為紅色。
擴大周期 Relaxing Timing Constraints
每個任務的周期在[Tmin, Tmax]之間,過載時增大任務周期
彈簧任務模型 Elastic Task model
通過彈性系數Ei改變任務周期
有限搶占排程
搶占缺點
搶占會存在任務切換的成本
引起高速緩存搶占相關延時CRPD,增加WCET(worst case execution time)
增加流水線成本,總線成本
增加額外執行時間,也會增加排程的任務數
總之,WCET估計時間更大,不可預測性更大
非搶占好處
減少上下文切換開銷,使WCETs更小,更加可預測
簡化共享資源,不需要信号量
減少棧大小,任務可以使用共同一個棧
能達到零I/O抖動,執行時間固定
非搶占劣勢
高優先級被推後
執行界限會被掉為零
有限搶占 Trade-off Solution
有門檻值的搶占 preemption thresholds
延遲搶占 Deferred Preemption
固定點搶占 Fixed preemption Points
WCET估計
動态方法
測量
靜态方法
流分析
循環邊界 loop bound
可行路徑 infeasible paths
計算
硬體層面分析
目前研究方向
驗證時間有效性
資料+執行,在目标機或模拟測試
必要但不充分
最壞情況響應時間 Worst-Case Response Time
計算時間+幹擾
WCET分析
計算每一個獨立代碼部分的執行時間