官术网_书友最值得收藏!

第1章 實(shí)時(shí)操作系統(tǒng)及μC/OS-III簡介

本章首先介紹從單片機(jī)應(yīng)用程序框架引入的實(shí)時(shí)操作系統(tǒng),接著介紹怎么學(xué)習(xí)μC/OS-III源碼,然后簡單介紹μC/OS-III的文件結(jié)構(gòu)和數(shù)據(jù)結(jié)構(gòu)、任務(wù)、內(nèi)核對象、常見代碼段等,為后面的章節(jié)做鋪墊。有些概念剛開始接觸可能不是很容易理解,可以先忽略它們繼續(xù)閱讀下去,后面就會有“柳暗花明又一村”的感覺。

1.1 單片機(jī)應(yīng)用程序框架

1.1.1 前后臺系統(tǒng)

在單片機(jī)應(yīng)用程序中,最常用的就是前后臺系統(tǒng),通常由一個(gè)大循環(huán)和中斷組成,大循環(huán)是后臺,中斷是前臺。前后臺系統(tǒng)的程序流程比較簡單,但是,當(dāng)芯片處理的事情越來越多、越來越復(fù)雜的時(shí)候,前后臺系統(tǒng)并不能保證實(shí)時(shí)性。這在一些協(xié)議棧等實(shí)時(shí)性要求高的場合是不允許的,這時(shí)編寫程序的人就要重新設(shè)計(jì)程序框架或者使用本書介紹的實(shí)時(shí)操作系統(tǒng)幫忙進(jìn)行合理的任務(wù)調(diào)度,讓緊急的事務(wù)優(yōu)先執(zhí)行。

順序執(zhí)行的前后臺系統(tǒng)的程序如代碼清單1-1所示。

代碼清單1-1 前后臺系統(tǒng)程序框架


    main()
    {
        Peripheral_Init();           //外設(shè)初始化
        while(1)
        {
            process();               //程序主要處理部分
        }
    }

除了簡單的順序執(zhí)行那樣的前后臺系統(tǒng)程序外,還可以采用時(shí)間片輪詢法加以改進(jìn)。

時(shí)間片輪詢法是多個(gè)任務(wù)以一定的頻率執(zhí)行,就像多個(gè)任務(wù)一起無干擾地執(zhí)行一樣。下面看看一個(gè)簡單的時(shí)間片輪詢法的代碼框架是怎么實(shí)現(xiàn)的。

本次介紹使用時(shí)間片輪詢法創(chuàng)建多個(gè)任務(wù)并一起運(yùn)行,LED1任務(wù)每500ms轉(zhuǎn)換一次狀態(tài), LED2任務(wù)每1s轉(zhuǎn)換一次狀態(tài),LED3任務(wù)每2s轉(zhuǎn)換一次狀態(tài),實(shí)現(xiàn)起來非常簡單。首先看看main函數(shù)的流程,時(shí)間片輪詢法中任務(wù)的編寫方式如代碼清單1-2所示,并且放入while(1)循環(huán)中。

代碼清單1-2 任務(wù)編寫方式


        01     if(Task_delay[i]==0)
        02     {
        03       Task(i);
        04       Task_Delay[i]=XXX;
        05     }

Task_delay是一個(gè)數(shù)組,有多少個(gè)任務(wù)就有多少個(gè)數(shù)組元素,我們在例程1-1中設(shè)置為3個(gè),但一開始數(shù)組元素都設(shè)置為0。第一次判斷肯定會進(jìn)入if判斷里面執(zhí)行任務(wù)Task(i)的代碼,執(zhí)行完這個(gè)任務(wù)之后,Task_Delay[i]變成了XXXX。這里假設(shè)XXXX=1000,下次判斷如果Task_Delay[i]不為0,就不會執(zhí)行任務(wù)Task(i)。如果使用一個(gè)定時(shí)器每1ms將Task_Delay[i]減1,那么在1000ms之后的判斷條件又成立了,并且任務(wù)執(zhí)行的間隔是1000ms,如此循環(huán)往復(fù),就可以讓任務(wù)Task(i)的執(zhí)行頻率大致為1Hz。在while(1)這個(gè)循環(huán)中,還可以創(chuàng)建其他任務(wù),并且可以通過設(shè)置Task_delay[j]的大小來決定任務(wù)執(zhí)行的頻率。想要理解好上面的內(nèi)容,需要忽略任務(wù)運(yùn)行的時(shí)間、定時(shí)器中斷服務(wù)程序執(zhí)行的時(shí)間。只要不在任務(wù)代碼里添加太長的延時(shí)、執(zhí)行時(shí)間長的任務(wù),那么任務(wù)的執(zhí)行頻率跟理想化的不會相差太遠(yuǎn),而導(dǎo)致前后臺系統(tǒng)實(shí)時(shí)性差的也正是這些原因。

        01 /*****************************************************************************
        02 * 函數(shù)名稱: main
        03 * 輸入?yún)?shù): void
        04 * 輸出參數(shù): int
        05 * 功    能: 程序入口
        06 * 作    者: 驍龍Lyc
        07 * 舉    例: 無
        08 ***************************************************************************/
        09 int main(void)
        10 {
        11   /*LED燈管腳的配置*/
        12   LED_ Config();
        13
        14   /*這里中斷頻率配置為1000Hz*/
        15   Interrupte _Config (72000);
        16
        17   /* Infinite loop */
        18   while (1)
        19   {
        20     if(Task_Delay[0]==0)
        21     {
        22       LED1_TOGGLE;
        23
        24       Task_Delay[0]=500;
        25     }
        26
        27     if(Task_Delay[1]==0)
        28     {
        29       LED2_TOGGLE;
        30
        31       Task_Delay[1]=1000;
        32     }
        33
        34     if(Task_Delay[2]==0)
        35     {
        36       LED3_TOGGLE;
        37
        38       Task_Delay[2]=2000;
        39     }
        40   }
        41 }

前面介紹了中斷服務(wù)程序會在每次中斷時(shí)將Task_delay這個(gè)數(shù)組的所有元素執(zhí)行減1操作。接下來看看嘀嗒定時(shí)器的中斷服務(wù)程序。

    01 /*****************************************************************************
    02 * 函數(shù)名稱: Interrupte_Handler
    03 * 輸入?yún)?shù): void
    04 * 輸出參數(shù): void
    05 * 功    能: 時(shí)間片輪詢法中定時(shí)器的中斷服務(wù)程序
    06 * 作    者: 驍龍Lyc
    07 * 舉    例: 1ms,1次中斷
    08 ***************************************************************************/
    09 void Interrupte_Handler(void)
    10 {
    11   unsigned char i;
    12   for(i=0;i<NumOfTask;i++)
    13   {
    14     if(Task_Delay[i])
    15     {
    16       Task_Delay[i]--;
    17     }
    18   }
    19 }

中斷服務(wù)程序其實(shí)很容易理解,在for循環(huán)中將NumOfTask個(gè)任務(wù)對應(yīng)于數(shù)組Task_Delay中的元素都減1,但是在減1之前要判斷元素是否為0,如果為0就停止減1操作。通過上述代碼,就簡單地實(shí)現(xiàn)了時(shí)間片輪詢法,可以在此基礎(chǔ)上任意地添加任務(wù)。想對一般程序來說,這段代碼的實(shí)時(shí)性比搶占式實(shí)時(shí)操作系統(tǒng)差。宏觀上,時(shí)間片輪詢法的各個(gè)任務(wù)也不會互相影響,好似多個(gè)任務(wù)一起相安無事地在進(jìn)行著,實(shí)際上,若任務(wù)很多或者任務(wù)執(zhí)行的時(shí)間比較長,還是會影響任務(wù)的執(zhí)行頻率。比如其中一個(gè)任務(wù)霸占CPU的時(shí)間足1s,而其他任務(wù)在這1s內(nèi)完全是不執(zhí)行的,即執(zhí)行頻率變?yōu)?,這在某些系統(tǒng)中是不允許的。

1.1.2 嵌入式實(shí)時(shí)操作系統(tǒng)

前后臺系統(tǒng)就像一個(gè)人從頭到尾去做一件事情,偶爾還被叫去處理一些突發(fā)事件(中斷)。編寫程序的人可完全掌握程序的運(yùn)行流程,但是當(dāng)事情多的時(shí)候可能無法把所有的事情都做好。而引入實(shí)時(shí)操作系統(tǒng)就像是請了一個(gè)管理團(tuán)隊(duì),這個(gè)管理團(tuán)隊(duì)會幫你協(xié)調(diào)好這些事情,這樣在處理事情的時(shí)候就會顯得井井有條,提高了CPU處理復(fù)雜事情的能力。但是請一個(gè)管理團(tuán)隊(duì)這個(gè)過程會占用一些資源,比如請管理人員的費(fèi)用等,若公司的運(yùn)行效率提高了,那么花這些費(fèi)用還是值得的。在實(shí)時(shí)操作系統(tǒng)中這就對應(yīng)著增加CPU的負(fù)擔(dān)、占用芯片的內(nèi)存。

實(shí)時(shí)操作系統(tǒng)通過一系列軟件管理讓一個(gè)CPU形成多個(gè)線程,就像有多個(gè)CPU在一起執(zhí)行一樣。至于這是怎么實(shí)現(xiàn)的,就是本書要講解的重點(diǎn)。

實(shí)時(shí)操作系統(tǒng)中比較重要的是實(shí)時(shí)性,即要求系統(tǒng)有比較快的響應(yīng)速度和執(zhí)行速度。在時(shí)間片輪詢法中,任務(wù)是一個(gè)一個(gè)執(zhí)行的,如果其中一個(gè)任務(wù)的執(zhí)行時(shí)間過長,那么會影響到其他任務(wù)的執(zhí)行,這就不適合實(shí)時(shí)性要求高的系統(tǒng)。為了應(yīng)對更復(fù)雜的程序開發(fā),嵌入式實(shí)時(shí)操作系統(tǒng)應(yīng)運(yùn)而生。不管當(dāng)前任務(wù)是否放棄使用CPU,嵌入式系統(tǒng)中的任務(wù)可以隨時(shí)切換到優(yōu)先級比較高的任務(wù)。所以,只要將任務(wù)的優(yōu)先級設(shè)置得足夠高,這個(gè)任務(wù)的實(shí)時(shí)性就很好。實(shí)時(shí)操作系統(tǒng)可以分為硬實(shí)時(shí)操作系統(tǒng)和軟實(shí)時(shí)操作系統(tǒng)。硬實(shí)時(shí)操作系統(tǒng)要求在規(guī)定的時(shí)間內(nèi)必須完成任務(wù),而軟實(shí)時(shí)操作系統(tǒng)要求越快完成越好。

實(shí)時(shí)操作系統(tǒng)又可分為不可剝奪型操作系統(tǒng)和可剝奪型操作系統(tǒng)。不可剝奪型內(nèi)核只有在當(dāng)前線程放棄使用CPU之后,其他的線程才可以占用CPU。而可剝奪型內(nèi)核只要存在有更高優(yōu)先級的線程就緒,低優(yōu)先級的線程就會被打斷,高優(yōu)先級線程就占有CPU。μC/OS-III屬于可剝奪型內(nèi)核。

在可剝奪型操作系統(tǒng)中,任務(wù)代碼隨時(shí)都可能被打斷而派去執(zhí)行另外的任務(wù),這時(shí)任務(wù)可能正在執(zhí)行往一個(gè)變量寫入數(shù)值的過程,而這個(gè)過程是需要多個(gè)指令的。如果寫入過程只進(jìn)行到一半,任務(wù)就被打斷,而且這個(gè)變量有可能在中斷或者其他的任務(wù)中被讀取,那么讀取到的這個(gè)變量將是一個(gè)錯(cuò)誤的值。所以寫入的這個(gè)過程就不能被中斷或者被其他任務(wù)搶占。這種類似主要的程序段我們稱為臨界段。臨界段是程序不能被中斷或者其他優(yōu)先級的任務(wù)打斷的程序段。一般來說,有兩種方式可以保護(hù)臨界段:一種是關(guān)中斷,另一種是鎖調(diào)度器。其中,鎖調(diào)度器可以防止其他任務(wù)訪問臨界段代碼,但是不能防止中斷訪問臨界段代碼。

注意:

1)變量的讀取如果只在一個(gè)任務(wù)的上下文中,那么就不需要保護(hù)這個(gè)變量的讀/寫過程。

2)如果變量只可能在其他的任務(wù)中讀取,而不可能被中斷讀取,那么可以采用鎖調(diào)度器的方式,避免了關(guān)中斷,節(jié)省了關(guān)中斷的時(shí)間。

因?yàn)榫€程可以隨意被高優(yōu)先級打斷,所以需要保存當(dāng)前線程運(yùn)行的上下文,以及注意臨界段的保護(hù)。因此可剝奪型內(nèi)核實(shí)現(xiàn)起來更困難。

1.2 如何使用和學(xué)習(xí)μC/OS-III源碼

好的學(xué)習(xí)方法事半功倍。本節(jié)將結(jié)合筆者學(xué)習(xí)μC/OS-III源碼的經(jīng)驗(yàn)來介紹下如何學(xué)習(xí)μC/OS-III源碼。

1. 查閱文檔,動手實(shí)踐

在學(xué)習(xí)源碼之前,還是先讓自己對μC/OS-III有個(gè)整體的感知,這是本書對讀者的最低要求。一般來說,開發(fā)板例程會提供μC/OS-III直接可用的程序,你可以在開發(fā)板例程的基礎(chǔ)上照貓畫虎寫幾個(gè)任務(wù)并運(yùn)行之。或許你不知道任務(wù)的奇怪參數(shù)是什么意思,但這也絲毫不會影響你編寫簡單的程序。創(chuàng)建完任務(wù)后最好還使用一些內(nèi)核對象,如信號量、消息隊(duì)列、定時(shí)器等,這些也可以照貓畫虎,但也可以自己照著μC/OS-III的手冊和函數(shù)注釋來使用這些內(nèi)核對象。下面舉例說明筆者一開始是怎么使用定時(shí)器的。筆者剛開始接觸μC/OS-III,是在做一個(gè)比較大的項(xiàng)目,需要用到多個(gè)定時(shí)器,了解用硬件定時(shí)器太麻煩,而μC/OS-III能提供軟件定時(shí)器。首先到定時(shí)器的文件中看看有什么函數(shù)可以調(diào)用,如圖1-1所示。

在函數(shù)列表中可看到一個(gè)OSTmrCreate函數(shù)(見圖1-1),無論是變量名、宏定義還是函數(shù)名,μC/OS-III命令都十分規(guī)范,讓人很容易見名知意,而通過OSTmrCreate這個(gè)函數(shù)名稱就可以知道這是一個(gè)創(chuàng)建定時(shí)器的函數(shù)。到底要怎么調(diào)用這個(gè)函數(shù)呢?μC/OS-III函數(shù)注釋得非常詳細(xì),如圖1-2所示。

圖1-1 OSTmrCreate函數(shù)的位置

找到OSTmrCreate函數(shù)的定義處,函數(shù)前面就有詳細(xì)的注釋。通過注釋可以知道函數(shù)的用途,應(yīng)該輸入什么樣的參數(shù),會有什么返回值,有什么注意事項(xiàng)等,可以根據(jù)這些提示來調(diào)用函數(shù)。注釋不可能面面俱到,讀完注釋后,在調(diào)用過程中可能還會有很多不能理解的地方,比如定時(shí)的單位是什么。這要靠你對μC/OS-III的理解,可以查閱一些相關(guān)書籍,本書也會講解μC/OS-III部分函數(shù)的使用。為什么是部分呢?在看源碼注釋的時(shí)候,我們會看到一些函數(shù)的注釋中包含這樣的注釋:

    * Note(s)     : 1) This function is INTERNAL to uC/OS-III and your application
    MUST NOT call it.

圖1-2 OSTmrCreate函數(shù)注釋

如果函數(shù)的注釋中有這樣的注釋,說明在使用μC/OS-III的時(shí)候不能調(diào)用他們,這些函數(shù)一般都是內(nèi)核操作相關(guān)的,如果隨便調(diào)用可能導(dǎo)致意想不到的錯(cuò)誤,最好還是不要調(diào)用他們,所以本書也就不講解這些函數(shù)是怎么使用的。代碼清單1-3展示了怎么調(diào)用函數(shù)OSTmrCreate。

代碼清單1-3 函數(shù)OSTmrCreate的調(diào)用


        1 OSTmrCreate ((OS_TMR               *)&TmrOfKey,
        2                              (CPU_CHAR             *)"TmrOfKey",
        3                              (OS_TICK               )0,
        4                              (OS_TICK               )1,
        5                              (OS_OPT                )OS_OPT_TMR_PERIODIC,
        6                              (OS_TMR_CALLBACK_PTR  )cbTimerOfKey,
        7                              (void                 *)0,
        8                              (OS_ERR               *)&err);

本書是在筆者閱讀μC/OS-III源碼和使用μC/OS-III的基礎(chǔ)上進(jìn)行介紹的。只翻譯注釋來講解每個(gè)函數(shù)怎么使用是沒有多少意義的。我們在解析每個(gè)源碼之前必須先講解怎么使用函數(shù)。看函數(shù)源碼解析的時(shí)候最好先按照本書的編排方式看看函數(shù)是怎么使用的,函數(shù)的使用也可以看成是源碼解析的一部分,因?yàn)楹瘮?shù)的使用會介紹函數(shù)的功能、函數(shù)的參數(shù)等,這些都是理解函數(shù)必不可少的信息。

用OSTmrCreate創(chuàng)建完定時(shí)器后,定時(shí)器是否就可以運(yùn)行了呢?實(shí)踐證明還是不可以。當(dāng)時(shí)筆者接著查看了定時(shí)器文件的函數(shù),又看到另一個(gè)函數(shù)OSTmrStart,可能還要調(diào)用這個(gè)函數(shù)來開啟定時(shí)器才能運(yùn)行,如圖1-3所示。這樣調(diào)用函數(shù)之后發(fā)現(xiàn)定時(shí)器就真的運(yùn)行了。

由以上過程可以看到,調(diào)用μC/OS-III函數(shù)的過程還是挺方便的。調(diào)用函數(shù)其實(shí)不難,但要結(jié)合注釋或者一些書籍的講解才能真正用好這些函數(shù)。本書配套了每個(gè)內(nèi)核對象的使用例程,注釋也十分詳細(xì),跟著例程照貓畫虎地使用這些內(nèi)核對象也是一種不錯(cuò)的選擇。

或許有人會想,如果已經(jīng)有相應(yīng)移植好的工程,那么在上手μC/OS-III之前就先進(jìn)行移植。這是完全沒有必要的,因?yàn)楣俜綆臀覀儗⒋蟛糠枝藽/OS-III移值到了CPU上,加上移植的過程并不簡單,移植需要建立在對CPU內(nèi)核和μC/OS-III有足夠理解的基礎(chǔ)上。本書將移植μC/OS-III放在最后介紹,也可以說是因?yàn)棣藽/OS-III的移植是最難的。

2.循環(huán)漸近,深入了解

以上介紹了怎么用好μC/OS-III,接下來介紹怎么深入學(xué)習(xí)μC/OS-III源碼。最開始編寫本書的時(shí)候,筆者在想, μC/OS-III中那么多內(nèi)容究竟應(yīng)該按照什么樣的順序講解才是最好的。一開始最好挑最容易的,與整個(gè)μC/OS-III聯(lián)系最少的那些源碼進(jìn)行講解。本書講解的順序相對來說是比較好的。所以,首先要對嵌入式操作系統(tǒng)和μC/OS-III有一個(gè)大致的了解。接著了解比較容易的時(shí)鐘節(jié)拍和定時(shí)器、多值信號量。由于多值信號量跟二值信號量、消息隊(duì)列、事件標(biāo)志聯(lián)系比較緊密,所以將這些內(nèi)容放在一起介紹。最后講解任務(wù)切換、任務(wù)相關(guān)等內(nèi)容。每學(xué)習(xí)一個(gè)內(nèi)核對象之前,最好先了解內(nèi)核對象的數(shù)據(jù)結(jié)構(gòu)。

圖1-3 OSTmrStart函數(shù)的位置

1.3 μC/OS-III文件結(jié)構(gòu)簡介

μC/OS-III文件將按照由底層到上層的排列順序進(jìn)行整理。下面根據(jù)圖1-4中的序號對μC/OS-III的文件結(jié)構(gòu)進(jìn)行介紹。

①配置文件。通過配置文件定義宏的值就可以輕易地裁剪其他不用的代碼。

②應(yīng)用程序。應(yīng)用程序包含任務(wù)的定義和聲明,用戶主要編寫的是這部分代碼。

③μC/OS-III與CPU無關(guān)代碼。這部分代碼與CPU無關(guān),可以不做修改移植到任何CPU,本書主要講解這部分代碼。

④庫。這部分主要是底層函數(shù)庫,比如字符串的一些常規(guī)操作、一些通常的數(shù)學(xué)計(jì)算等。

⑤μC/OS-III與移植相關(guān)代碼,如果讀者想要移植μC/OS-III到不同平臺上,那么可以根據(jù)CPU來修改這部分代碼。

⑥μC/OS-III與CPU相關(guān)代碼。

圖1-4 μC/OS-III文件結(jié)構(gòu)

從圖1-4中可以看出μC/OS-III文件結(jié)構(gòu)層次分明,我們僅需修改⑤、⑥這兩部分的代碼即可,直到不同的CPU。

1.4 μC/OS-III數(shù)據(jù)結(jié)構(gòu)簡介

學(xué)習(xí)OS(操作系統(tǒng)),很重要的也是很基礎(chǔ)的就是要先了解它里面復(fù)雜繁多的數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)結(jié)構(gòu)是程序操作的對象,操作的過程都建立在這些數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上面,操作對象搞明白了,操作的過程也很容易懂。不管學(xué)習(xí)什么OS的源碼,筆者建議大家一定要在前面花些時(shí)間去了解它的數(shù)據(jù)結(jié)構(gòu),并做做筆記、畫畫圖。

如果讀者自學(xué)μC/OS-III,則可能會被μC/OS-III中“指來指去”的指針搞暈。為什么要搞這么復(fù)雜的數(shù)據(jù)結(jié)構(gòu)呢?數(shù)據(jù)結(jié)構(gòu)雖然復(fù)雜,但是操作卻容易,在函數(shù)的入口只要輸入一個(gè)簡單的變量就可以找到很多相關(guān)的變量。同時(shí),設(shè)計(jì)好的數(shù)據(jù)結(jié)構(gòu)也方便程序的編寫。如果讀者還沒有學(xué)過數(shù)據(jù)結(jié)構(gòu),建議同時(shí)看看《大話數(shù)據(jù)結(jié)構(gòu)》這本書。對于本書來講,只要了解前面的線性表章節(jié)就可。本書后面介紹內(nèi)核對象的章節(jié)都會講解相關(guān)的數(shù)據(jù)結(jié)構(gòu)。讀者要是清楚這些數(shù)據(jù)結(jié)構(gòu),那就在后面講到這些相關(guān)變量操作的時(shí)候再回來看看那些圖片。筆者也沒有記住多少內(nèi)容,只是看名字就大概知道它們的作用以及它們之間的關(guān)系。圖片可以讓大家更好地理解數(shù)據(jù)結(jié)構(gòu)的關(guān)系,所以本書筆者精心制作了多幅圖片來講解它們之間的關(guān)系。下面以節(jié)拍列表的數(shù)據(jù)結(jié)構(gòu)圖來講解應(yīng)該怎么看這些圖片(見圖1-5)。

圖1-5 TickList數(shù)據(jù)結(jié)構(gòu)

第一眼看圖1-5,讀者可能有點(diǎn)看不明白。這種圖在本書中很常見,也常見于數(shù)據(jù)結(jié)構(gòu)的書,本書用這樣的圖來展示出μC/OS-III中的各種數(shù)據(jù)結(jié)構(gòu)。從圖1-5中首先可以看到由幾個(gè)小方格組成一個(gè)大方格。大方格表示一個(gè)結(jié)構(gòu)體類型,大方格上面的名稱就是相應(yīng)的結(jié)構(gòu)體類型的名稱;小方格是大方格的成員,小方格里面的名稱表示成員的名稱。大方格左側(cè)是具體的結(jié)構(gòu)體變量的名稱,如圖1-5左上角中的OSCfgTickWheel[0]是一個(gè)OS_TICK_SPOKE類型的結(jié)構(gòu)體,結(jié)構(gòu)體中有三個(gè)成員,分別是FirstPtr、NbrEntries、NbrEntrMax。Entry表示條目,記載數(shù)量的變量名經(jīng)常有這個(gè)單詞。Nbr是number的縮寫,μC/OS-III中有很多變量,千萬不要死記硬背,而要根據(jù)其命名和習(xí)慣來知道它們代表什么,這點(diǎn)對理解源碼也是很有幫助的。

從圖1-5的右邊可以看到幾個(gè)由單向箭頭組成的雙向箭頭,雙向箭頭表示雙向鏈表。值得注意的是,雖然TickNextPtr在圖中看起來是指向下一個(gè)結(jié)構(gòu)體的TickNextPtr,但實(shí)際上是TickNextPtr存放下一個(gè)結(jié)構(gòu)體的地址。如果不知道雙向鏈表的含義,請參閱數(shù)據(jù)結(jié)構(gòu)相關(guān)的書籍。

從圖1-5中還可以看到有很多個(gè)箭頭,箭頭代表指向其他變量的指針,如圖1-5中最上方的一個(gè)箭頭表示的就是OSCfg_TickWheel[0](結(jié)構(gòu)體)的成員FirstPtr存放的是OS_TCB類型的數(shù)據(jù)。

后面若有相關(guān)的圖,也是按照上面的解釋來解讀。

1.5 任務(wù)

在μC/OS-III的管理下,可以創(chuàng)建多個(gè)任務(wù),并讓它們看起來是各自有一個(gè)CPU在并發(fā)運(yùn)行。例如在計(jì)算機(jī)上,我們可以一邊瀏覽網(wǎng)頁,一邊聽歌,這也得虧操作系統(tǒng)的管理。任務(wù)一般都是死循環(huán),如果只想任務(wù)運(yùn)行一次,那么可以在執(zhí)行完任務(wù)后將其刪除。OS_TCB結(jié)構(gòu)體類型可以定義任務(wù)控制塊,每個(gè)任務(wù)都會有這樣一個(gè)結(jié)構(gòu)體變量,里面包含任務(wù)的各種信息,包括優(yōu)先級、任務(wù)的狀態(tài)、堆棧的基地址、任務(wù)名稱等。OS_TCB為每個(gè)任務(wù)定義的任務(wù)控制塊可以看成是任務(wù)的“身份證”,“身份證”包含任務(wù)的各種信息。

任務(wù)的狀態(tài)

理解好任務(wù)的幾個(gè)狀態(tài)十分重要,內(nèi)核中所有的程序幾乎都涉及這些狀態(tài)的轉(zhuǎn)化。任務(wù)將其狀態(tài)保存在任務(wù)控制塊的元素TaskState中。

1)OS_TASK_STATE_RDY:就緒狀態(tài)。μC/OS-III可能有多個(gè)任務(wù)處于就緒狀態(tài),但只能是其中優(yōu)先級最高的任務(wù)占用CPU,因?yàn)镃PU只有一個(gè),不可能多個(gè)任務(wù)一起并行運(yùn)行。運(yùn)行的任務(wù)是就緒狀態(tài)。創(chuàng)建任務(wù)完成的時(shí)候也是就緒狀態(tài)。

2)OS_TASK_STATE_DLY:延時(shí)狀態(tài)。用官方的延時(shí)函數(shù)(OSTimeDly/OSTimeDlyHMSM)將其設(shè)置為這種狀態(tài),這時(shí)任務(wù)會放棄CPU讓其他就緒任務(wù)中優(yōu)先級最高的任務(wù)運(yùn)行,如果沒有任務(wù)就緒,系統(tǒng)就會運(yùn)行優(yōu)先級最低的空閑任務(wù)——這其實(shí)就是不斷地給變量做加法的任務(wù)。

3)OS_TASK_STATE_PEND:任務(wù)在等待某些事情的發(fā)生,比如等待其他任務(wù)釋放資源后發(fā)送一個(gè)信號量來讓任務(wù)運(yùn)行,也可以是其他的任務(wù)發(fā)送消息過來等。將其設(shè)置為這種狀態(tài)的一般都是名字中含有Pend的內(nèi)核函數(shù),比如OSQPend、OSSemPend等。

4)OS_TASK_STATE_PEND_TIMEOUT:也是任務(wù)在等待某些事件的發(fā)生,不過這是要經(jīng)過超時(shí)檢測的,一旦任務(wù)調(diào)用等待函數(shù)OSQPend、OSSemPend等將任務(wù)處于等待狀態(tài),就要設(shè)置超時(shí)的時(shí)間。如果超時(shí)時(shí)間設(shè)置為0,那么任務(wù)就是OS_TASK_STATE_PEND,即無限期地等下去,直到事件發(fā)生。如果超時(shí)時(shí)間設(shè)置為N(N>0),設(shè)置為等待后的時(shí)間N內(nèi)任務(wù)狀態(tài)就是OS_TASK_STATE_PEND_TIMEOUT,在等待N個(gè)系統(tǒng)節(jié)拍后事件還是沒有發(fā)生就會退出等待狀態(tài)而轉(zhuǎn)為就緒狀態(tài)。

5)OS_TASK_STATE_SUSPENDED:掛起狀態(tài),可以理解為強(qiáng)行暫停一個(gè)任務(wù)。

6)OS_TASK_STATE_DLY_SUSPENDED:這種情況就是任務(wù)自己先產(chǎn)生一個(gè)延時(shí),延時(shí)沒有結(jié)束的時(shí)候又被其他的任務(wù)掛起。注意,不是掛起后又被延時(shí),因?yàn)閽炱鸬臅r(shí)候任務(wù)已經(jīng)被剝奪了CPU的使用權(quán),而延時(shí)的時(shí)候只能自己延時(shí)自己。需要這兩種狀態(tài)都解除才可以就緒。

7)OS_TASK_STATE_PEND_SUSPENDED:這種情況也是任務(wù)自己先等待一個(gè)事件的發(fā)生,還沒有等到事件發(fā)生就又被掛起。需要兩種狀態(tài)都解除才可以就緒。

8)OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:與上面的狀態(tài)一樣,只是加了一個(gè)超時(shí)檢測。需要兩種狀態(tài)都解除才可以就緒。

9)OS_TASK_STATE_DEL:任務(wù)被刪除后的狀態(tài)。任務(wù)被刪除后將不再運(yùn)行,要讓其恢復(fù)運(yùn)行只能重新創(chuàng)建任務(wù)。

1.6 內(nèi)核對象簡介

內(nèi)核對象有很大一部分是為任務(wù)服務(wù)的,比如想在兩個(gè)任務(wù)之間傳遞數(shù)據(jù)就可以采用消息隊(duì)列。任務(wù)之間是相互獨(dú)立的,它們之間的“溝通”是通過內(nèi)核來完成的。所有的內(nèi)核對象包括任務(wù)、堆棧、信號量、事件標(biāo)志組、消息隊(duì)列、消息、互斥信號量、內(nèi)存分區(qū)、軟件定時(shí)器等。

1.6.1 信號量

這里的信號量主要是指多值信號量。多值信號量包含兩項(xiàng)功能:一項(xiàng)是管理資源,一項(xiàng)是標(biāo)志事件的發(fā)生。管理資源的一個(gè)很通俗的例子就是停車場,比如停車場開始的停車位有10個(gè),每用掉一個(gè)就將停車位剩余數(shù)量減1,這時(shí)信號量就相當(dāng)于停車場外剩余停車位的個(gè)數(shù),這里管理的資源就是停車位。當(dāng)停車位為0時(shí)就不可以將車開進(jìn)停車場。想象如果停車場外沒有這個(gè)標(biāo)志或者沒有人充當(dāng)這個(gè)標(biāo)志來管理,那么停車場在滿的情況下外面的車還是不斷地開進(jìn)去,最后將導(dǎo)致整個(gè)停車場癱瘓。管理的資源如內(nèi)存空間、I/O口等如果沒有管理也會出錯(cuò)。下面以單片機(jī)內(nèi)部的串口為例進(jìn)行介紹。如果串口使用過程中被其他任務(wù)占用,那么輸出的結(jié)果將不倫不類,如同一個(gè)句子寫一半后接著另外句子的開頭。這也說明芯片的外設(shè)在搶占式實(shí)時(shí)操作系統(tǒng)的管理下,如果在不同的任務(wù)中被使用,則需要用信號量保護(hù)起來。操作系統(tǒng)給編程帶來便利的同時(shí)也帶來了額外需要考慮的問題。信號量的另一項(xiàng)功能是標(biāo)志事情的發(fā)生,這時(shí)初始值要設(shè)置為0,表示沒有發(fā)生事件。若程序有時(shí)候想讓兩件事情同步,就可以先讓信號量作為同步的中介。第一個(gè)任務(wù)的一個(gè)事件先發(fā)生后,想等待另外一個(gè)任務(wù)的另外一個(gè)事件發(fā)生,就可以先等待(等待的英文為pend)一個(gè)信號量。等待的事件發(fā)生后調(diào)用OS內(nèi)部提交(提交的英文為post)信號量的函數(shù),函數(shù)就會把等待信號量的任務(wù)解除等待,并置于就緒隊(duì)列。

1.6.2 事件標(biāo)志組

實(shí)際上,信號量的第二項(xiàng)功能才是事件標(biāo)志組經(jīng)常干的事情。事件標(biāo)志組可以用來標(biāo)志多個(gè)事件的發(fā)生,同信號量一樣,也可以進(jìn)行post和pend操作。事件標(biāo)志組可以設(shè)置多個(gè)位,最大的位數(shù)由數(shù)據(jù)類型OS_FLAGS決定,在我們的例程中是32位,代表多個(gè)事件的情況。其他的任務(wù)可以等待某些位被置1或者清0,然后允許任務(wù)執(zhí)行,這里時(shí)間標(biāo)志組充當(dāng)了中間的媒介。一個(gè)現(xiàn)實(shí)的例子就是鍵盤的組合操作,通過事件標(biāo)志就可以很容易設(shè)置組合鍵。任務(wù)先設(shè)置一個(gè)兩個(gè)位都被置1的等待事件,然后將這兩個(gè)位置0,當(dāng)一個(gè)按鍵被按下時(shí)就post這個(gè)按鍵相應(yīng)位置1的情況。若兩個(gè)按鍵都被按下,那么兩個(gè)位都被置1,等待這兩個(gè)事件發(fā)生的任務(wù)不再被阻塞,繼續(xù)執(zhí)行要執(zhí)行的內(nèi)容。事件標(biāo)志組聽起來可能有點(diǎn)抽象,讀者可能不知道具體為何物,這是很正常的,保持好奇心去閱讀就好。

1.6.3 消息隊(duì)列

消息隊(duì)列可以分為消息和隊(duì)列。消息的集合才是隊(duì)列,隊(duì)列是用來管理消息的。消息隊(duì)列主要用來在任務(wù)之間傳輸數(shù)據(jù)。消息可以簡單地理解為一個(gè)存儲發(fā)送數(shù)據(jù)的信息(包括信息的地址、信息的字節(jié)大小等)的存儲空間。

1.6.4 互斥信號量

前面已有信號量的存在,為什么還要互斥信號量(互斥信號量英文為Mutex)呢?這里面存在一個(gè)稱為優(yōu)先級反轉(zhuǎn)的問題。搶占式內(nèi)核的宗旨就是讓高優(yōu)先級的任務(wù)盡量先占用CPU。信號量(這時(shí)信號量用來管理資源)已經(jīng)被低優(yōu)先級占用的時(shí)候,高優(yōu)先級就要等待這個(gè)低優(yōu)先級釋放信號量。這是可以理解的,也是很合理的,資源不像CPU那樣可以隨便切換,比如前面講過的串口打印數(shù)據(jù),打印一半就切換給高優(yōu)先級的任務(wù),結(jié)果肯定不是我們想要的,我們需要的是各自完整的打印結(jié)果。更有甚者,即使目前占用資源的低優(yōu)先級任務(wù)被掛起,也不可以“切換”資源給等待這個(gè)信號量的高優(yōu)先級任務(wù)。等待資源的這個(gè)過程,高優(yōu)先級已經(jīng)被掛起,意味著高優(yōu)先級任務(wù)的優(yōu)先級已經(jīng)“降低”到占用信號量的任務(wù)的較低優(yōu)先級,“降低”的原因是受資源不能切換的限制。而這時(shí)如果還有一個(gè)處于前面提到的這兩種高低優(yōu)先級中間的優(yōu)先級任務(wù)想要占用CPU,那么CPU就會被中優(yōu)先級的這個(gè)任務(wù)占用,因?yàn)樗膬?yōu)先級比低優(yōu)先級高。這不符合我們前面的宗旨,中優(yōu)先級占用CPU實(shí)際上讓高優(yōu)先級等待更多的時(shí)間才運(yùn)行,所以不能讓中優(yōu)先級占用CPU!解決辦法就是優(yōu)先級繼承,就是提高前面提到的低優(yōu)先級任務(wù)的優(yōu)先級,讓它跟等待資源的高優(yōu)先級處于同一個(gè)優(yōu)先級。前面提到的優(yōu)先級“降低”的問題就迎刃而解,“降低”到跟自身一樣相當(dāng)于沒有“降低”。因此就產(chǎn)生了互斥信號量。互斥信號量比多值信號量多一個(gè)優(yōu)先級反轉(zhuǎn)的機(jī)制,互斥信號量可以避免優(yōu)先級反轉(zhuǎn)的問題,但是操作過程占用的時(shí)間也更多。

1.6.5 內(nèi)存分區(qū)

內(nèi)存分區(qū)主要用于減少內(nèi)存碎片,相關(guān)內(nèi)容請參見相應(yīng)章節(jié)。

1.6.6 軟件定時(shí)器

跟硬件定時(shí)器一樣,軟件定時(shí)器也有定時(shí)的功能。相對來說,軟件定時(shí)器的配置比較容易,但是定時(shí)精度很難達(dá)到硬件定時(shí)器的標(biāo)準(zhǔn)。因此,軟件定時(shí)器可以用于精度要求相對不高的一些事務(wù)中,比如1分鐘后關(guān)閉LCD屏幕的背光等。

1.7 μC/OS-III常見的程序段

本節(jié)介紹的是經(jīng)常在程序中出現(xiàn)的相似的程序段。

1.7.1 中斷嵌套層數(shù)統(tǒng)計(jì)

每進(jìn)入一層中斷,μC/OS-III的OSIntNestingCtr就會加1,每退出一層中斷就要減1。中斷嵌套層數(shù)功能之一就是可以用來檢查μC/OS-III是否進(jìn)入了中斷,OSIntNesting大于0就代表至少進(jìn)入了一層中斷,這樣可以防止一些不能在中斷中調(diào)用的函數(shù)被調(diào)用,比如任務(wù)調(diào)度函數(shù)OSSched,在中斷服務(wù)程序運(yùn)行的時(shí)候進(jìn)行了任務(wù)切換,其后果肯定是相當(dāng)嚴(yán)重的。中斷通常是比較緊急的事情,就好像這邊著火了,你肯定不能救火救到一半去看場電影再回來。還有一些函數(shù)的臨界段比較長,并且沒有必要在中斷中運(yùn)行,比如信號量創(chuàng)建函數(shù)OSSemCreate等是不允許在中斷中調(diào)用的,調(diào)用這些函數(shù)之前都會先檢查是否進(jìn)入中斷。進(jìn)入中斷之前,用戶需要調(diào)用增加嵌套層數(shù)的函數(shù),如代碼清單1-4所示。

一個(gè)中斷服務(wù)函數(shù)通常如下。

代碼清單1-4 μC/OS-III中斷服務(wù)函數(shù)示例


    1     void XXX_ISR(void)
    2 {
    3     OSIntEnter();     //嵌套層數(shù)加1
    4
    5     XXXXXXXXXXXX;     //中斷執(zhí)行內(nèi)容
    6
    7     OSIntExit();       //嵌套層數(shù)減1
    8 }

1.7.2 開中斷和關(guān)中斷

開關(guān)中斷進(jìn)入臨界段的用法如代碼清單1-5所示。

代碼清單1-5 臨界段程序的編寫


    1 XXX_Fuction(void)
    2 {
    3     CPU_SR_ALLOC();
    4     CPU_CRITICAL_ENTER();
    5
    6     //臨界段代碼
    7
    8     CPU_CRITICAL_Exit();
    9 }

后面介紹的每個(gè)函數(shù)基本上都有這些代碼段,因?yàn)樯婕疤鄡?nèi)核的東西,剛接觸的讀者可能比較難以理解,所以在這里先不講解其具體代碼的實(shí)現(xiàn),只要知道這些函數(shù)的功能和怎么使用即可。

注意:不能在關(guān)中斷期間阻塞任務(wù)運(yùn)行,即不能調(diào)用阻塞函數(shù),如包含pend或者delay等詞的函數(shù),這些都涉及任務(wù)切換。而任務(wù)切換跟中斷是相關(guān)的,如果在關(guān)中斷期間阻塞任務(wù),則不會達(dá)到阻塞任務(wù)的效果。

1.7.3 使能中斷延遲的鎖住和開啟調(diào)度器

使用中斷延遲的時(shí)候,進(jìn)入和退出臨界段有兩種方式。關(guān)中斷前面已經(jīng)介紹了,下面介紹鎖住和開啟調(diào)度器。

鎖住調(diào)度器在開啟中斷延遲的時(shí)候分為兩種情況:一種是調(diào)用宏OS_CRITICAL_ENTER(),直接鎖住調(diào)度器;另一種是調(diào)用宏OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(),調(diào)用這個(gè)宏之前先關(guān)閉中斷,接著鎖住調(diào)度器,恢復(fù)中斷在關(guān)閉之前的狀態(tài)(注意這里不是打開中斷)。這種常用于臨界段前面(部分)不被中斷打斷,而后面部分只要不被其他任務(wù)打斷即可。前半部分先關(guān)中斷,后半部分調(diào)用開執(zhí)行OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(),這樣就減少了關(guān)中斷的時(shí)間。沒有使用中斷延遲的時(shí)候整段都要關(guān)中斷,這樣就延長了關(guān)中斷的時(shí)間。

解開一層調(diào)度嵌套也有兩種方式:一種是調(diào)用宏OS_CRITICAL_EXIT(),另外一種是調(diào)用宏OS_CRITICAL_EXIT_NO_SCHED()。從宏的名字上看,它們的區(qū)別是在解開一層調(diào)度嵌套后是否嘗試進(jìn)行任務(wù)調(diào)度。臨界段中是無法實(shí)現(xiàn)任務(wù)調(diào)度的,退出臨界段的時(shí)候可能需要進(jìn)行任務(wù)調(diào)度,所以一般調(diào)用的是OS_CRITICAL_EXIT()。但是有些函數(shù)如提交函數(shù)在調(diào)用的時(shí)候可以選擇是否要進(jìn)行調(diào)度,如果這時(shí)退出臨界段就先不要調(diào)度。因此,要使用OS_CRITICAL_EXIT_NO_SCHED(),在函數(shù)的最后應(yīng)根據(jù)選項(xiàng)情況是否進(jìn)行調(diào)度。

1.7.4 沒有使能中斷延遲的鎖住和開啟調(diào)度器

沒有使用中斷延遲的時(shí)候,進(jìn)入和退出臨界段的操作如代碼清單1-6所示。對比使用中斷延遲,以下代碼中的4種鎖住和開啟調(diào)度的方式非常“簡單暴力”,直接調(diào)用開關(guān)中斷的宏。這樣做的壞處是導(dǎo)致關(guān)中斷的時(shí)間變長。

代碼清單1-6 沒有使用中斷延遲進(jìn)入和退出臨界段的代碼


      1 #define  OS_CRITICAL_ENTER()                     CPU_CRITICAL_ENTER()
      2
      3 #define  OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT()
      4
      5 #define  OS_CRITICAL_EXIT()                      CPU_CRITICAL_EXIT()
      6
      7 #define  OS_CRITICAL_EXIT_NO_SCHED()             CPU_CRITICAL_EXIT()

OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT()之所以是一個(gè)空宏,是因?yàn)樵谡{(diào)用這個(gè)函數(shù)之前肯定已經(jīng)關(guān)中斷,這時(shí)本想切換到鎖住調(diào)度器來進(jìn)入臨界段,可是沒有使能中斷延遲進(jìn)入臨界段,所以只能關(guān)中斷。

注意:開關(guān)中斷、開啟和鎖住調(diào)度器名稱上有OS和CPU的區(qū)別。OS指的是μC/OS-III內(nèi)核代碼,是屬于軟件層面的,所以O(shè)S_CRITICAL_ENTER 就是軟件層面的進(jìn)入臨界段,即鎖住調(diào)度器。CPU是你使用的平臺,這里使用的是基于Cortex-M3內(nèi)核的STM32單片機(jī),指向的是硬件層面,CPU_CRITICAL_ENTER就是硬件層面的進(jìn)入臨界段,即關(guān)中斷。

1.7.5 中斷嵌套檢測

中斷嵌套檢測程序如代碼清單1-7所示。

代碼清單1-7 中斷嵌套檢測代碼


          1 #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
          2 if (OSIntNestingCtr > (OS_NESTING_CTR)0)
          3 {
          4     *p_err = OS_ERR_XXX;
          5     return;
          6 }
          7 #endif

以上代碼主要用于檢測程序是否在中斷中被調(diào)用,如等待函數(shù)、延時(shí)函數(shù)等阻塞線程的函數(shù),甚至創(chuàng)建內(nèi)核對象、刪除內(nèi)核對象等都有這段程序,因?yàn)檫@些函數(shù)有較長的臨界段。

OSIntNestingCtr這個(gè)變量是用來檢測中斷嵌套層數(shù)的。

第2行先判斷中斷嵌套的次數(shù),每進(jìn)入一次中斷,OSIntNestingCtr就會加1。所以,如果大于0,就是至少進(jìn)入了一層中斷。若沒有進(jìn)入任何中斷,則OSIntNestingCtr為0。如果是在中斷中調(diào)用,且直接返回,那么不再執(zhí)行下面的程序段。

如果不確定函數(shù)是否在中斷中被調(diào)用,就查看函數(shù)的內(nèi)容有沒有這段代碼即可,一般阻塞的函數(shù)肯定是不可以在中斷中被調(diào)用的。

1.7.6 調(diào)度器嵌套檢測

調(diào)度器嵌套檢測程序如代碼清單1-8所示。

代碼清單1-8 調(diào)度器嵌套檢測代碼


    1 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u)
    2 {
    3     *p_err = OS_ERR_SCHED_LOCKED;
    4     return;
    5 }

同中斷嵌套,調(diào)度器每被鎖住一次,調(diào)度器嵌套層數(shù)的變量OSSchedLockNestingCtr就會加1。一些需要任務(wù)調(diào)度的函數(shù),比如延時(shí)的時(shí)候需要調(diào)用系統(tǒng)提供的函數(shù)先把當(dāng)前的任務(wù)置于延時(shí)狀態(tài),然后調(diào)度就緒列表中優(yōu)先級最高的任務(wù)。如代碼清單1-8所示,在延時(shí)函數(shù)的前面會先檢測調(diào)度器是否已經(jīng)被鎖住,即判斷OSSchedLockNestingCtr是否大于0,如果被鎖住,就退出延時(shí)函數(shù),延時(shí)失敗。

注意:調(diào)度器若嵌套多層,也需要解開多層后才可以執(zhí)行調(diào)度。

1.7.7 時(shí)間戳

為了測量任務(wù)運(yùn)行時(shí)間、任務(wù)等待內(nèi)核對象時(shí)間等,μC/OS-III保存了很多時(shí)間戳。時(shí)間戳可以理解為是記錄時(shí)間點(diǎn)的一個(gè)量,如代碼清單1-9所示。在程序段XXX運(yùn)行的前后分別保存時(shí)間戳,并相減得出XXX程序段運(yùn)行的時(shí)間,最后更新該程序段運(yùn)行的最大時(shí)間。定時(shí)器任務(wù)、節(jié)拍任務(wù)、統(tǒng)計(jì)任務(wù)、中斷延遲提交任務(wù)都有這段程序,這些任務(wù)都是系統(tǒng)任務(wù)。

代碼清單1-9 計(jì)算程序運(yùn)行時(shí)間代碼段


        ts_start = OS_TS_GET();
        XXX;
        ts_end = OS_TS_GET() - ts_start;
        if (ts_end > OSTickTaskTimeMax)
        {
            OSTickTaskTimeMax = ts_end;
        }

1.7.8 錯(cuò)誤類型

如果程序運(yùn)行過程中出現(xiàn)錯(cuò)誤,則程序會返回這些錯(cuò)誤,以便用戶得知出錯(cuò)。為了程序的健壯性,調(diào)用系統(tǒng)函數(shù)之后最好有相應(yīng)的錯(cuò)誤處理。有些錯(cuò)誤類型可以很容易地從其名字中看出來,而且在后面的程序中出現(xiàn)的概率非常高,因此先放在這里介紹。

1)OS_ERR_NONE:正常情況,沒有錯(cuò)誤。

2)OS_ERR_OBJ_TYPE:內(nèi)核對象類型錯(cuò)誤,根據(jù)判斷內(nèi)核對象變量的元素Type即可知道內(nèi)核對象類型是否錯(cuò)誤,比如在一個(gè)定時(shí)器刪除函數(shù)中,可以根據(jù)元素Type來判斷輸入的內(nèi)核對象變量是否是定時(shí)器類型。

3)OS_ERR_OPT_INVALID:選項(xiàng)設(shè)置有誤。

4)OS_ERR_SCHED_LOCKED:調(diào)度器被鎖住。

5)OS_ERR_TIME_DLY_ISR:在中斷中調(diào)用延時(shí)函數(shù)。

1.7.9 參數(shù)檢測

參數(shù)檢測程序如代碼清單1-10所示。

代碼清單1-10 參數(shù)檢測代碼段


    1 #if OS_CFG_ARG_CHK_EN > 0u
    2 if (p_tmr == (OS_TMR *)0)
    3 {
    4     *p_err = OS_ERR_TMR_INVALID;
    5     return (OS_TMR_STATE_UNUSED);
    6 }
    7 #endif

代碼清單1-10中OS_CFG_ARG_CHK_EN是參數(shù)檢測的宏,若置1,則會在很多內(nèi)核函數(shù)的前面進(jìn)行參數(shù)檢測。主要用于檢測傳遞的指針是否是空指針、參數(shù)范圍是否符合要求等。

1.7.10 內(nèi)核對象類型檢測

內(nèi)核對象類型檢測程度如代碼清單1-11所示。

代碼清單1-11 內(nèi)核對象類型檢測代碼


    1 #if OS_CFG_OBJ_TYPE_CHK_EN > 0u
    2 if (p_tmr->Type != OS_OBJ_TYPE_XXX)
    3 {
    4     *p_err = OS_ERR_OBJ_TYPE;
    5     return (DEF_FALSE);
    6 }
    7 #endif

比如,調(diào)用一個(gè)定時(shí)器刪除函數(shù),則傳入的內(nèi)核對象參數(shù)類型必須是定時(shí)器。這里用于檢測傳入的內(nèi)核對象類型是否是定時(shí)器。

1.7.11 安全檢測

安全檢測程序如代碼清單1-12所示。

代碼清單1-12 安全監(jiān)測代碼段


    1 //是否定義安全檢測的宏
    2 #ifdef OS_SAFETY_CRITICAL
    3 if (p_err == (OS_ERR *)0)
    4 {
    5     //如果傳入的參數(shù)p_err是空指針,那么將進(jìn)入安全關(guān)鍵異常,這部分代碼需要用戶自己編寫
    6     OS_SAFETY_CRITICAL_EXCEPTION();
    7     return;
    8 }
    9 #endif

如果定義了安全檢測的宏,那么作為參數(shù)輸入的存放返回錯(cuò)誤類型的指針p_err就不能為空指針,否則在調(diào)用OS_SAFETY_CRITICAL_EXCEPTION后返回,而且OS_SAFETY_CRITICAL_EXCEPTION需要自己編寫。

1.7.12 安全關(guān)鍵IEC61508

安全關(guān)鍵IEC61508程序如代碼清單1-13所示。

代碼清單1-13 安全關(guān)鍵IEC61508相關(guān)代碼


    1 //是否啟動安全關(guān)鍵
    2 #ifdef OS_SAFETY_CRITICAL_IEC61508
    4     if (OSSafetyCriticalStartFlag == DEF_TRUE) {
    5          *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
    6          return;
    7     }
    8 #endif

在定義了安全關(guān)鍵的宏OS_SAFETY_CRITICAL_IEC61508后,并且OSSafetyCriticalStartFlag被置為DEF_TRUE時(shí),不再允許調(diào)用相關(guān)創(chuàng)建函數(shù)。

1.8 總結(jié)

本章首先介紹了單片機(jī)程序框架,從最簡單的前后臺系統(tǒng)到時(shí)間片輪詢法、再到嵌入式實(shí)時(shí)操作系統(tǒng)。它們分別對應(yīng)不同層次的應(yīng)用,有著各自的局限和優(yōu)勢。

接著介紹了μC/OS-III整體的文件框架,從CPU相關(guān)移植代碼到μC/OS-III的主體代碼(跟CPU無關(guān)),再到用戶的應(yīng)用層代碼。層次感非常強(qiáng),方便移植μC/OS-III到不同的CPU上去。

最后介紹了μC/OS-III任務(wù)、內(nèi)核對象以及常見代碼段,這些是為后面的內(nèi)容做鋪墊,了解即可。

主站蜘蛛池模板: 西盟| 柘荣县| 碌曲县| 镇江市| 咸阳市| 闽清县| 子长县| 绥芬河市| 清镇市| 依兰县| 高雄县| 丘北县| 金平| 万州区| 龙州县| 巴青县| 城步| 石城县| 阿拉善左旗| 田林县| 土默特左旗| 环江| 仙游县| 霍城县| 双鸭山市| 河源市| 纳雍县| 莫力| 顺义区| 威海市| 德安县| 宜黄县| 宾阳县| 南平市| 德令哈市| 漾濞| 拉萨市| 沂源县| 瑞昌市| 方正县| 衢州市|