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

第四章

任務就緒表

我們有任務之後爲了方便切換的時候判斷需要讀取那個任務的屬性進行比較,我們就需要任務就緒表來記錄哪個任務是就緒的,我們可以通過查表來找到這個就緒的任務。

由於就緒表記錄的狀態只有就緒和未就緒兩種,所以我們可以使用1位來表示一個任務的就緒狀態。

假如說我們有8個任務,那麼我們可以使用一個字節表示這8個任務的就緒狀態,第0位就是任務0的就緒狀態,以此類推。

代碼中任務就緒表定義爲

u8 os_rdy_tbl = 0;

任務控制塊

既然我們有任務了,就需要一個結構來記錄每個任務的屬性,這樣我們才能實現任務切換算法。下面我們參考ucos的任務控制塊來實現我們自己的任務控制塊。

我們需要先確定我們想要支持多少個任務,好用來分配多少個任務控制塊來記錄任務。

這個任務的數量不是憑空想象的,一定是有一些限制。上面做堆棧講解的時候說過,stc單片機能夠作爲堆棧的大小隻有256個字節,加入我們爲每個任務分配20個字節堆棧(這樣往往是不夠的),那麼我們就能有最多12個任務。但是還要考慮中斷和函數執行過程中使用堆棧所以不可能有12個任務,那麼我們選擇8個任務吧,因爲我們有個任務就緒表的概念,任務就緒表是8位爲一個字節,所以任務儘量選擇8的倍數爲宜。

typedef struct os_tcb {
    void            *OSTCBStkPtr;           /* 指向堆棧棧頂的指針            */
#ifdef STACK_DETECT_MODE
    u8              OSTCBStkSize;           /* 堆棧的大小                 */
#endif
} OS_TCB;

暫時定義控制塊中的數據有這些,之後需要在向裏添加。

我們還需要一個能夠記錄當前運行的是哪一個任務的當前任務id記錄變量。

u8 os_task_running_num = 0;

這樣我們創建任務的函數功能就是將相應任務控制塊進行初始化的過程。

//建立任務
u8 os_task_create(void (*task)(void), u8 taskID, u8 *pstack, u8 stack_size)
{
    if (taskID >= TASK_SIZE)
        return (OS_TASK_NUM_INVALID);
    if (os_tcb[taskID].OSTCBStkPtr != NULL)
        return (OS_TASK_NUM_EXIST);

#ifdef STACK_DETECT_MODE
{
    u8 i = 0;
    for (; i<stack_size; i++)
        pstack[i] = STACK_MAGIC;
}
#else
    stack_size = stack_size; //消除編譯警告
#endif
    *pstack++ = (unsigned int)task; //將函數的地址高位壓入堆棧,
    *pstack = (unsigned int)task>>8; //將函數的地址低位壓入堆棧,
    pstack += 13;

    os_tcb[taskID].OSTCBStkPtr = pstack; //將人工堆棧的棧頂,保存到堆棧的數組中
    os_rdy_tbl |= 0x01<<taskID; //任務就緒表已經準備好

    return (OS_NO_ERR);
}

這個時候我們只是把控制塊填充和就緒表更新,sp並沒有切換到對應任務,所以我們還需要一個start函數。

//開始任務調度,從最低優先級的任務的開始
void os_start_task(void)
{
    os_task_running_num = 0;
    SP = (u8)os_tcb[0].OSTCBStkPtr - 13;
}

這樣我們就能在調用函數之後進行任務切換。

這裏啓動的是任務0,也就是說這個操作系統必須有一個任務0,我們可以在後面把任務0作爲空閒任務,在這裏可以進行一些省電和統計任務,優先級最低。爲了不讓用戶更改這個任務,我們可以增加任務初始化函數,在這裏進行任務0的創建。同時也可以暴露回調函數進行空閒任務的用戶接口。

idle_user idle_user_func = NULL;
//空閒任務
void idle_task_0(void)
{
    while (1) {
        if (idle_user_func)
            idle_user_func();
    }
}

#define TASK0_STACK_LEN 20
STACK_TYPE stack[TASK0_STACK_LEN]; //建立一個 20 字節的靜態區堆棧
//操作系統初始化
void os_init(void)
{
    os_task_create(idle_task_0, 0, stack, TASK0_STACK_LEN);
}

這樣我們只需要在main函數中這樣調用。

void main(void)
{
    os_init();
    os_start_task();
}

就可以了。

到這裏我們就構建起了最基本的只有idle任務的操作系統。

這裏我們會發現最多可以定義8個任務,而且還有1個被空閒任務佔用,也就是隻留給了用戶7個任務,這樣的操作系統有什麼意義呢?其實只要經過精心的設計,7個任務已經夠用,除非有特殊要求的。

但是還是有解決辦法,只是比較耗費系統資源,會增加調度開銷。

方法我大致說一下思路,這裏我們還是以學習爲主,從最簡單的做起,下面的這段話聽不懂也不要求,在全部學完之後會對這段話有更好的理解。

前面我們說過,對於stc單片機來說影響任務個數的因素主要是堆棧的大小,那麼如何能夠解決呢?片內的ram只有256,但是片外的ram可是有3840字節的,這樣比較片內資源要豐富許多。我們只需要在片內ram中開闢一個共享最大堆棧,例如200字節,每個任務的堆棧都是用片外ram保存,每次任務切換時我們都進行一次片內片外的交換,就可以實現我們的想法了。大致過程如下:

任務切換髮生

->將片內ram數據拷貝到對應任務的片外ram上

經過任務調度,需要運行task2,我們就把task2的片外堆棧拷貝到片內堆棧中

然後我們設置好sp指針的值,這個在任務控制塊中有棧頂指針做記錄,之後退出調度函數就可以繼續運行task2了。

大體思想就是這樣,這裏就不進行代碼實現了,感興趣的同學可以自己嘗試實現以下。

 

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