- VxWorks設備驅動開發詳解
- 曹桂平等編著
- 2646字
- 2019-01-09 15:53:24
2.2 VxWorks任務調度算法——基于優先級的搶占式調度
VxWorks是實時操作系統,進程調度時機對其而言至關重要?;旧纤械牟僮飨到y的進程調度時機都選擇在同一個地方:從內核狀態退出之時。從內核狀態退出時主要有兩個渠道:系統調用和中斷。外部中斷可有多個源,一般操作系統只是使用系統時鐘中斷作為固定的進程調度時刻點,而其他中斷源以及系統調用由于其執行時機的不確定性,只是作為進程調度觸發的輔助手段。實時操作系統與通用操作系統區別的關鍵點在于前者必須能夠迅速地響應外部中斷,所以,實時操作系統只是在響應速度上更快,而在響應時機上與通用操作系統并無區別。中斷響應速度即中斷產生到對應中斷處理程序的調用之間的延遲時間。實時操作系統將這個延遲盡量減少到最小。
一個理解的誤區是系統時鐘頻率對實時性起決定性影響,事實并非如此,例如,VxWorks系統時鐘間隔1ms量級,而現在絕大多數通用操作系統的系統時鐘間隔也是在1ms量級,并非時鐘間隔越小越好。當時鐘間隔過小時(如間隔為0.5ms時),操作系統將花費大量的時間用于處理進程的調度,這樣會嚴重影響整個系統的工作性能。注意:系統時鐘是進程調度的脈搏。
另一個誤區是將系統時鐘與CPU主頻混為一談。CPU主頻是指CPU內硬件單元處理速度,一般以每秒內執行的指令數為指標參數。CPU主頻越高,系統的實時性越好,因為可以在更快的時間內執行完過渡指令,從而使得中斷響應的時間更少。所以,系統實時性一方面由操作系統決定,另一方面由操作系統運行的平臺決定,或者更進一步,由平臺上的CPU主頻決定。
VxWorks操作系統與通用操作系統區別的一個顯著特點是其不劃分運行態的級別。應用層程序可以直接調用內核函數,而不需要通過軟中斷的方式從應用層進入到內核層。故VxWorks下系統調用的概念與通用操作系統(如Linux)有本質上的差別。下面的代碼分別演示了在VxWorks和Linux下一個應用層函數(userRtn)是如何調用一個內核層函數(kernelRtn)的。
VxWorks下系統調用:
void userRtn(){ int param; … //prepare param //直接調用kernelRtn函數 kernelRtn(param); }
Linux下系統調用(_NR_kernelRtn表示kernelRtn對應的系統調用號):
void userRtn(){ long _res; int param; … //prepare param //使用軟中斷進入內核態,進行kernelRtn函數的調用(以Intel x86體系為例) _asm_ volatile ( "int $0x80" \ :"=a"(_res) \ :"0"(_NR_kernelRtn),"b"(param) \ ); }
VxWorks下所謂的內核態僅僅由一個內核布爾變量kernelState表示。當kernelState設置為TRUE時,表示此時代碼運行在內核狀態。VxWorks內核態的本質即保護內核數據結構,防止多處代碼對內核數據結構同時進行訪問,所以它不同于通用操作系統內核態的概念。通用操作系統內核態是為了保護內核數據結構不受應用層的影響。所以,VxWorks內核態更多的是一個信號量的概念。如下代碼顯示了kernelState的作用,從而可以看出內核態的基本含義:防止多處代碼對內核數據結構的同時操作。
if (kernelState) /@ defer work if in kernel @/ { workQAdd1 (windResume, tid); /@ add work to kernel work q @/ return (OK); } kernelState = TRUE; /@ KERNEL ENTER @/ … /@ OPERATE ON KERNEL STRUCTRUES @/ windExit (); /@ KERNEL EXIT @/
當代碼需要對內核數據結構進行操作時,其首先檢查kernelState的狀態,如果kernelState設置為TRUE,則表示當前已經有代碼正在操作內核數據結構,故將當前代碼要做的工作加入到內核工作隊列中,稍候再完成。
可以說,內核工作隊列以及任務隊列構成了VxWorks內核的架構,基本上內核代碼都是對各種隊列的操作,所以,上文中所說的VxWorks內核數據結構主要就是指這些內核隊列。VxWorks內核態的進入通過簡單地設置kernelState變量值為TRUE完成,而退出內核態則是由windExit函數完成,該函數除了將kernelState設置為FALSE之外,完成的另外兩個重要工作就是運行內核工作隊列中延遲的工作以及進程調度。進程調度函數(reschedule)在windExit中被調用用以調度更高優先級的進程運行。而windExit函數被調用的時機則是從內核態退出,而中斷和系統調用則是進入內核態的唯一手段,所以從這個意義上說,VxWorks進程調度的時機即在系統調用和中斷發生之時。
更進一步,中斷一定會引起進程調度,系統調用在絕大多數情況下也會引起進程調度。系統調用并不總是能夠引起進程調度,因為可能調用的內核函數較為簡單,不需要對內核共享數據結構進行操作,此時就不會執行windExit函數。
中斷可以分為時鐘中斷和其他硬件中斷。時鐘中斷即以固定時間間隔產生中斷,這個中斷專門用于系統定時器和進程調度。任務狀態的改變主要由時鐘中斷觸發。其他硬件中斷則是嵌入式系統的關鍵組成部分,用以控制特定設備。對這些硬件中斷的實時響應可以說是VxWorks嵌入式系統的主要功能。一個嵌入式系統可以沒有任務(當然除了Idle任務外,這是VxWorks操作系統自帶的后臺進程),但是不可以沒有硬件中斷。
下面以ARM處理器為例詳細介紹時鐘中斷的注冊和執行流程,這個中斷是系統脈搏,了解它對于整體掌握VxWorks操作系統將有很大幫助,這對于在嵌入式平臺下實現一些有意義的策略也很有啟示。在后文“中斷處理”一節中,我們還將對這些內容進行更詳細的講解。
時鐘中斷的注冊在VxWorks操作系統初始化過程中完成,這主要由在userRoot中調用的如下三個函數完成。
sysClkConnect ((FUNCPTR) usrClock, 0); /* connect clock ISR */ sysClkRateSet (SYS_CLK_RATE); /* set system clock rate */ sysClkEnable ();
其中,sysClkConnect完成時鐘中斷程序的注冊。事實上,傳遞給sysClkConnect函數的userClock函數并非是直接中斷響應函數。ARM處理器支持兩類中斷:普通中斷irq和快速中斷fiq。VxWorks操作系統只使用了irq。
對于一個嵌入式平臺而言,單個中斷源顯然不夠用,故實際上各種中斷都是以irq的形式存在的。VxWorks內核維護一個irq中斷的總入口中斷處理程序,再由這個總入口中斷處理函數根據中斷向量號進一步調用對應中斷處理程序。
時鐘中斷向量號一般設置為0。時鐘中斷對應的中斷響應函數為sysClkInt,這是在sysHwInit2 函數中完成注冊的。事實上,sysHwInit2 是在sysClkConnect中被調用的。sysClkConnect調用sysHwInit2注冊sysClkInt作為時鐘中斷響應函數,基本代碼如下:
void sysHwInit2(){ … (void)intConnect ( INUM_TO_IVEC(INT_TINT0), sysClkInt, 0); intEnable ( INT_TINT0 ); … }
而后,sysClkConnect將userClock(sysClkConnect的參數)作為二次函數調用注冊到VxWorks內核中。所謂二次函數調用,即當發生一個時鐘中斷時,sysClkInt作為時鐘中斷響應函數將首先被調用,而sysClkInt又進一步調用userClock函數。userClock函數定義在userConfig.c中,其代碼如下:
void usrClock () { tickAnnounce (); /* announce system tick to kernel */ }
從以上代碼可以看出,userClock直接調用tickAnnounce函數作為響應。
tickAnnounce函數主要完成如下工作:
① 對vxTick變量做加1運算。vxTick表示系統自啟動之時到現在的tick數,所以用vxTick乘以系統時鐘間隔就是開機時間。
② 對處于等待狀態的任務進行檢查,將超時任務(那些調用taskDelay延遲的任務)重新設置為ready狀態,并轉移到調度隊列中。
③ 遍歷內核工作隊列,對延遲的內核工作進行執行。
④ 進程調度。選擇最高優先級任務作為當前任務運行。
時鐘中斷注冊過程總結如下:
userConfig.c:userRoot()→arm_timer.c:sysClkConnect()→sysLib.c:sysHwInit2()
其中,arm_timer.c為ARM平臺BSP中的定時器驅動文件。如前文所述,sysClkConnect函數調用sysHwInit2,將sysClkInt函數注冊為時鐘中斷處理函數,同時將作為參數傳入的userClock函數作為二次函數調用注冊到VxWorks內核中。(注:二次函數調用即被sysClkInt調用執行。)
時鐘中斷執行流程總結為:時鐘中斷產生→VxWorks內核IRQ總入口中斷處理函數→時鐘中斷處理函數(sysClkInt)→userClock函數→tickAnnounce函數。