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

第2章 VxWorks操作系統的基本組件

本章將對VxWorks操作系統中的任務、任務調度、任務間通信、內存管理、中斷處理幾方面知識進行介紹。

2.1 VxWorks任務

VxWorks是實時操作系統,這決定了它的任務調度必須是基于優先級的,而且必須是可搶占式調度方式,這樣才能夠區分實際情況下不同狀態的處理級別,對高優先級的情況進行優先響應。

2.1.1 內核實現基本原理

VxWorks內核維護三個隊列:tick隊列、ready隊列、active隊列。另外還有一個隊列涉及任務,即任務等待資源時所處的隊列,這個隊列可以是VxWorks內核提供的,也可以是用戶提供的,此處令其為pend隊列。

所謂tick隊列,即當調用taskDelay函數讓任務延遲一段固定的時間時,任務所處的隊列,此時任務被設置為Delay狀態,無資格競爭使用CPU;ready隊列即有資格競爭使用CPU的所有任務,該隊列以優先級為序排列任務,隊列頭部是除了當前運行任務外,系統中最高優先級的任務;active隊列有些誤導,實際上稱之為task隊列更合適,因為系統中所有的任務無論當前狀態如何,都將在這個隊列中,這個隊列維護著系統中當前所有的任務,即通過該隊列可以查找到當前系統中的所有任務,在Shell下運行“i”命令,顯示系統中所有的任務,就是通過遍歷active隊列完成的;pend隊列即當任務競爭使用某資源,而資源當前不可得時,任務就被設置為pend狀態,進入pend隊列。

函數taskSpawn創建一個新的任務。首先,其創建一個任務控制結構,對該結構進行初始化后,將結構加入active隊列以作為系統任務管理之用。此時任務仍無資格競爭使用CPU,taskSpawn函數的最后一步就是將這個任務結構再加入到ready隊列,此時這個任務才真正可以稱為已經在競爭使用CPU了。當系統中所有的優先級高于這個任務的其他任務運行完畢或者由于等待資源而處于阻塞時,這個新創建的任務就將被調度運行。所以,在VxWorks下,如果一個新創建的任務優先級不高,創建后將等待一段時間才能被真正執行。在實際項目中,有時需要一個新的任務被創建后立刻得到執行,那么就需要在創建任務時,指定一個較高的任務優先級。VxWorks內核將任務分為256個優先級,標號從0到255。其中0表示最高優先級,255表示最低優先級。任務在調用taskSpawn函數進行創建時就指定了優先級,當然任務的優先級并非在創建后就無法改變,用戶可以通過調用taskPrioritySet函數在任務創建后重新設定優先級,taskPrioritySet專門針對嵌入式平臺下不同的情況對同一任務不同運行級別的需求進行設置。值得注意的是,taskPrioritySet函數不同于通用操作系統提供的類似函數,taskPrioritySet函數可以提高或者降低任務的運行級別,而不是通用操作系統下在任務創建后就只能動態地降低任務優先級。

VxWorks下對于應用層任務,推薦使用100~250 之間的優先級,驅動層任務可以使用51~99之間的優先級。要特別注意的是,內核網絡數據包收發任務tNetTask的優先級為50,如果使用網口進行調試,則一定注意不要創建任務優先級高于50 的任務,否則tNetTask任務將無法得到運行,表現形式是死機,因為系統將無法再從網口接收調試命令,無法響應Tornado Shell或者Telnet下輸入的任何命令。以上只是推薦值,事實上,由于嵌入式系統下的特殊應用,對于某個任務優先級的設置需要根據該任務完成的具體工作而定,而不可一味地遵循推薦值。如筆者曾在項目中為了與Shell“爭讀”從串口發送的命令,就將一個應用層任務優先級設置為0。要謹記在任務達到某一特殊目的后,必須將任務優先級設置回正常(推薦)值。

無論何種操作系統,任務在設計中都由一個數據結構表示。這個數據結構包含一個任務運行時需要的所有信息,我們一般將這些信息稱為任務上下文。具體的任務上下文(廣義上)包括以下內容:

1)所在平臺CPU內部所有的寄存器值,特別是指令寄存器,這代表了任務當前的執行點。這一般是狹義上的任務上下文。除了寄存器值,每個任務有自己的內存映射空間、任務名稱、任務優先級值、任務入口函數地址、打開文件句柄數組、信號量和用于各種目的的隊列等。

2)任務運行時暫時存放函數變量以及函數調用時被傳遞參數的棧。從操作系統底層實現來看,很多操作系統將表示任務的數據結構和任務棧統一管理,如Linux下在分配任務結構的同時分配任務的內核棧,這兩者作為一個整體進行內存分配,通常將一頁(如4KB)的開始部分作為任務結構,用以存儲任務關鍵信息,而將頁的末尾作為任務內核棧的頂部。如此,實際上用一頁頁面的大小(通常為4KB)減去任務信息占據的空間,剩下的空間都作為任務的內核棧在使用。VxWorks與Linux在棧的分配和管理上基本類似,不過VxWorks區分于Linux的一個最大不同是VxWorks下所有的代碼都運行在一個狀態下,不區分內核態和用戶態(當然,VxWorks下也有內核態的概念,但含義完全不同,見下文分析)。所以,不存在Linux下的內核態棧和用戶態棧。VxWorks下的任務自始至終都在使用同一個棧,不論這個任務在運行過程中調用了任何VxWorks內核函數,都不存在棧的切換。正因如此,VxWorks對棧的大小無法預先進行把握,棧的大小將由被創建的任務決定,而且不同于通用操作系統,VxWorks下任務棧在任務創建時就被確定,而且此后不可以改變棧的大小。所以,對于一個存在很多遞歸調用的任務,必須在任務創建時指定一個較大的任務棧,防止在后續的運行中造成棧的溢出,導致任務異常退出。

3)各種定時信息。這些信息實際上都作為任務結構中的一個字段而存在。任何操作系統都必須有一個系統時鐘進行驅動,該系統時鐘通常稱為系統的脈搏。系統時鐘一定與一個高優先級的中斷聯系,這樣,每當時鐘前進一個滴答(Tick),操作系統就會響應一次中斷,該中斷通常就被作為操作系統進程調度的觸發點。每次滴答,操作系統都會增加內核維護的一個全局變量(如VxWorks操作系統維護的vxTick變量),通過該變量為系統各種定時器提供定時依據。每個任務都有一個內部固定的定時器,用于任務內部特定的需求,每次系統時鐘產生一個中斷,操作系統都會對當前系統內所有需要關注的任務定時器進行處理,從而完成任務定時器特殊的用途。定時器的一個特殊變相應用即Round-Robin任務調度(簡稱RR調度)。RR調度實際上對每個支持RR調度的任務內部都維護有一個定時器。當一個支持RR調度的任務被調度進入運行狀態時,在任務運行期間,每次系統時鐘前進一個滴答時,該定時器指針都會前進一個單位。當到達預定的值時,該任務就要主動讓出CPU,以便讓相同優先級的其他任務運行。定時器可以以加法或者減法運算運行。對于減法運算,一般根據定時時間計算出一個Tick數,每次系統前進一個滴答,Tick數減一,當到達0時,表示定時器到期。而對于加法運算,則一般需要使用操作系統維護的全局Tick變量。VxWorks下,如設置定時時間間隔為N,則定時器到期時間為vxTick+N=T0,每次系統前進一個滴答,操作系統會對當前系統內維護的所有定時器進行檢查,判斷T0是否大于vxTick,一旦T0小于或等于vxTick,則表示該定時器到期,此時將根據定時器的目的做出相應的響應。

4)信號處理函數。事實上,操作系統的每個信號都有一個默認的響應方式,如用戶在命令行按“Ctrl+C”組合鍵時,則系統默認響應方式是中止當前前臺任務。每個任務可以根據自身情況定制對某個信號的響應方式。如一個任務可以將用戶的“Ctrl+C”組合鍵操作響應為打印輸出當前任務中某個變量的值。每個任務內部對每個信號都維護一個響應函數句柄,操作系統在創建任務時已經將所有的句柄設置為系統默認方式,用戶在創建任務后,可以針對某個信號安裝自己的信號響應句柄。

5)其他輔助信息。這些信息包括統計上的一些數據,如任務運行總時間、任務最終返回值等。

2.1.2 任務操作函數

前文中說到VxWorks是基于優先級方式的搶占式任務調度操作系統,同時對于相同優先級的任務,支持Round-Robin循環調度方式(以下簡稱RR調度)。RR調度方式通過kernelTimeSlice函數使能。該函數調用原型如下。

        STATUS kernelTimeSlice
        (
        int ticks /* time-slice in ticks or 0 to disable round-robin */
        )

RR調度在默認情況下是禁用的。當用戶以非零參數明確調用kernelTimeSlice函數后,RR調度方才使能,此時相同優先級的任務可以輪流使用CPU,但是整個系統仍然是按優先級方式進行調度的。換句話說,只有在當前系統中最高優先級下存在多個任務運行時,RR調度才有效,如果存在多個低優先級的任務,那么系統仍然運行著高優先級的任務,這些低優先級任務根本無法使用CPU。注意,kernelTimeSlice函數參數為tick系統時鐘嘀答數,一般情況下,VxWorks下一個滴答為1ms,故很多時候直接將參數作為以ms為單位的時間值。如果在調用kernelTimeSlice函數使能RR調度方式后,又想禁用RR調度方式,則只需要以參數0再次調用kernelTimeSlice函數即可。

控制任務調度的另一個函數是taskPrioritySet,該函數通過改變任務優先級來控制調度。注意,taskPrioritySet函數可以任意改變任務的優先級,該函數的調用原型如下。

        STATUS taskPrioritySet
        (
        int tid, /* task ID */
        int newPriority /* new priority */
        )

第一個參數(tid)為需要改變優先級的任務的ID號,這是一個整型值,是調用taskSpawn創建任務時的返回值。第二個參數(newPriority)即對應任務需要設置的優先級,該參數范圍為0~255,即可以改變某個任務到任意優先級,而不是只能降低任務的優先級。

注意

一個任務可以在運行過程中通過taskPrioritySet函數按照需要自己改變自己的優先級,此時只需要將第一個參數設置為0即可。這種可以動態改變任務自身優先級的方式給任務的運行提供了極大的靈活性,特別是當任務需要在某個特定時刻從特殊渠道獲取數據時,必須提高自身的優先級才能讀取到數據,而在讀取數據后,又必須退回到正常優先級進行數據的處理,taskPrioritySet函數就是針對這種情況專門設計的。通常,任務不需要使用taskPrioritySet函數。

另外一對控制任務調度的關鍵函數是taskLock和taskUnlock。函數調用原型分別如下:

        STATUS taskLock (void)
        STATUS taskUnlock (void)

taskLock函數即關閉任務調度,taskUnlock函數即重新開啟任務調度,這兩個函數必須成對使用。這兩個函數通常用于系統級的資源共享上,是一種變相的互斥機制。不過需要注意的是,這兩個函數并不禁止中斷,所以并不能與在中斷上下文中運行的函數形成互斥。另外要注意的是,taskLock并不是信號量,它僅僅是暫時禁止了任務調度機制,保證了接下來的一段時間內(直到調用taskUnlock),當前任務一直主動占用CPU。taskLock主要使用在需要以原子方式執行一段代碼的情況下,如對某個變量進行某種操作(如加1或減1)。剛才說到任務主動占用CPU,即沒有其他外界手段可以將任務調度出去,但是任務自身可以進入阻塞狀態(如阻塞于某個資源,雖然不應在此種方式下調用taskLock),此時taskLock應有的作用將被取消,任務調度機制重新工作,直到該任務又重新被調度運行。換句話說,只要調用taskLock的任務處于運行狀態,則任務調度機制一直被禁止,而一旦該任務由于某種原因主動讓出CPU,則任務調度機制重新啟動。taskLock的作用對于某個任務而言,只有taskUnlock可以取消它。

VxWorks提供任務創建函數taskSpawn,該函數的調用原型如下。

        int taskSpawn
        (
        char * name,            /* name of new task (stored at pStackBase) */
        int priority,            /* priority of new task */
        int options,             /* task option word */
        int stackSize,           /* size (bytes) of stack needed plus name */
        FUNCPTR entryPt,         /* entry point of new task */
        int arg1,                /* 1st of 10 req'd task args to pass to func */
        int arg2,
        int arg3,
        int arg4,
        int arg5,
        int arg6,
        int arg7,
        int arg8,
        int arg9,
        int arg10
        )

① 參數1:char * name

任務名。可以為NULL,此時系統將使用默認的任務名。默認任務名形式為“tN”,其中,“t”為前綴,“N”是一個數字,系統將對所有的任務創建時未提供任務名的任務依次進行編號,從1開始。

② 參數2:int priority

任務優先級。該優先級決定了任務的調度級別。在任務創建完畢后,仍然可以使用taskPrioritySet函數對任務優先級進行動態改變。

③ 參數3:int options

任務選項。用于控制任務的某些行為。任務可用選項如表2-1所示,一般情況下,將該參數設置為0。

表2-1 任務可用選項

④ 參數4:int stackSize

任務棧大小。注意,任務棧在創建任務時指定,此后不可更改。任務從創建到結束自始至終都在使用這個棧。VxWorks下任務棧和表示任務的結構連續存放,在內存分配時是作為一個整體進行分配的。

⑤ 參數5:FUNCPTR entryPt

任務開始執行時入口函數的地址。當任務被調度運行時,從該參數指定的地址開始執行。

⑥ 參數6~15:int arg1~int arg10

入口函數參數,最多可同時傳遞10個參數,多于10個時,可以通過指針的方式傳遞。

⑦ 返回值

taskSpawn函數返回一個整型數據,表示剛創建任務的ID。實際上,這個ID是一個內存地址,指向這個被創建任務的TCB(Task Control Block)。故,原則上可以通過以下方式通過任務ID得到任務的TCB:

        WIND_TCB *tPcb;
        tPcb = ((WIND_TCB *) tid);
        …

但是并不建議這樣做,因為對于任務ID的解釋可以隨著VxWorks內核版本的不同而改變,故要從任務ID得到任務對應的TCB,建議使用內核提供的函數taskTcb。taskTcb函數調用原型如下。

        WIND_TCB *taskTcb
        (
        int tid /* task ID */
        )

使用方式的代碼如下。

        WIND_TCB *tPcb;
        tPcb = taskTcb(0);

注意

taskTcb要求傳入任務ID,當以0作為參數傳遞給該函數時,taskTcb將返回當前任務的TCB結構地址。參數0表示得到當前任務的某種信息,這種處理方式在VxWorks下很常見。

taskSpawn調用舉例:

        taskSpawn ("demo", 20, 0, 2000, (FUNCPTR)usrDemo, 0,0,0,0,0,0,0,0,0,0);

        void usrDemo (void)
        {
            ……
        }

taskSpawn函數調用完畢后,任務就進入運行狀態,即與其他任務競爭使用CPU。有時創建一個任務后,需要暫時使其處于掛起狀態,不具有調度運行的資格,這可以通過調用taskCreate函數完成。taskCreate調用原型如下。

        int taskCreate
        (
        char * name, /* name of new task */
        int priority, /* priority of new task */
        int options, /* task option word */
        int stackSize, /* size (bytes) of stack needed */
        FUNCPTR entryPt, /* entry point of new task */
        int arg1, /* 1st of 10 req'd args to pass to entryPt */
        int arg2,
        int arg3,
        int arg4,
        int arg5,
        int arg6,
        int arg7,
        int arg8,
        int arg9,
        int arg10
        )

taskCreate函數參數與taskSpawn完全一致,其區別于taskSpawn函數之處就在于taskCreate創建后的任務暫不具有運行的資格,需要另一個函數taskActivate來“激活”。taskActivate函數將任務結構加入到ready隊列,從而使得任務有資格競爭使用CPU。至于任務確切的運行時間,則根據系統當前任務情況決定。一般而言,某個任務結構被加入到系統ready隊列,我們就認為其已經在運行狀態,因為余下的情況已經無法由系統和用戶掌控。如果用戶需要在任務被激活后立刻得到運行,可以設置任務為較高優先級。任務激活函數taskActivate調用原型如下。

        STATUS taskActivate
        (
        int tid /* task ID of task to activate */
        )

該函數接收taskCreate函數返回的任務ID,對指定ID的任務進行激活,即將任務結構添加到系統任務ready隊列中,使得任務成為競爭使用CPU的一員。

2.1.3 深入了解任務棧

任務在VxWorks內核中作為調度的基本單元,每個任務是程序的一個實例,程序可以由單個或多個函數構成。任務在運行這些函數的過程中,不可避免地使用一些函數變量(定義在函數內部的變量),這些函數變量的存放地就在任務棧中;當存在函數間調用時,需要某種機制傳遞參數,使用較多的就是棧機制。棧實際上就是一塊地址連續的內存塊,只不過由于其特殊的使用方式,從而被封裝成一個特殊的結構類型。一般而言,棧的使用以向下遞減地址方式,即棧頂位于內存高地址處,棧底位于內存低地址處,使用時從棧頂開始使用,逐漸向棧底逼近。當然也有相反方向的工作模式。無論方向如何,其目的都是一樣的,只要在某個平臺保持全局范圍內的統一性,就可以保證系統工作的正常性。注意:棧的方向一般是由CPU硬件決定的,更進一步地說,是由指令集決定的。如Intel x86系列提供的push、pop指令就是向下遞減棧地址的,操作系統編寫者對此必須了解。

通用操作系統進程棧(通用操作系統一般稱每個調度單元為進程)分為兩個層次,這是由于通用操作系統一般是由兩個運行態(內核態和用戶態)決定的。通用操作系統需要提供高安全性的運行環境,它必須明確地隔離兩個不同層次上的操作,從而知道哪些操作是被允許的,哪些是被禁止的,當然安全措施不僅僅通過運行態來實現,運行態的區分只是其中重要的措施之一。通用操作系統進程在兩個不同運行態下具有不同的棧,當進程運行在用戶態時,其使用用戶態棧,當發生系統調用或由于中斷進入到內核態時,其切換到進程的內核態棧。

一般而言,用戶態棧是使用不盡的,由平臺內存空間決定,地址空間一般不造成限制(如Linux下用戶態棧原則上有接近3GB的空間可用),而內核態棧大小是存在很大限制的,如Linux下內核態棧一般只有大約4KB的大小。所以,進程運行在內核態時,要求大空間的結構必須使用堆的方式分配,不要使用棧進行分配。事實上,由于運行在內核態的代碼都是嚴格限制的,所以一般都不會發生內核態棧溢出的問題,而由于用戶態棧可使用的空間極大,雖然其先分配給用戶態棧的實際內存較小,但是可以根據需要動態地增加用戶態棧內存,所以很難使得用戶態棧溢出(你能想象一個應用程序可以使用完3GB的棧嗎?)。

VxWorks內核不使用通用操作系統下的運行態,雖然VxWorks下也有內核態的概念,不過其本質上就是一個內核數據結構的簡單保護機制,僅僅由一個全局變量kernelState表示是否在內核態,當用戶進行內核函數調用或者中斷發生進入到VxWorks內核運行時,其并不發生運行態的本質切換,而任務自始至終都在使用同一個棧。換句話說,VxWorks下的任務棧既被應用函數(用戶編寫的函數)使用,也被內核函數(VxWorks內核提供)使用,其VxWorks下任務棧在初始創建后,不可以根據需要在后期動態地增加內存容量。這就對VxWorks任務棧的初始創建大小提出了一個要求,即其在創建棧(即創建任務)時指定的棧大小必須足夠大,以滿足任務后續運行期間對棧容量的需求,而這一點是很難由用戶在創建時進行判斷的。故實際上,用戶在創建任務時會指定一個比實際需要大得多的棧容量,這對于一個嵌入式系統而言,對寶貴的內存資源造成了極大的浪費。VxWorks官方文檔的建議是通過試驗法,在程序開發期間,在任務創建時,指定一個比較大的棧,在任務運行期間,不斷地使用checkStack函數查看任務的棧使用情況,從而得到一個任務棧使用數據的大致統計情況。其后,用這個得到的統計值作為任務棧的實際容量。checkStack函數的調用原型如下。

        void checkStack
        (
        int taskNameOrId /* task name or task ID; 0 = summarize all */
        )

參數(taskNameOrId)指定要檢查的棧對應任務的ID。以參數0調用該函數時,會打印出系統內所有任務的棧的使用情況。checkStack函數的一個使用實例如下。

        -> checkStack tShell
        NAME ENTRY TID SIZE CUR HIGH MARGIN
        ------------ ------------ -------- ----- ----- ----- ------
        tShell _shell 23e1c789208832 36325576

2.1.4 任務名長度問題

當在Shell下使用“i”命令查看當前系統中的任務時,對于任務名的顯示有些令人誤解:任務名長度較長的字符串(如大于11B)都發生了截斷。這讓很多人誤解為任務名最大只能有10B的長度。故在創建任務時刻意減少任務名的長度。其實這只是顯示上的一些問題,taskShow函數(“i”命令的底層調用函數)在打印任務名時進行了截斷操作,而非在taskSpawn函數中對傳入的任務名參數進行截斷。事實上,VxWorks支持任意長度的任務名。換句話說,在調用taskSpawn或者taskCreate創建任務時,用戶可以指定一個其需要的任何名稱。VxWorks內核在內部以無損方式保存了這個用戶傳入的任意名稱。只是在調用taskShow對任務信息進行顯示時,在打印格式上的一些操作只打印出了任務名前11B。用戶可以根據需要自編寫一個任務信息顯示函數,將任務名稱全部打印出來。

VxWorks下并不要求任務名的全局唯一性,兩個完全不同的任務可以使用相同的名稱而不會造成底層(內核)使用上的任何問題,但是這會讓查看任務信息的用戶產生疑問。同樣,上文中討論到的taskShow對于任務名的截斷顯示也會造成同樣的問題。故如果仍然使用內核提供的taskShow顯示任務信息,則建議用戶在任務創建時盡量將任務限制在11B之內,或者兩個不同任務的名稱最好在前11B內有所區別。任務信息顯示函數taskShow調用原型如下。

        STATUS taskShow
        (
        int tid, /* task ID */
        int level /* 0 = summary, 1 = details, 2 = all tasks */
        )

參數1(tid):需要顯示信息的任務ID。

參數2(level):任務信息開放程度。

注意

taskShow根據第2 個參數值對任務信息和任務范圍做不同程度的信息顯示。當參數2傳入的值為2時,即對系統內所有的任務進行信息顯示,此時將忽略第1個參數值;否則將只顯示參數1指定的任務信息。而參數2的值表示顯示任務信息的詳細程度。0表示只顯示大概信息,這些信息包括函數名稱、入口函數、任務棧使用情況總結;1表示顯示任務的詳細信息,這些信息除了以上所說的大概信息外,還包括任務棧的詳細信息(棧基址、棧容量)、任務所包含的事件、任務當前寄存器值。

2.1.5 正確結束任務

除了極少數幾個任務自始至終保持存活外,大多數任務都只是在一段時間內保持活動,最后都不可避免地要消亡。消亡有兩種方式:一是任務正常運行到結束,通過exit函數結束;二是被直接刪除,即非正常方式結束。一般情況下,用戶創建一個任務,完成某個特定的功能,最后“功成身退”,從系統中消失,這是一般任務的工作方式。例如,任務執行一個main函數,最后通過reture返回。代碼在編譯時,編譯環境實際上在函數最后加上了一個exit函數,這是對用戶透明的,用戶只需要調用reture語句或者直接退出都可以。但是從底層代碼運行來看,在運行完所有的用戶代碼后,還會執行一個exit函數,這個函數是由編譯環境在編譯用戶程序時自動加入的,與在函數結束時加上一個exit函數的道理相同。實際上,編譯環境在調用用戶提供的入口函數之前,也加上了一個類似于init的函數,創建程序的運行環境,進而調用用戶提供的入口函數,真正執行用戶的代碼。這些機制有些類似于C++中自動加入的初始化(constructor)和消亡(destructor)函數。

VxWorks下exit函數用于結束一個任務的壽命,其底層調用windDelete函數,完成表示任務的數據結構的釋放和任務棧的釋放。注意:windDelete只釋放任務棧和表示任務本身的結構,并不負責釋放用戶在任務運行過程中分配的任何內存空間。exit函數用于一個任務正常的自動消亡。內核同樣提供了一個非正常結束任務的方式:taskDelete函數,該函數的調用原型如下。

        STATUS taskDelete
        (
        int tid /* task ID of task to delete */
        )

注意

當以參數0調用taskDelete時,表示刪除當前運行的任務自身。此時的功能類似于被動調用exit函數退出。一般而言,如果一個任務需要刪除自身,那么直接調用exit即可,當然也可以以參數0調用taskDelete。但是taskDelete更多地被用于刪除一個其他正常運行的任務,而非自身。該函數提供的功能必須慎用。通常對于普通的用戶不需要調用這么一個任務刪除函數,這個函數極容易造成內核的不一致性,從而導致內核崩潰。設想一個任務剛剛獲取一個資源的使用權,正在所謂的“關鍵區域”執行代碼,而另外一個任務卻“霸道”地將其直接刪除,此時被刪除任務將停止運行余下的代碼,表示任務的結構和任務棧被釋放,進而造成內核資源狀態的不一致性,因為任務退出之前沒有相應地釋放其已獲取資源的使用權,導致其他任何競爭使用該資源的任務處于無限等待狀態。以一個簡單的例子說明,如多個函數需要競爭使用同一項資源,該資源有一個變量進行保護,任何使用該資源的任務在使用之前必須檢查該變量的值,如果該變量為1,則表示資源可用;若為0,則表示資源已被其他的任務使用,所有其他的任務必須等待,直到該變量重新變為1。為了保證對變量本身操作的原子性,一般會使用某種特定編碼方式,如在Intel x86結構下,會使用lock指令修改該變量的值。

某個競爭使用資源的任務準備使用資源時,其檢查該變量的值,如果為0,則等待,等待一段時間后,其重新檢查該變量的值,此時該變量值為1,表示資源可以訪問,那么任務將會將該變量的值設置為0,表示“我”這個任務現在正在使用資源,其他任務必須等待。而在該任務使用資源期間,一個“外來不明是非者”將這個任務進行了刪除,即中斷了正在使用資源的任務的執行,致使其非正常消亡,即系統中已經不存在這個任務了,那么這個任務原先在使用完資源后將該變量重新設置為1,表示資源可用的代碼永遠也無法得到運行。換句話說,該資源被一個“死亡”的任務無限期地“使用”,所有其他競爭使用該資源的任務都無限期地處于等待狀態。所以,不正當地刪除一個正在運行的其他任務是非常危險的一個操作,除非明確知道被刪除的任務當時所處的狀態,如被刪除任務已經設置了一個標志位,表示它可被刪除,因為其已經釋放了它所使用的一切資源,正在處于“無為”狀態,但是在外界其他某個條件滿足之前,其又不能提前結束,故等待其他任務對其進行刪除。但是這種情況除了某個特殊情況,一般都不會發生。所以,taskDelete函數的使用場合有限,用戶只需對其進行了解即可,建議不要使用。

與taskDelete“分庭抗禮”的一對函數就是taskSafe和taskUnsafe。一個任務為了防止在預先無任何提示的情況下被其他任務刪除,其可以調用taskSafe函數進行自身的保護,一個任務在調用taskSafe函數后,則其他任何任務都不可對其進行刪除操作,這個函數就是為了應對taskDelete而設計的,以防止上文所述的一個處于“關鍵區域”的任務被野蠻刪除的行為發生。taskSafe和taskUnsafe函數調用原型如下。

        STATUS taskSafe (void)
        STATUS taskUnsafe (void)

如下代碼列舉了這兩個函數的典型應用環境。

        taskSafe ();
        semTake (semId, WAIT_FOREVER); /* Block until semaphore available */
        .
        . /* critical region code */
        .
        semGive (semId); /* Release semaphore */
        taskUnsafe ();

VxWorks內核還提供如下三個函數對運行中的任務施加影響。第一個是taskSuspend,即掛起一個正在執行的函數,該函數調用原型如下。

        STATUS taskSuspend
        (
        int tid /* task ID of task to suspend */
        )

當以參數0調用taskSuspend時,表示掛起當前正常執行的任務。而以非0任務ID方式指定一個其他被掛起的任務。這個其他被掛起的任務原先可以是由于優先級較低而等待運行,也可以是一個處于延遲(delay)睡眠的任務或者是一個已被掛起的任務。

第二個函數taskResume的功能是取消taskSuspend的作用,其將一個被掛起的任務重新設置為可運行狀態。當然,此時被恢復的任務一定是一個其他任務,因為一個任務是不可能Resume其自身的。taskResume函數的調用原型如下。

        STATUS taskResume
        (
        int tid /* task ID of task to resume */
        )

第三個比較重要的控制任務運行的函數是taskDelay,即置當前正在運行的任務為睡眠狀態,睡眠時間長度以調用taskDelay時輸入的參數為據。taskDelay函數的調用原型如下。

        STATUS taskDelay
        (
        int ticks /* number of ticks to delay task */
        )

注意

參數以系統滴答數為單位,即以系統時鐘的時間間隔為單位。一般而言,這個間隔為1ms,所以可以簡單地認為是毫秒級的任務延遲。任務調用taskDelay進行延遲在代碼中使用較多,特別是以輪詢方式對某個設備進行操作的任務一般都是以while循環加taskDelay的方式工作。

另外要注意taskDelay的一種特殊工作方式,就是以NO_WAIT參數調用taskDelay,此種方式主要是在RR調度禁止使用的前提下,給相同優先級的其他任務一個使用CPU的機會。當以NO_WAIT調用taskDelay,VxWorks內核將當前任務置于ready隊列中所有相同優先級的任務之后,從而給相同優先級的其他任務提供一個運行的機會。注意,這種方式一般使用在RR調度被禁用的情況下。如果使能了RR調度,則一般無須使用此種方式,因為RR調度會循環讓相同優先級的任務使用CPU資源的。

2.1.6 任務的鉤子函數——黑客機制

涉及VxWorks任務一個重要的使用是可以在任務創建、消亡、調度之時調用用戶注冊的鉤子函數,這在某些情況下會特別有意義。VxWorks提供一種機制可以讓用戶注冊一種鉤子函數,當系統中有新的任務被創建,一個任務消亡以及任務調度時,可以執行用戶注冊的這些函數,從而達到用戶的特殊目的。如對于低功耗的實現,可以利用VxWorks內核提供的這種機制,雖然這不是很優美的一種實現,但可以用于說明鉤子函數的作用。低功耗要求當CPU中沒有用戶任務運行時,將整個平臺盡量置于低功耗狀態。當VxWorks下沒有用戶任務時,VxWorks將執行其自身提供的一個Idle任務,Idle任務具有最低優先級。換句話說,只要存在一個可運行的用戶任務,無論這個任務的優先級是什么,其都將被調度運行,如此,我們可以使用一個最低優先級的后臺任務加上一個任務調度鉤子函數實現平臺低功耗目的。在系統初始化階段,創建一個最低優先級的后臺任務,這個任務的優先級要低于所有的有“責任”在身的其他任務。換句話說,當這個后臺任務被調度運行時,就表示當前系統可以處于低功耗模式。我們同時注冊一個調度任務時被調用的鉤子函數,后臺任務實現代碼就是一個FOREVER語句,其偽代碼如下。

        FOREVER //等同于for(;;)
        {
            if(powerdown==FALSE){
                  put board to power down state;
                  powerdown=TRUE;
            }

        }

鉤子函數實現如下。

        void taskSwitchHook(WIND_TCB *pOldTcb, WIND_TCB *pNewTcb){
              if(old task is our daemon task){ /*即后臺任務被其他任務替代了*/
                    put board back to Normal state;
                    powerdown=FALSE;
              }
        }

一般而言,將平臺從低功耗模式喚醒的方式有很多種,如可以使用一個外部中斷專門用于喚醒平臺,使脫離低功耗模式,上述代碼只是一個簡單示例,要使用到實際中需要很多考慮,但是這給我們提供了鉤子函數的一種使用方式。

VxWorks提供的鉤子函數注冊和注銷如下。

① 任務創建鉤子函數注冊和注銷。

        STATUS taskCreateHookAdd
        (
        FUNCPTR createHook /* routine to be called when a task is created */
        )

參數為注冊的鉤子函數,其將在任何一個新任務創建時被調用,它必須具有的定義形式如下:即以新創建任務的結構為參數。鉤子函數可以這個代表新創建任務的結構做一些個性化的處理,如檢查優先級使其限定在特定的范圍等。

        void createHook
        (
        WIND_TCB *pNewTcb /* pointer to new task's TCB */
        )

taskCreateHookDelete用以注銷之前注冊的鉤子函數,其調用原型如下。

        STATUS taskCreateHookDelete
        (
        FUNCPTR createHook /* routine to be deleted from list */
        )

② 任務調度鉤子函數注冊和注銷。

        STATUS taskSwitchHookAdd
        (
        FUNCPTR switchHook /* routine to be called at every task switch */
        )

taskSwitchHookAdd用于注冊一個發生任務調度時調用的鉤子函數,該鉤子函數必須具有如下的定義形式。(其中,pOldTcb表示被調度出去的任務結構,而pNewTcb則表示被調度進來成為CPU新的使用者的任務結構。)

        void switchHook
        (
        WIND_TCB *pOldTcb, /* pointer to old task's WIND_TCB */
        WIND_TCB *pNewTcb /* pointer to new task's WIND_TCB */
        )

taskSwitchHookDelete用以注銷通過taskSwitchHookAdd添加的鉤子函數。其調用原型如下。

        STATUS taskSwitchHookDelete
        (
        FUNCPTR switchHook /* routine to be deleted from list */
        )

③ 任務消亡(刪除)鉤子函數注冊和注銷。

        STATUS taskDeleteHookAdd
        (
        FUNCPTR deleteHook /* routine to be called when a task is deleted */
        )

上述代碼用以注冊在任何一個任務消亡時被調用的鉤子函數。鉤子函數deleteHook必須具有如下的定義形式,參數是消亡的任務結構。

        void deleteHook
        (
        WIND_TCB *pTcb /* pointer to deleted task's WIND_TCB */
        )

taskDeleteHookDelete用以注銷之前通過taskDeleteHookAdd注冊的鉤子函數。其調用原型如下。

        STATUS taskDeleteHookDelete
        (
        FUNCPTR deleteHook /* routine to be deleted from list */
        )

除了這些注冊和注銷函數外,VxWorks同時還提供了三個信息顯示函數用于顯示當前注冊到系統中的所有鉤子函數。這三個函數的調用原型如下。

        void taskCreateHookShow (void)

顯示當前注冊到系統的所有在任務創建時被調用的鉤子函數列表。

        void taskDeleteHookShow (void)

顯示當前注冊到系統中所有在發生任務切換時被調用的鉤子函數列表。

        void taskSwitchHookShow (void)

顯示當前注冊到系統中的在任務消亡時被調用的鉤子函數列表。

2.1.7 任務小結

至此,我們完成對VxWorks任務的說明,VxWorks下的任務即通用操作系統下所說的進程是內核的基本運行單元,也是內核調度算法的處理單元。縱觀所有的操作系統,進程或者任務在底層上都由一個數據結構表示,這個數據結構一般稱為任務(或者進程)控制塊TCB(或者PCB),用以保存一個任務(或者CPU執行單元)的所有信息,這些信息中有些是保證一個任務可以被CPU運行的基本關鍵信息(如所有的寄存器值、內存映射),但很多是為管理需要而存在的“輔助”信息,這些“輔助”信息構成了某個操作系統的實現方式。

VxWorks任務給用戶提供了極大的靈活性,如果深入到任務控制結構這一層面(即內核編程和驅動編程),那么可以對任務執行其任何需要的操作,即便讓系統立刻崩潰也在所不惜。這也是底層編程提供的極大“娛樂性”。

VxWorks任務的幾個重要方面總結如下:

● 任務具有優先級,是任務調度運行的依據和獲取CPU資源的“競爭卡”,優先級越高,其獲得CPU資源的可能性就越大,VxWorks以0~255的數字表示優先級,數值越大,優先級越小,0表示最高優先級。任務優先級可以在任務運行過程中動態地改變,可以改變一個任務到任意優先級。

● 任務具有任務棧。任務棧的容量在任務創建時就被確定,而且其后不可進行更改。任務棧與表示任務的內核結構作為一段連續的區域進行分配。任務棧是任務運行過程中各種局部函數變量的存放地和函數調用參數傳遞的渠道。由于VxWorks下任務棧容量不可動態改變,故在創建任務時必須指定一個足夠大的容量,通常可以在函數調試階段通過checkStack統計出一個任務的棧使用情況,進而指定一個合理的任務棧容量,避免浪費較多的內存空間。

● VxWorks提供了一種機制可以在涉及任務的三個關鍵狀態變化出調用用戶注冊的鉤子函數,這三個狀態變化為任務初始創建時、任務被調度使用或放棄CPU時、任務消亡時。通過利用VxWorks提供的這種靈活性可以實現一些嵌入式平臺下有意義的策略。

● 每個任務在操作系統底層的實現上都是由一個數據結構表示的,通過直接更改這個數據結構中的某些字段值,可以控制任務某些運行行為,這在內核層編程時十分有效。VxWorks任務結構定義在taskLib.h頭文件中。感興趣的用戶可以查看VxWorks任務結構WIND_TCB的具體定義。

主站蜘蛛池模板: 彭阳县| 博兴县| 阿巴嘎旗| 区。| 丹阳市| 竹山县| 郎溪县| 玛多县| 焉耆| 隆子县| 德清县| 新沂市| 卢湾区| 常熟市| 定西市| 仁怀市| 壤塘县| 仪征市| 临西县| 古交市| 乌兰察布市| 且末县| 许昌县| 葵青区| 许昌市| 蒲江县| 黄骅市| 新宁县| 南郑县| 丰原市| 横峰县| 景宁| 进贤县| 甘德县| 河津市| 中江县| 阳西县| 瓮安县| 昭觉县| 广州市| 海晏县|