UCOSII內核代碼分析


1 UCOSII定義的關鍵數據結構

  OS_EXT  INT8U             OSIntNesting;

  OSIntNesting用於判斷當前系統是否正處於中斷處理例程中。

  OS_EXT  INT8U             OSPrioCur;

  OSPrioCur表示當前進程的優先級。

  OS_EXT  INT8U             OSPrioHighRdy;

      OSPrioHighRdy表示最高優先級任務的優先級。

  OS_EXT  OS_PRIO           OSRdyGrp;

      OSRdyGrp主要來標記可運行任務優先級除去低位3位或4位后的group bit位。例如任務的優先級為65(假設超過了系統最大的優先級超過了63,那么OS_PRIO應該有16位),由於超過了63,所以這個任務的group bit位為2^(65>>4)=2^4,也就是OSRdyGrp的第5位(從低位開始算起)為1

  OS_EXT  OS_PRIO           OSRdyTbl[OS_RDY_TBL_SIZE]; 

  准備運行的任務標記,對應位為1表示任務可以運行,否則不能。

  OS_EXT  BOOLEAN           OSRunning; 

  標志內核是否正在運行。

  OS_EXT  INT8U             OSTaskCtr; 

  任務被創建的個數。

  OS_EXT  OS_TCB           *OSTCBCur;

  指向當前正在運行任務的TCB(任務控制塊)。

  OS_EXT  OS_TCB           *OSTCBFreeList;

  空閑TCB塊指針。

  OS_EXT  OS_TCB           *OSTCBHighRdy;

  指向最高優先級任務的TCB塊。

  OS_EXT  OS_TCB           *OSTCBList;

  正在使用的TCB塊的雙向鏈表。

  OS_EXT  OS_TCB           *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];

  每個優先級對應的已創建的TCB塊。

  OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];

  系統靜態分配的一個系統中所有的TCB塊,包括正在使用的和空閑的。

typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;        //指向任務的棧頂
    struct os_tcb   *OSTCBNext;             //指向TCB鏈表中的下一個TCB塊
    struct os_tcb   *OSTCBPrev;             //指向TCB鏈表中的前一個TCB塊
    INT32U           OSTCBDly;              //任務延遲時間
    INT8U            OSTCBStat;             //任務當前狀態
    INT8U            OSTCBStatPend;         //任務懸掛狀態
    INT8U            OSTCBPrio;             //任務優先級(0表示最高優先級)
    INT8U            OSTCBX;                //與任務優先級一致group bit位(一般是OSTCBPrio的低3位)
    INT8U            OSTCBY;                //與任務優先級一致的ready table(一般是OSTCBPrio高5位)
    OS_PRIO          OSTCBBitX;             //訪問ready table的bit mask
    OS_PRIO          OSTCBBitY;             //訪問ready group的bit mask
} OS_TCB

  TCB塊是任務的描述符,描述了任務的狀態和運行時必須的信息。

2 系統初始化

      在OSInit函數中初始化系統的各個數據結構,具體如下:

      1、調用函數OS_InitMisc初始化OSIntNestingOSTaskCtrOSRunning等一些變量。

  2、調用函數OS_InitRdyList初始化准備運行的OSRdyTbl表位都為空。

  3、調用函數OS_InitTCBListOSTCBPrioTbl表的數據都初始化為0,第i個控制塊指向第i+1個控制塊,並且都加入到OSTCBFreeList指向的鏈表中,把OSTCBList初始化為空鏈表。

  4、調用函數OS_InitTaskIdle來創建一個Idle任務,該任務是優先級最低的任務,任務號為65535,優先級為63

3 創建啟動任務

  然后我們自己調用OSTaskCreate函數創建我們自己的任務,具體如下:

  1、先判斷系統當前是否處於中斷,如果是,則返回,因為當系統處於中斷時是不允許創建任務的。

  2、判斷需要創建任務的優先級是否存在其它相同優先級的任務,如果存在,則返回,即相同優先級的任務只能有一個。

  3、調用函數OSTaskStkInit初始化任務的棧。

  4、調用函數OS_TCBInit初始化任務的TCB塊。這個函數主要是從OSTCBFreeList鏈表獲取空閑的TCB塊並初始化這個控制塊,然后將這個控制塊從OSTCBFreeList鏈表中刪除,並插入到OSTCBList鏈表中。

  如果現在內核正在運行,那么調用函數OS_Sched切換任務。

  OS_Sched函數主要用來切換任務,如果當前系統不處於中斷例程中,就通過調用函數OS_SchedNew來獲取當前已經准備好的最高優先級的任務(最高優先級主要通過OSRdyGrpOSRdyTbl來計算得到),然后調用函數OS_TASK_SW來將任務切換到當前優先級最高的任務。

  OS_TASK_SW函數則調用os_cpu_a.asm文件中的匯編函數OSCtxSw來觸發PendSV異常來完成任務的切換,至於為什么要這樣做可以參考前面講的重點的PendSV

  PendSV異常的處理的偽代碼如下:

PendSVHandler:
    if (PSP != NULL) {//判斷程序的堆棧指針是否為空,為空說明這是第一個任務
        //當調用PendSVHandler()時,
        //CPU 就會自動保存xPSR、PC、LR、R12、R0-R3 寄存器到堆棧
        //保存后,CUP 的棧SP指針會切換到使用主堆棧指針MSP上
        //我們只需檢測進入棧指針PSP是否為NULL就知道是否進行任務切換
        //因此當我們第一次啟動任務是,OSStartHighRdy()就把PSP設為NULL,
        //避免系統以為已經進行任務切換
      Save R4-R11 onto task stack;   //手動保存 R4-R11
      OSTCBCur->OSTCBStkPtr = SP;    //保存進入棧指針PSP到任務控制塊
      //以便下次繼續任務運行時繼續使用原來的棧
    }     
    OSTaskSwHook();                  //此處便於我們使用鈎子函數來拓展功能
    OSPrioCur   = OSPrioHighRdy;        //獲取最高優先級就緒任務的優先級
  OSTCBCur  = OSTCBHighRdy;        //獲取最高優先級就緒任務的任務控制塊指針
  PSP        = OSTCBHighRdy->OSTCBStkPtr;   //保存進入棧指針
  Restore R4-R11 from new task stack;       //從新的棧恢復 R4-R11 寄存器
    Return from exception;                   //返回  

  PendSV異常處理代碼很簡單,就是保存現場,將當前程序的堆棧指針保存到TCB塊中,並將下一個要運行任務的棧指針更新到PSP中,同時恢復下一個運行任務的現場。

4 運行系統

  一切都已經准備就緒,那么我們就調用函數OSStart運行系統。該函數和任務切換函數OS_TASK_SW差不多,只不過它會調用函數OSStartHighRdycpuPSP初始化為0來運行我們系統中的第一個任務並將OSRunning標記為true來表示我們的內核開始運行了。

5 任務切換時機

  從OS_Sched函數和OSStartHighRdy函數可以看出,它們總是選擇當前可運行的最高優先級任務來運行,所以只有最高優先級的任務不讓出cpu,低優先級的任務永遠也不可能運行,所以發生任務切換的唯一時機就是高優先級的進行主動讓出cpu。高優先級的任務可以調用函數OSTimeDlyHMSM來讓自己延遲從而讓出cpuOSTimeDlyHMSM本質上是調用函數OSTimeDly來實現讓出cpu的操作。下面來看看這個函數的具體實現:

void  OSTimeDly (INT32U ticks)
{
    if (OSIntNesting > 0u) {                     //系統正處於中斷中
        return;
    }
    if (OSLockNesting > 0u) {                    //調度器加鎖了
        return;
    }
    if (ticks > 0u) {                            //延遲時間大於0
        OS_ENTER_CRITICAL();
        y            =  OSTCBCur->OSTCBY;        //獲取當前優先級的高位
        OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;  //清空表中對應當前任務的bit位
        if (OSRdyTbl[y] == 0u) {
            OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
        }
        OSTCBCur->OSTCBDly = ticks;              //設置當前任務的延遲時間
        OS_EXIT_CRITICAL();
        OS_Sched();                              //切換任務
    }
}

  這個函數其實就是將本任務在OSRdyTbl表和OSRdyGrp對應的標記位清0,使得后面的調度函數OS_Sched可以選擇其它優先級的任務,即當前任務讓出cpu。這個函數還做的事就是設置當前任務的延遲時間到任務的TCB塊中,這個時間會在SysTick處理函數中每個節拍的減少,直到減為0時,會將這個任務對應的bit位加入到OSRdyTbl表和OSRdyGrp中來獲取cpu

6 任務調度算法

  UCOSII涉及的調度算法比較的簡單,正如在任務切換時機里說的,只要高優先級的任務不讓出cpu,低優先級的任務永遠不可能得到cpu,調度器總是選擇系統中已經就緒的最高優先級任務運行。問題就來了,調度器怎樣確定到當前系統中任務的最高優先級是多少呢?當然最笨的方法是將OSTCBList鏈表都遍歷一遍,這樣肯定是可以的。但這樣效率是很低的,特別是系統中就緒任務比較多的時候,它的時間復雜度位O(n)n為系統中就緒任務的個數。UCOSII采用了一種比較巧妙的方法實現在O(1)的復雜度獲得這個最高優先級值。

  當一個任務就緒時,這個任務的優先級OSTCBPrio被拆分成兩個部分,低4位的值存放在任務控制塊的OSTCBX中,高4位存放在OSTCBY中。然后將2^OSTCBX的值存放在控制塊的OSTCBBitX中,將2^OSTCBY存放在OSTCBBitY中。通過下面方法更新OSRdyTbl表和OSRdyGrp變量。

  OSRdyGrp |= ptcb->OSTCBBitY;

  OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

  OSRdyGrp存放了當前系統中所有就緒任務優先級高4位表示的bit位,OSRdyTbl表其實就是一個位圖,用來標記系統中所有就緒任務的優先級,每一個優先級對應一個bit位。

有了這些信息,怎樣來獲取當前就緒任務中最高優先級的值呢?系統維護了一張表:

INT8U  const  OSUnMapTbl[256] = {

    0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F*/

    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F*/

    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F*/

    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F*/

    7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F*/

    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF*/

    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF*/

    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF*/

    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF*/

};

  通過函數OS_SchedNew來獲取最高的優先級值:

static  void  OS_SchedNew (void)
{
    INT8U     y;
    OS_PRIO  *ptbl;
    if ((OSRdyGrp & 0xFFu) != 0u) {
        y = OSUnMapTbl[OSRdyGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &OSRdyTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
    } else {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
    }
}

  這個函數先根據OSRdyGrp值這張表來獲得最高優先級的高4位的值y,然后通過OSRdyTbl[y]來獲得低4位的值x,這樣最終的值就是y<<4 + x。為什么這樣就可以得到最高優先級呢?現在來看一個例子:假設OSRdyGrp = 10,對應的二進制值為1010,那么就緒任務中優先級前4位最小的值為1,由於OSRdyGrp的二進制值第二位為1(這個可以通過前面計算OSRdyGrp的方法來理解)。然后通過這個1,我們可以知道最高優先級在OSRdyTbl對應的bit位在OSRdyTbl[1]中,然后通過OSRdyTbl[1]來查表OSUnMapTbl同樣可以獲得最高優先級的低4位的值。然后合並這兩個值就可以獲得最高優先級的值了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM