手把手,嘴對嘴,講解UCOSII嵌入式操做系統的任務調度策略(五)

 

整個UCOSII嵌入式操做系統的任務調度策略即是如此,如今進行一個總結:架構

①某個任務在執行中,每隔必定週期發生滴答時鐘中斷,在中斷中遍歷整個任務鏈表,更新每一個任務的延時時間,修改就緒狀態。app

②任務執行完畢後,進入延時函數,在延時函數中會把當前任務掛起(清空當前任務的就緒狀態,使其進入未就緒狀態),而後根據查表發找到在就緒任務中,優先級最高的那一個任務。函數

③找到新任務之後,人工強制發生一箇中斷,保存上個任務的堆棧信息,彈出下個任務的堆棧信息,同時更改PC指針,進行任務切換。this

通過以上三個步驟,即可以完成任務的調度。spa

如今回到第一篇提出的那個問題:UCOSII究竟是如何保證它的實時性的呢? 操作系統

若是任務的調度都是發生在當前任務進入延時以後,彷佛操做系統根本沒法自身的保障實時性。指針

好比一個優先級最低的任務因爲某些處理很是耗費時間,它一直沒法進入延時,致使沒法進入任務切換,那麼優先級高的任務反而是一隻都沒法被執行了……code

一樣在第一篇說過,UCOSII系統除了在當前任務進入延時函數會發生調度以外,還有別的時機會進行任務切換:orm

  1.當前任務進入了延時。blog

  2.當前任務被掛起。

  3.當前任務執行時,發生了某些中斷。

 

第1點咱們已經所有講完,第2點很是好理解,咱們如今看一個函數:OSTaskSuspend()

這個函數的做用是把某個任務掛起(也就是不進行調度),如今來分析一個實例:

有一個任務調用了這個函數:

void App1_task(void *pdata)
{
    while(1)
    {
        if (OS_ERR_NONE != OSTaskSuspend(OS_PRIO_SELF))
        {
            Dbg_SendStr("App1_task Suspend Error£¡\r\n");
        }
        delay_ms(10);
    };
}

當前任務執行了紅色代碼以後,便會把自身掛起來,若是沒有再別的地方對它進行激活,這個任務便永遠也不會執行下去了。

深刻分析OSTaskSuspend函數:

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);
}

直接從紅色代碼部分開始看,他首先判斷一下我要掛起的任務是否是本身,如今咱們傳的參數就是OS_PRIO_SELF,全部它應該執行第一個if判斷。

在這個if判斷中保存了一下須要掛起的任務的優先級,而後用藍色代碼判斷一下須要掛起的任務是否存在(因爲咱們掛起的是自身,自身確定是存在的,可是這並不表示這個判斷多餘,由於若是是一個優先級爲1的任務調用這個函數去掛起一個優先級爲2的任務,那判斷一下仍是很必要的)。

而後接下來的幾句代碼就不用再解釋了,和任務進入延時函數把本身的就緒狀態狀況是一毛同樣的處理。

直接看ptcb->OSTCBStat |= OS_STAT_SUSPEND這句代碼,變量OSTCBStat 很容易理解,它表示當前任務的狀態,整句代碼的意義就是給當前任務設定一個已經被人工掛起了的狀態,省得在任務調度的時候被調度出來(在滴答時鐘中斷中有這個變量的判斷)。

這句代碼之後:

    if (self == OS_TRUE) {                                      /* Context switch only if SELF         */
        OS_Sched();                                             /* Find new highest priority task      */
    }

 

這幾句代碼也已經很熟悉了,中間那個函數就是任務切換,先看看那個判斷,若是我要掛起的是當前任務,那麼就當即進行切換,若是掛起的是別的任務,那就不用切換,這個理解起來應該不難。

在理解的第一種切換時機的前提下,第二種任務切換的時機很好理解,可是第二種任務切換的時機仍然不能保證任務執行的實時性,若是低優先級的任務既不進入延時,也不掛起,高優先級的任務依然沒法執行。

 

如今來看第三種,當中斷髮生時,任務切換……

在任務執行期間,發生頻繁的中斷必然就是滴答時鐘中斷,如今從新回到之前看過的那個中斷服務函數:

void SysTick_Handler(void)
{
    if(delay_osrunning==1)                      //OS開始跑了,才執行正常的調度處理
    {
        OSIntEnter();                           //進入中斷
        OSTimeTick();                           //調用ucos的時鐘服務程序
        OSIntExit();                            //觸發任務切換軟中斷
    }
}

 

這一次的重點再也不是第二個函數,而是第一個和第三個函數:OSIntEnter,OSIntExit。

這兩個函數是成對出現,從函數名即可看出,OSIntEnter是進入中斷時候調用,OSIntExit是離開中斷時候調用。

因爲滴答時鐘是週期性調用,所以這兩個函數也是週期性被調用

OSIntEnter的定義以下:

void  OSIntEnter (void)
{
    if (OSRunning == OS_TRUE) {
        if (OSIntNesting < 255u) {
            OSIntNesting++;                      /* Increment ISR nesting level                        */
        }
    }
}

 

入口函數的定義很簡單,就是對變量OSIntNesting執行加處理,表示我如今正在執行中斷函數,若是發生了中斷,或者有中斷嵌套,那麼這個變量確定是大於1的,在系統的不少地方,都須要判斷這個變量,由於不少地方都不能在中斷中執行。

出口函數的定義就有些複雜了:

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();
    }
}

 

直接從紅色部分開始看,首先判斷系統是否在運行,在系統運行的前提下,對變量OSIntNesting進行減處理。

當進入中斷之後,調用入口函數,對變量OSIntNesting加1,中斷內容處理完之後,對變量OSIntNesting減1,當變量OSIntNesting爲0的時候,表示沒有進行中斷處理,這個時候才能夠進行任務切換。

            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_SchedNew,這個函數也已經熟悉了,做用就是尋找在就緒任務中,優先級最高的那一個。

把優先級最高的任務保存在OSPrioHighRdy中,若是當前任務不等於優先級最高的任務,那麼就調用系統函數OSIntCtxSw進行任務切換……

 

看到這裏,應該可以回答那個問題了:如何保證系統的實時性?

void SysTick_Handler(void)
{
    if(delay_osrunning==1)                      //OS開始跑了,才執行正常的調度處理
    {
        OSIntEnter();                           //進入中斷
        OSTimeTick();                           //調用ucos的時鐘服務程序
        OSIntExit();                            //觸發任務切換軟中斷
    }
}

 

在中斷服務函數中,第二個函數負責更新任務就緒表,第三個任務負責切換任務,由於滴答中斷是週期性發生的,因此任務切換也是週期性發生的。

當有一個優先級低的任務執行時,若是有優先級更高的任務就緒了,那麼只要發生了一次滴答中斷,任務就能被當即切換過去,延時只有一個滴答時鐘的時間,若是定義的時鐘週期是1ms,那麼低優先級的任務最多也就能運行1ms,而後便會強行剝奪CPU的執行權限,轉交給高優先級的任務。

因爲存在這種機制,所以便能保證UCOSII系統任務的實時性。

 

總結

 我的認爲,對於一個嵌入式操做系統而言,最核心和最重要的,即是任務調度的機制與策略,只要實現了這個功能,那麼一個嵌入式操做系統的架構也就搭建了起來,至於其餘的消息,郵箱,隊列等功能,都是在這個架構上實現增值產品。

只要深刻理解了UCOSII系統的任務調度的原理,那麼本身手動實現一個簡易的操做系統內核,彷佛也並非一件觸不可及的事情。

若是有興趣,能夠去這裏看看(https://www.zhihu.com/question/25628124)裏面有不少大神都實現了本身的操做系統。

相關文章
相關標籤/搜索