第五章
這一張我們開始進行任務調度的相關實現,先從最簡單的任務調度開始,任務主動釋放cpu。
主動釋放CPU
我們需要先準備兩個任務,就是要進行這兩個任務之間的主動釋放,實現任務切換。
我們爲了觀察結果,這裏我們使用串口操作,需要把bsp中的串口部分打開。
首先修改idle任務,讓它在運行時打印一行log,並延時1秒,避免刷屏。
void idle_task_0(void)
{
while (1) {
if (idle_user_func)
idle_user_func();
debug_print("idle_task_0\r\n");
delay_ms(1000);
}
}
然後我們在準備一個app任務,也是打印一行log。
void app_task_1(void)
{
while (1) {
debug_print("app_task_1\r\n");
delay_ms(1000);
}
}
準備好了兩個切換的任務後我們就開始今天的正題,釋放cpu,我們準備一個功能就是任務進入掛起態,這個是把自己就緒標誌設置爲0,並觸發任務調度。而且爲了能使其他任務正常調度我們需要一個取消任務掛起函數。
總結一下,我們需要準備3個函數,任務掛起,任務取消掛起,任務調度。
首先,我們實現任務調度,這個函數需要做的工作是將現有任務的寄存器入棧,然後找到下一個需要運行的任務ID,並將需要運行的任務寄存器出棧並賦值SP指針。
//任務調度函數,只能在非中斷中使用
void OS_TASK_SW(void)
{
u8 i = 0;
#pragma asm
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#00H
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7
#pragma endasm
os_tcb[os_task_running_ID].OSTCBStkPtr = SP;
//切換任務棧
for (; i<TASK_SIZE; i++)//查找就緒的任務
{
if (os_rdy_tbl & (1<<i))
break;
}
os_task_running_ID = i;
SP = os_tcb[os_task_running_ID].OSTCBStkPtr;
#pragma asm
POP AR7
POP AR6
POP AR5
POP AR4
POP AR3
POP AR2
POP AR1
POP AR0
POP PSW
POP DPL
POP DPH
POP B
POP ACC
#pragma endasm
}
任務掛起函數,其實很簡單,只需要將自己的就緒標誌清0,然後觸發任務調度就行了。
//描述:調用此函數去掛起一個任務,如果傳送到os_task_suspend()的任務的ID是要掛起的任務或者是OS_TASKID_SELF,那麼這個任務將被掛起。
//參數:taskID:需要掛起任務的ID。如果指定OS_TASKID_SELF,那麼這個任務將自己掛起,再發生再次調度。
//返回:OS_NO_ERR:如果請求的任務被掛起。
//OS_TASK_SUSPEND_IDLE:如果想掛起空閒任務
//OS_TASK_ID_INVALID:想掛起任務優先級不合理
//OS_TASK_SUSPEND_TASKID:需要掛起的任務不存在。
u8 os_task_suspend (u8 taskID)
{
BOOLEAN self;
if (taskID == OS_IDLE_TASKID) {
return (OS_TASK_SUSPEND_IDLE);
}
OS_ENTER_CRITICAL();
if (taskID >= TASK_SIZE && taskID != OS_TASKID_SELF) {
OS_EXIT_CRITICAL();
return (OS_TASK_ID_INVALID);
}
if ((taskID < TASK_SIZE) && (os_tcb[taskID].OSTCBStkPtr == 0)) {
OS_EXIT_CRITICAL();
return (OS_TASK_SUSPEND_TASKID); // return(90)
}
if (taskID == OS_TASKID_SELF) {
os_rdy_tbl &= ~(0x01<<os_task_running_ID); //清除任務就緒表標誌
self = TRUE;
} else {
self = FALSE;
}
OS_EXIT_CRITICAL();
if (self == TRUE) {
OS_TASK_SW();
}
return (OS_NO_ERR); //return(0)
}任務恢復函數,就是將需要恢復的任務就緒表置位,並觸發任務調度
//描述:調用此函數以恢復先前掛起的任務。
//參數:taskID是恢復任務的優先級。
//返回:OS_NO_ERR:如果請求的任務被恢復。
//OS_TASK_ID_INVALID:想恢復任務優先級不合理
//OS_TASK_RESUME_TASKID:需要恢復的任務不存在。
//OS_TASK_NOT_SUSPENDED:如果要恢復的任務尚未掛起
u8 os_task_resume (u8 taskID)
{
if (taskID >= TASK_SIZE) { /* Make sure task priority is valid */
return (OS_TASK_ID_INVALID);
}
OS_ENTER_CRITICAL();
if (os_tcb[taskID].OSTCBStkPtr == 0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_TASK_RESUME_TASKID); // return(90)
}
os_rdy_tbl |= 0x01<<taskID; //任務就緒表已經準備好
OS_EXIT_CRITICAL();
OS_TASK_SW();
return (OS_NO_ERR);
}
這樣我們還需要修改一下需要切換的兩個任務,分別如下,在任務中分別調用掛起和恢復,這個原因我們運行之後在講解
void idle_task_0(void)
{
while (1) {
if (idle_user_func)
idle_user_func();
debug_print("idle_task_0\r\n");
delay_ms(1000);
//os_task_resume (1);
os_task_suspend (OS_TASKID_SELF);
}
}
void app_task_1(void)
{
while (1) {
debug_print("app_task_1\r\n");
delay_ms(1000);
os_task_resume (0);
//os_task_suspend (OS_TASKID_SELF);
}
}
運行結果如下:
[2019-09-01 16:06:03.368]# RECV ASCII>
STC15F2K60S2 UART1 Test Prgramme!
idle_task_0
[2019-09-01 16:06:04.366]# RECV ASCII>
app_task_1
[2019-09-01 16:06:05.367]# RECV ASCII>
idle_task_0
[2019-09-01 16:06:06.368]# RECV ASCII>
app_task_1
[2019-09-01 16:06:07.370]# RECV ASCII>
idle_task_0
[2019-09-01 16:06:08.370]# RECV ASCII>
app_task_1
[2019-09-01 16:06:09.371]# RECV ASCII>
idle_task_0
現在來說一下上面的函數爲什麼沒有調用恢復掛起,咱們現在的調度算法是最簡單的算法,可以看出這個算法沒有任何優先級和時間片的算法,只是簡單的從0任務開始查找,只要是有就緒的就運行,所以如果任務0那次恢復任務1是無效的。同樣的原因,任務1之後的那次掛起也是無效的。
延遲功能
這一節我們需要實現類似於linux中sleep相似的函數,就是讓任務進入睡眠模式,把cpu讓給其它任務。我們需要使用到timer中斷來記錄已經過去的時間,這個中斷剛好也可以在後面作爲時間片的設計。
我們還需要把idle任務改爲不停地做任務切換的方法,迴歸到它真正的用法。
void idle_task_0(void)
{
while (1) {
if (idle_user_func)
idle_user_func();
if (os_rdy_tbl != 1)
OS_TASK_SW ();
}
}
首先我們要在任務控制塊中添加一個屬性,是任務等待節拍數
U16 OSTCBDly;
由於新加入了屬性,所以我們需要對之前實現的功能做review,看看是否有邏輯衝突。當我們掛起其它任務時,如果這個任務正在進入休眠狀態,那麼當休眠時間到達時我們的任務會被再次喚醒,此次掛起就失去作用,所以我們需要在掛起函數中清空等待節拍數。同理恢復函數也需要進行相同操作。
如果我們需要延遲就把延遲的節拍數寫入,然後在中斷中自減,當減到0時就把就緒標誌置位,並在idle中進行任務調度。
/********************* Timer0中斷函數************************/
void timer0_int (void) interrupt TIMER0_VECTOR
{
u8 i;
for(i=0; i<TASK_SIZE; i++) { //任務時鐘
if(os_tcb[i].OSTCBDly) {
os_tcb[i].OSTCBDly--;
if(os_tcb[i].OSTCBDly == 0) { //當任務時鐘到時,必須是由定時器減時的纔行
os_rdy_tbl |= (0x01<<i); //使任務在就緒表中置位
}
}
}
}
普通任務需要調用使任務能夠進入休眠的函數void os_tick_sleep(u16 ticks)
這個函數就是把任務的就緒狀態清除,並填充任務的等待節拍數,然後觸發調度
void os_tick_sleep(u16 ticks)
{
if(ticks) { //當延時有效
OS_ENTER_CRITICAL();
os_rdy_tbl &= ~(0x01<<os_task_running_ID); //清除任務就緒表標誌
os_tcb[os_task_running_ID].OSTCBDly = ticks;
OS_EXIT_CRITICAL();
OS_TASK_SW(); //從新調度
}
}
用戶任務就需要自行調用這個函數即可
void app_task_1(void)
{
while (1) {
debug_print("app_task_1\r\n");
os_tick_sleep(100);
}
}
運行結果:
[2019-09-01 23:04:11.701]# RECV ASCII>
STC15F2K60S2 UART1 Test Prgramme!
app_task_2
app_task_1
[2019-09-01 23:04:12.699]# RECV ASCII>
app_task_1
[2019-09-01 23:04:13.702]# RECV ASCII>
app_task_2
app_task_1
[2019-09-01 23:04:14.702]# RECV ASCII>
app_task_1
[2019-09-01 23:04:15.704]# RECV ASCII>
app_task_2
app_task_1
至此我們已經準備好了一個滴答時鐘了,可以進行我們的時間片輪訓概念了。