1. 程式人生 > >從裸奔到RTX的使用提示

從裸奔到RTX的使用提示

RTX是實時微核心作業系統,本文涉及的部分內容同樣適合於μcos等RTOS。同時,某些內容可能是RTX特有的。
1. 跟迴圈實現的Delay說拜拜
形如這樣的Delay函式應該從程式碼中消失了:

void Delay(int n)
{
  int i = 0;
  int j = 0;
  int k = 0;
  int temp =0;

  for (i = 0; i < n; i++)
    for (j = 0; j < 2450; j++)
      for (k = 0; k < 65535; k++)
        temp++;
}


呼叫這樣的Delay,有以下壞處:
(1)延時時間無法保證。在某個CPU的某種配置下是這個時間,換個CPU或更改配置,延時的時間就變了。
(2)如果在任務中呼叫,會使較低優先順序的任務阻塞,而如果Round-Robin沒開啟的話,相同優先順序任務也被阻塞,完全發揮不出多工的優勢。
使用os_dly_wait或os_itv_wait等函式吧。
或許有人要說,我在os_sys_init之前就要用到延時,怎麼辦?那麼我要說的是,這種情況並不多見,即使有,也多半可以移到os_sys_init後面去執行。
假如實在沒有辦法,確實要在os_sys_init之前呼叫Delay,那麼請確保它不在os_sys_init之後的任務中被呼叫。
2. 任務永遠不要返回

我沒有試過用os_tsk_create去建立一個不帶__task關鍵字的任務會有什麼後果。
但是,我試過讓帶有__task關鍵字的函式返回,結果是宕機。
不知道μcos是不是這樣的。
__task關鍵字的定義在\Keil\ARM\RV31\INC\RTL.h

#define __task          __declspec(noreturn)


關於__declspec(noreturn),這裡有描述:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0348bc/CJADJEII.html
不過遺憾的是,當我顯式地返回時,編譯器並沒有給出警告(我用的警告級別是All Warnings)。
當一個任務所要做的事都做完了,要麼用os_tsk_delete_self將自己刪除,要麼無限迴圈。而且我建議使用os_tsk_delete_self,因為多餘的迴圈是有害的,壞處在第1點中的Delay中已經說明。
在使用RTX的時候,這一點要特別注意。
3. 任務棧

裸奔的時候,我們都習慣於在啟動檔案中為使用者模式和ISR模式分配足夠的呼叫棧,然後就基本上不用管它了。而且Keil會生成一個最終映像的htm檔案,提供靜態呼叫棧的最大值,可以參考。
然而RTX的每個任務棧是獨立分配的。我碰到的90%的宕機問題,都是由於任務棧的不足引起的。我的一位同事對此深有同感,他以前所在的公司曾經用過μcos。
可以在RTX_Conf_xxx.c檔案中修改系統自動分配的任務棧大小,而os_tsk_create_user和os_tsk_create_user_ex則可以指定單個任務的棧大小。
暫時還沒有發現有什麼方法可以檢視一個任務的呼叫棧大小情況,目前只能手工估算了。
4. ISR中斷的處理
正如《RL-ARM User's Guide》所建議,中斷函式應儘量短小,用isr_evt_set等函式將中斷事件告訴一個任務就可以了。提高這個任務的優先順序,那麼這個中斷就可以優先處理了。

此外,還要注意FIFO Queue Buffer的大小。按照《RL-ARM User's Guide》的說法,似乎每個isr_函式佔用4 entries。對於不呼叫isr_的中斷函式,不佔用該快取。而FIFO Queue Buffer一旦溢位,核心會呼叫system error,因此需要注意。
5. 查詢等待的問題
與硬體通訊時,經常會需要查詢等待某個狀態。很多時候,我們可以使用中斷,比如串列埠的收發。然而並不是所有狀態都能通過中斷來觸發。對於裸奔系統,只能以犧牲程式碼結構為代價使得系統的其它部分不因這個狀態的等待而阻塞。
用了作業系統之後,由於有多工,這個問題就簡單了許多。我們可以在一個任務裡面簡單地使用while迴圈來等待,哪怕這個狀態的條件永遠無法達到,系統中比它高優先順序的任務依然暢通執行。
那麼相同優先順序和低優先順序任務呢?任務中的迴圈正如第1點的Delay函式,對相同優先順序任務和低優先順序任務會有影響。
RTX提供了Round-Robin功能和os_tsk_pass函式,可以將CPU的控制權交出。比如使用os_tsk_pass,查詢等待變成這樣:

while (!IsStateReady())
  os_tsk_pass();


遺憾的是,無論是Round-Robin還是os_tsk_pass,都只能將控制權交給相同優先順序的任務,低優先順序的任務依然被阻塞。
看來解決的辦法只有一個,那就是將查詢等待的任務設為最低優先順序,並考慮將Round-Robin開啟或使用os_tsk_pass。

事實上,許多工本身是一個無限迴圈。比如一個鍵盤任務:

__task void task_key(void) {
  while (1) {
    //等待、判斷、查詢等待

    //處理鍵盤事件
  }
}


那麼,這個無限迴圈內部不管採取什麼方式去處理,都需要考慮:這個任務會不會阻塞其它任務。

RL-ARM Getting Started上有個典型設計,可參考一下:

IRQ任務和高優先順序的後臺任務完成一定功能後,必須掛起(MUST BLOCK);低優先順序的使用者任務採用round robin方式執行;空閒任務執行多餘的迴圈。