(5)從1開始寫一個操作系統

第五章

這一張我們開始進行任務調度的相關實現,先從最簡單的任務調度開始,任務主動釋放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

至此我們已經準備好了一個滴答時鐘了,可以進行我們的時間片輪訓概念了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章