第二章:時鐘節拍

時鐘節拍

書中用了比較通俗的一句話解釋了時鐘節拍:CPU以一定的頻率進行中斷,可以看成操作系統的心跳。利用時間節拍可以做到一些任務的時間管理,延時、定時、超時檢測、時間片輪轉調度。
設問:常規的檢查每個任務的延時是否完成,在每個時鐘節拍到達的時侯要檢查每個任務?這樣比較消耗系統資源,UCOSIII給出比較好的解決辦法。。。

2.1系統節拍中斷服務程序

這裏的時鐘頻率大致設置爲10hz-1000hz,如果這個頻率太低就會導致有時候任務不能夠及時的就緒(任務的就緒靠的是每個時鐘節拍的檢測,比如始終節拍是1s則當一個任務明明過了0.5s就能處於就緒態、等待運行,但是由時鐘節拍的原因,在1s鐘的時候才被設置爲就緒態),同時這個頻率也不能太高,太高系統中斷過於頻繁,內核負擔加重。在os_cfg_app.c文件中能夠設置這個值

   #define OS_CFG_TICK_RATE_HZ   1000

每次產生時鐘節時候就會進入一個ISR(Interrupt Serive Routine),在stm32中選用的是系統滴答定時器systick,所以每次時鐘節拍到來的時候就會執行以下函數

    void SysTick_Handler(void)
   {
      OSIntEnter();//中斷嵌套層數向量加一
      OSTimeTick();
      OSIntExit();//中斷嵌套層數向量減一
   }

這裏主要就是OSTimeTick();這個函數,這個函數裏面首先執行了一個
1:鉤子函數OSTimeTickHook();
鉤子函數簡單來說就是自己要編寫的函數,寫在這裏是希望某些函數以一個時鐘節拍爲週期而執行。
若需要使用這個鉤子函數首先要把OS_CFG_APP_HOOKS_EN 這個宏置爲1。其次初始化一個指向目標函數的指針。

     void OSTimeTickHook(void)
    {
       #if OS_CFG_APP_HOOKS_EN >0u
       if(OS_AppTimeTickHookPtr!=0)
       (*OS_AppTimeTickHookPtr)();
       #endif
     }

從代碼很容易看出如果定義的宏OS_CFG_APP_HOOKS_EN不爲1那麼就會執行指向OS_AppTimeTickHookPtr()的函數。
所以當你想使用這個以一個時鐘節拍爲週期的鉤子函數(假設函數名爲My_Hook_Handler())初始化語句如下:
OS_AppTimeTickHoolPtr=(OS_APP_HOOK_TCB)My_Hook_Handler;
緊接着這個函數給時鐘階節拍任務發送了一個信號量(注意這裏不是執行時鐘節拍任務,相當於只是置了一個標誌位,後面會做解釋
2:向時鍾節拍任務OSTickTaskTCB發送信號量;
此時是系統滴答定時器執行的第二個步驟,按道理本來要執行時鐘節拍相關的任務(),但是可能這個任務裏面要做的事情比較複雜。於是UCOSIII做了一個優化處理,把時鐘節拍的處理當成一個任務,而不再ISR裏面執行.原則上ISR裏面要做時間比較短的事情。所以說如果現在有一個時間要求比較長但是又相對比較重要的時候,可以在ISR裏面發信號給當前優先級較高的任務,要處理的事情放在這個任務中,並把任務掛起,等待ISR的信號量。這樣就實現了中斷級到任務級的轉換!
3:還有一些時間片輪轉調度和定時器的相關任務,暫時不贅述。。。;

2.2節拍任務處理時間相關事務

銜接上文的ISR發出的信號量,該信號量是發送給下面這個函數:OSTickTask(void *p_arg);
這個函數調用了OSTaskSemPend();

 void OS_TickTask(void *p_parg)
 {
   OS_ERR err;
  CPU_TS  ts;
  p_arg=p_arg;//undo the warning
  whie(DEF_ON)
  {
  (void)OSTaskSePend(
  (OS_TICK)0,
  (OA_OPT)OS_OPT_PEND_BLOCKING,
  (CPU_TS*)&ts,
  (OS_ERR*)&err;
  )
  if(err==OS_ERR_NONE)//系統是否在運行
      {
      if(OSRunning==OS_STATR_OD_RUNNING)
       {
     OS_TickListUpdate();
       }
      }
  } 
 }  

一般在UCOSIII中第一個參數都是操作的對象,最後一個參數都是錯誤信息。上述代碼的OSTaskSemPend();函數就是這樣的規則。

2.2.1節拍列表更新

詳細代碼見OS_TickListUpdate(void);這個函數。

2.2.2節拍列表

設問:UcosIII怎麼能夠快速的查找到已經到期的任務?
首先UcosIII中會定義一個全局變量數組OSCfg_TickWheel[OS_CFG_TICK_WHEEL_SIZE],用於存放延時、超時檢測等全部信息,用鏈表的形式把他們都串起來,就得到了一個節拍列表Tick_List.
在這裏插入圖片描述
上圖爲OS_CfgTickWheel[OS_CFG_TICK_SIZE]這個全局變量數組的數據結構,每個元素後面有一系列TCB任務的結構體變量,這些任務是由雙向鏈表進行連接的。接下來分別對上述任務控制塊中和TickList裏面有關的元素進行介紹。
1)TickNextPtr、TickPrevPtr共同組成雙向鏈表,此雙向鏈表對應掛載在數組的某一個元素上。雙向鏈表中的越後面的任務越晚到期。
2)TickCtrMatch 該變量和OSTickCtr相匹配的時候,任務退出TickList.
3) TickRemain 任務脫離TickList鎖剩下的時鐘節拍,等於OSTickCtr -TickCtrMatch
4) TaskState 任務狀態 表徵任務處於什麼狀態,
5) TS 紀錄上一個任務的時間戳
6) PendStatus 等待的相關狀態
7) TickSpokePtr 標記了任務在全局數組裏面的哪個元素裏面
8) PendOn 描述了任務等待的對象,比如事件標誌、二值信號量、等待消息隊列

2.2.3哈希算法檢測到期任務

當一個任務到來的時候要進行超時檢測和延時檢測,內核會把這些任務插入到這個全局變量數組的各個元素中去,爲了能夠快速的檢測到期的任務,這裏採用了一個方法在任務插入進數組的時候就進行了分類。OSCtr_TickWheelSize是一個const修飾的一個變量。根據任務的TickCtrMatch對OSCtr_TickWheelSize取餘數得到該任務應該插入到數組的哪個元素裏面。每次檢查的任務最多就是全部任務的1/OSCtr_TickWheelSize這麼多個。OSCtr_TickWheelSize個數越大,檢查的次數就越小,但那個全局變量數組就會越大。這個看各個單片機自己的存儲空間而定。數據分類方法就是對數據取餘運算。
具體實現代碼如下:

              spoke=(OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch%OSTickWheelSize);
              p_spoke=&OSCfg_TickWheel[spoke];

2.3總結

CPU以一定的時間進行中斷就是時鐘節拍。當時鍾節拍到來的時候,先給時鐘節拍任務發送一個信號量(由於此時是高頻率的中斷中,所以是給的信號量而不是直接在這個中斷服務函數裏面執行時鐘節拍任務)。時鐘節拍任務收到這個信號量後更新TickList。當任務插入代TickList裏面的時候按照哈希取餘運算進行分類。餘數相同的放在特定數組的同一個元素裏面,並用雙向鏈表把這些具有相同餘數的任務串起來。同時系統會計算OSTickCtr對常數的餘數,並根據此餘數找到數組中對應的元素,並在此元素中的雙向鏈表找到對應的任務,進行判斷和相關操作。

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