第六章
任務優先級及任務狀態
到現在爲止我們其實已經實現了一些多任務的功能,而且我們也能從中總結出一些狀態,比如正在運行,睡眠,掛起,就緒。這些狀態都有他們特殊的邏輯,在之後我們還會設計支持時間片的方式,這時候我們只有就緒表就有些不夠用了,我們需要在任務控制塊中添加任務狀態屬性。
在前面講到的任務切換邏輯時間上是進行了任務ID從大到小的先後順序進行調度,這就有點像優先級,只不過優先級與任務ID是一個。同樣在後面時間片的方式中要對同優先級的任務進行邏輯處理,所以我們需要在任務控制塊中添加優先級的屬性。
typedef struct os_tcb {
u8 OSTCBStkPtr; /* 指向堆棧棧頂的指針 */
u16 OSTCBDly; /* 任務等待的節拍數 */
u16 OSTCBStatus; /* 任務狀態 */
u8 OSTCBPrio; /* 任務優先級 */
#ifdef STACK_DETECT_MODE
u8 OSTCBStkSize; /* 堆棧的大小 */
#endif
} OS_TCB;
OSTCBStatus的類型目前有如下這些
/*
*********************************************************************************************************
* TASK STATUS (Bit definition for OSTCBStatus)
*********************************************************************************************************
*/
#define OS_STAT_DEFAULT 0x0000u /* 初始化的默認值 */
#define OS_STAT_RUNNING 0x0001u /* 運行中 */
#define OS_STAT_RDY 0x0002u /* Ready to run */
#define OS_STAT_SLEEP 0x0004u /* Task is sleeping */
#define OS_STAT_SUSPEND 0x0008u /* Task is suspended */
在狀態屬性中有就緒這個標誌OS_STAT_RDY我們在需要判斷就緒狀態時就可以使用這個屬性,所以我們把現有代碼中就緒表使用狀態屬性代替,這裏改動位置有些多,就不再一一貼代碼了,需要的可以下載教程源碼查看。
對於優先級我們需要在創建任務時指定,所以修改創建任務函數
u8 os_task_create(void (*task)(void), u8 taskID, u8 task_prio, u8 *pstack, u8 stack_size)
{
if (taskID >= TASK_SIZE)
return (OS_TASK_ID_INVALID);
if (task_prio == OS_IDLE_TASK_PRIO)//不允許與idle相同優先級
return (OS_PRIO_INVALID);
if (os_tcb[taskID].OSTCBStkPtr != 0)
return (OS_TASK_ID_EXIST);
OS_ENTER_CRITICAL();
#ifdef STACK_DETECT_MODE
{
u8 i = 0;
for (; i<stack_size; i++)
pstack[i] = STACK_MAGIC;
}
#else
stack_size = stack_size; //消除編譯警告
#endif
*pstack++ = (u16)task; //將函數的地址高位壓入堆棧,
*pstack = (u16)task>>8; //將函數的地址低位壓入堆棧,
{
u8 i = 0;
for (; i < 13; i++) //初次被任務調度函數pop時需要清空堆棧內容,避免原有內容導致錯誤
*(++pstack) = 0;
}
os_tcb[taskID].OSTCBStkPtr = (u8)pstack; //將人工堆棧的棧頂,保存到堆棧的數組中
//os_rdy_tbl |= 0x01<<taskID; //任務就緒表已經準備好
os_tcb[taskID].OSTCBStatus = OS_STAT_RDY;
os_tcb[taskID]. OSTCBPrio = task_prio;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
我們看到在開始的時候我們判斷了優先級是否爲idle(0),這樣是不被允許的,任何任務不能與idle均分時間片,這樣會影響idle中的統計和用戶任務。
在最後我們也將OSTCBPrio屬性賦值了。
既然有了優先級我們在開始調度任務的時候就可以直接運行優先級最高的任務,而不是傻傻的運行idle,等着idle進行任務調度。修改啓動運行函數如下:
void os_start_task(void)
{
char i = 0;
u8 highest_prio_id = 0;
u8 highest_prio = 0;
tick_timer_start();
for (; i<TASK_SIZE; i++)
{
if ((os_tcb[i].OSTCBStatus == OS_STAT_RDY) && (os_tcb[i].OSTCBPrio > os_tcb[highest_prio_id].OSTCBPrio))
highest_prio_id = i;
}
os_task_running_ID = highest_prio_ id;
os_tcb[highest_prio_id].OSTCBStatus = OS_STAT_RUNNING;
SP = os_tcb[highest_prio_id].OSTCBStkPtr - 13;
}
其中是由for循環遍歷所有任務控制塊,找到最大優先級的任務,並運行它。
這裏根據邏輯我們定義的優先級範圍是1-255,數值越大優先級越高。
有了以上代碼我們會發現如果我們在任務運行起來之後再調用創建任務時並不能是進行任務調度,這樣就導致我們在start之後create更高優先級的任務時不能馬上進行調度,這就不是一個好的搶佔式內核了,所以我們需要做一個start標誌,並在create時進行判斷是否需要進行任務調度。
u8 os_task_create(void (*task)(void), u8 taskID, u8 task_prio, u8 *pstack, u8 stack_size)
{
……
if (os_core_start) {//如果任務已經啓動則進行一次任務調度
os_tcb[os_task_running_ID].OSTCBStatus = OS_STAT_RDY; //先將自己置位就緒態
OS_EXIT_CRITICAL();
OS_TASK_SW();
}
return (OS_NO_ERR);
}
現在我們需要考慮讓任務能夠修改優先級,我們提供修改優先級函數。
// 此功能允許您動態更改任務的優先級。 請注意,新的優先級必須可用。
// 返回:OS_NO_ERR:是成功
// OS_TASK_ID_INVALID:如果試圖改變idle任務的優先級或者任務不存在或者任務ID超過最大任務數量
// OS_PRIO_INVALID: 如果試圖更改爲idle任務的優先級(0)是不允許的
// OS_PRIO_ERR: 其他錯誤
u8 os_task_change_prio (u8 taskID, u8 new_prio)
{
if (taskID == OS_IDLE_TASKID)
return (OS_TASK_ID_INVALID);
if (new_prio == OS_IDLE_TASK_PRIO)
return (OS_PRIO_INVALID);
if (taskID >= TASK_SIZE && taskID != OS_TASKID_SELF)
return (OS_TASK_ID_INVALID);
OS_ENTER_CRITICAL();
if ((taskID < TASK_SIZE) && (os_tcb[taskID].OSTCBStkPtr == 0)) {
OS_EXIT_CRITICAL();
return (OS_TASK_ID_INVALID);
}
if ((taskID == OS_TASKID_SELF) || (taskID == os_task_running_ID)){
os_tcb[os_task_running_ID].OSTCBPrio = new_prio;
OS_EXIT_CRITICAL();
} else {
os_tcb[taskID].OSTCBPrio = new_prio;
OS_EXIT_CRITICAL();
OS_TASK_SW();
}
return (OS_NO_ERR); //return(0)
}
下面我們寫一段測試代碼測試一下
首先我們準備3個任務
任務1 優先級爲20,任務2 優先級爲10,任務3初始優先級爲15
void app_task_3(void)
{
while (1) {
debug_print("app_task_3\r\n");
os_tick_sleep(100);
}
}
void app_task_1(void)
{
u8 cnt = 0;
while (1) {
if (cnt++ == 3)
os_task_change_prio(3, 30);
debug_print("app_task_1\r\n");
os_tick_sleep(100);
}
}
void app_task_2(void)
{
os_task_create(app_task_3, 3, 15, app3_stack, 25);
while (1) {
debug_print("app_task_2\r\n");
os_tick_sleep(100);
}
}
程序開始時先create任務1和任務2
Start後會先運行任務1,因爲任務1優先級最高,然後任務1會讓出cpu進入sleep,任務2執行,任務2中會create任務3,因爲任務3比任務2優先級高會先運行任務3,然後任務3進入sleep,繼續運行任務2,任務會按照132,132,132的順序運行。當任務1運行到第4次時修改任務3優先級爲30,此時任務3最高,會觸發調度,但是任務3在sleep所以還是繼續運行任務1.
輸出如下:
[2019-09-04 23:14:52.037]# RECV ASCII>
STC15F2K60S2 UART1 Test Prgramme!
app_task_1
app_task_3
app_task_2
idle_task_0 in
[2019-09-04 23:14:53.042]# RECV ASCII>
app_task_1
app_task_3
app_task_2
[2019-09-04 23:14:54.043]# RECV ASCII>
app_task_1
app_task_3
app_task_2
[2019-09-04 23:14:55.045]# RECV ASCII>
app_task_3
app_task_1
app_task_2
之後一次非常有趣,順序從132變爲312,只是由於時鐘中斷計時的sleep要比idle中的任務切換要快,這就導致有一次調度時發生123任務同時進入就緒態,這時就會優先運行優先級高的3,之後是1,最後是2.
這個問題是由於沒有中斷context中進行過調度,所以sleep時間到了的時候沒有及時的進行調度產生的,這個問題我們會在下一章搶佔式內核中進行完善。