- 概述
- VxWork系統任務
- 任務調度
- 任務創建和管理
- 任務的錯誤狀態
- 任務異常處理
- 共享代碼和重入
概述
現代實時操作系統是基於多任務和任務間通信的概念的。多任務環境運行一個實時進程RTP可以被作為一系列相互獨立的任務集,每一個任務都有自己的執行線程和系統資源。任務是VxWorks調度的基本單元。所有任務,不管是在內核中,還是進程中,使用相同的調度(VxWorks進程本身不被調度)。
任務的概念與其他操作系統的線程概念比較類似。多任務為應用程序對多個孤立的實時事件的控制和反應對提供了基礎。在單核處理器(UP)系統中,多任務呈現了多個任務同時執行的表象,實際上,內核在調度策略的基礎上交替執行這些任務。
每個任務都有自己的上下文,即任務被內核調度運行時能訪問的CPU環境和系統資源。上下文切換時,認為的上下文被保存在任務控制塊中(TCB)。
TCB包含
- 執行的線程;即任務的程序計數器
- 任務的虛擬內存上下文(如果CPU支持且被包含)
- CPU寄存器和可選的協處理器的寄存器
- 動態變量和函數調用的棧
- 分配給標准輸入、輸出和錯誤的I/O
- 一個延時定時器
- 內核控制結構
- 信號處理器
- 任務的私有環境(環境變量)錯誤狀態(errno)
- 調試和性能監視值
如果,VxWorks禁用RTP的支持(INCULDE_RTP),任務上下文不包含它的虛擬內存。所有任務僅運行在單一地址空間(內核)。
然而,如果VxWorks啟用了RTP的支持,不管進程是否被激活,內核任務的上下文包含了虛擬內存上下文,因為系統有可能與內核之外的虛擬內存上下文打交道。即,系統可能有任務運行在幾個不同的虛擬內存上下文中(內核和一個或多個進程)。
任務的狀態和轉變
內核維護在系統中的任務的當前狀態。作為活動結果,任務將從一個狀態轉化為另一個狀態,如被應用程序調用的某種函數(如在信號量無法使用時,試圖獲取信號量)和使用開發工具如調試器。
在就緒狀態的最高優先級的任務將被執行。當任務用taskSpawn創建時,它們立即進入就緒狀態。
當任務通過taskCreate或帶有VX_TASK_NOACTIVATE選項參數的taskOpen函數創建時,它們被實例化為suspend狀態。稍后可以調用taskActivate來使它們進入就緒狀態。激活階段非常快,使得應用程序能以及時的方式創建和激活它們。
任務狀態
狀態 | 描述 |
READY | 任務除了等待CPU沒有等待其它任何資源。 |
PEND | 由於資源不可用,任務在等待資源。 |
DELAY | 任務在睡眠。 |
SUSPEND | 任務無法執行(不是PEND或DELAY)。這種狀態主要用於調試。掛起不會禁止狀態轉變,只是執行。因此,pended-suspended任務仍然不會阻塞, delayed-suspended任務能夠被喚醒。 |
STOP | 任務被調試器停止(也被錯誤檢測和報告功能使用) |
DELAY + S | 任務既處於delayed,也處於suspended |
PEND + S | 任務既PENDED也SUSPENDED |
PEND + T | 任務被擱置帶有超時值 |
STOP + P | 任務被擱置和停止(被調試器,錯誤檢測和報告功能,或SIGSTOP信號) |
STOP + S | 任務被停止和掛起 |
STOP + T | 任務被延時和停止(被調試器,錯誤檢測和報告功能,或SIGSTOP信號) |
PEND + S + T | 任務被擱置帶有超時值並掛起 |
STOP + P + S | 任務被調試器擱置,掛起和停止 |
STOP + P + T | 任務被調試器擱置帶有超時和停止 |
STOP + T + S | 任務被調試器掛起,延時和停止 |
ST + P + S + T | 任務被調試器擱置帶有超時,掛起和停止 |
state + I | 任務被state指定(任何狀態或上面狀態的組合)加上內在的優先級。 |
STOP狀態被調試器使用,當斷點被執行時。也可以被錯誤檢測和報告功能使用。
狀態轉變
VxWorks的系統任務
VxWorks在引導時啟動的系統任務依賴於配置,有些總是運行。任務集與VxWorks的基本配置相關,很少的任務常用於可選的組件。
注意:別掛起、刪除或改變任何系統任務的優先級。否則將導致不可預期的系統行為。
更多信息請看另外一篇隨筆
任務調度
多任務要求任務調度器分配CPU給就緒的任務。VxWorks提供了以下調度選項:
- 傳統的調度,提供基於優先級,搶占式及循環調度。
- VxWorks的POSIX線程調度,專門為RTP設計。
- 自定義調度框架,允許你開發自己的調度算法。
任務優先級
任務調度依賴於創建任務時指定的優先級。內核提供了256種優先級,0到255。0是最高優先級,255是最低優先級。在創建時指定了優先級,但是也可以在稍后編程修改它的優先級。
應用程序任務優先級
所有應用程序的任務優先級都在范圍100到255.
注意:網絡應用程序任務的優先級可以在100以內。
驅動任務優先級
相對與應用程序的任務優先級從100到255,它的任務優先級(與ISR相關的任務)可以是51到99。這些任務比較關鍵;舉個例子,如果從芯片拷貝數據的任務失敗,設備將丟失數據。驅動支持的任務包括tNet0,HDLC等。
tNet0的優先級是50,所以用戶不應該分配的優先級低於該任務。如果低於,則將導致網絡連接掛掉並阻止主機工具的調試功能。
VxWorks傳統調度器
傳統調度提供了基於優先級的搶占式調度及可選的可編程的循環調度。傳統調度可以參考original或native調度。
傳統調度默認被INCLUDE_VX_TRADITIONAL_SCHEDULER組件包含。
基於優先級的搶占式調度
當更高優先級的任務就緒時,當前任務被搶占。因此,CPU總是確保就緒的最高優先級的任務被執行。
這種調度策略的缺點是,多個相同優先級的任務必須共享處理器,如果單個任務永遠不阻塞,它可能霸占CPU。因此其它相同優先級的任務將永遠不會被執行。循環調度解決了這個辦法。
調度和就緒隊列
調度器維護了一個FIFO的隊列,該隊列包含了在系統中每個優先級的所有就緒的任務。當CPU可用時,排在前面的任務被調度。
任務在就緒隊列中位置可能改變,取決於以下因素:
- 如果認為被搶占,調度器運行高優先級任務,但是被搶占的任務仍然保留在該優先級列表中的前面的位置。
- 如果認為被擱置,延時,掛起或停止,它將從就緒列表中被刪除。當它隨后再次就緒時,它被放在該優先級列表的尾部。
- 如果優先級通過taskPrioritySet被改變,它被放在心優先級列表的尾部。
- 如果認為的優先級是基於互斥信號量優先級而臨時提高的,它執行之后將被放在它本來優先級的列表的尾部。
移動任務到優先級列表的尾端
taskRotate函數用於移動任務從優先級列表的前部到尾部。如taskRotate(100),它將把從100優先級列表中的前面的任務移到尾部。如果要移動本任務到尾部,則使用TASK_PRIORITY_SELF作為參數傳遞給taskRotate函數,而不是優先級的數值作為參數。該函數可用於替代循環調度。它可以控制相同優先級的就緒任務之間共享CPU,而不是讓系統以相同的間隔來決定。
循環調度
循環調度是對於基於優先級調度的擴展。循環調度運行相同優先級的任務共享CPU。循環調度通過時間片來共享CPU。相同優先級的一組任務中的每一個任務在交出CPU之前,都執行一定的時間間隔,或時間片。因此,它們中沒有哪個任務可以一直占有CPU。時間片用完則該任務將被放在該優先級列表中的尾部。
VxWorks的循環調度不像其它操作系統,它的任務是可以被更高優先級任務搶占的,更高優先級完成后,它繼續執行未完成的任務。
啟用循環調度
可以使用參數時間片來調用kernelTimeSlice啟用循環調度。如果參數為0,則禁用循環調度。
時間片計數和搶占
每個任務可以執行相同的時間片。如果循環調度啟用,搶占被執行的任務也啟用,系統的tick處理器將增加時間片計數。當指定的時間片用完,系統tick處理器將清0任務的時間片計數,任務被放在該優先級列表的尾部。啟用循環調度不影響任務上下文切換的性能,也不分配額外的內存。
如果任務被更高優先級任務搶占或阻塞,則它的時間片計數將被保存,並在繼續執行時,執行剩余的時間。由於任務被搶占,任務實際執行的時間比分配給它的時間片多一點或少一點都是可能的。
任務狀態錯誤碼
ANSI C標准中有errno一個全局整數值來存放錯誤碼。然而,errno也被定義成一個宏,對於VxWorks的所有函數來說,該宏定義為調用__errno()。而對於其它的函數來說,該宏定義為ANSI C標准中errno的地址,因此對於標准ANSI C的函數,該宏其實就是errno整數值。
在VxWorks,errno是一個預定義全局變量,可以被應用程序直接使用。然而,對於多任務的環境,每個任務必須訪問自己的errno。因此errno被內核作為任務的上下文保存和恢復。
類似的,中斷服務程序也有errno。
幾乎所有的VxWorks函數都遵守一個約定:通過返回值來指示函數成功或失敗。很多函數僅返回成功或失敗;有些函數通常返回非負值表示成功,返回ERROR表示失敗。返回指針的函數,通常返回NULL(0)表示失敗。很多情況,返回錯誤的情況下也設置了errno值來指明發生什么錯誤。
全局的errno永遠不會被VxWorks清除,因此,它的值總是指示最后發生的錯誤。當VxWorks函數調用別的函數發生錯誤時,它不會修改底層的errno,而是返回自己的錯誤,因此,你可以得到errno來查找底層發送的錯誤。建議你也使用這種機制。可以通過printErrno來顯示錯誤信息,如果errno有相應的字符串常量在錯誤狀態符號表中(statSymTbl)。
錯誤值的分配
errno使用最重要的2個自己來表示發生錯誤的模塊,用最不重要的2個自己來表示發生的錯誤。所有VxWorks模塊都在范圍1到500。帶有0模塊號的errno用於源碼兼容。
所有其他errno值(大於或等於(501<<16)正數和所有負數)都可以被應用程序使用。更多信息參考errnoLib類庫。
異常處理
程序的代碼和數據發生錯誤會引起異常條件如非法指令,總線或地址錯誤,被0整除等。VxWorks的異常處理包會處理這些異常。
如果為某個異常使用自己的異常處理,則通過signal會捕獲到異常,但默認的異常處理將被禁用。用戶自定義異常對於從災難中恢復是非常有用的。通常setjmp和longjmp會被使用。
共享代碼和重入
代碼被幾個任務同時調用而不會沖突,則該代碼是可共享的,也是可重入的。大多數Vxworks的函數是可重入,但是也有一些是不可重入的。如果函數有相應的_r版本的函數,則該函數是不可重入的,而_r函數是可重入的,如ldiv()函數有相應的ldiv_r(),所以ldiv是不可重入的,而ldiv_r()是可重入的。
I/O和驅動是可重入,但是要求應用程序小心設計。對於帶緩沖的I/O,風河公司建議使用基於每個任務的文件指針緩存。在驅動層,由於全局文件描述符表,很可能從不同的任務的流中加載到緩存。如報文驅動可能從不同的任務接收報文混合到流中,每個報文都有自己的目的地。
VxWorks使用了以下重入技術
- 動態棧變量 -- 沒有自己的數據,數據都是作為參數傳遞進來。
- 用信號量保護的全局和靜態變量
- 任務變量
注意:在有些情況無法重入。此時應該使用二進制信號量來保護,或者在ISR中使用intLock和intUnlock。初始化函數應該可以被調用多次,即使邏輯上應該只能被調用一次。作為規則,函數應該避免使用靜態變量來保存狀態信息。但是初始化函數是個例外;使用靜態變量來返回初始化函數成功與否是合適的。