1. 程式人生 > >VxWorks/MIPS中斷處理機制

VxWorks/MIPS中斷處理機制

在《中斷處理》中,梳理了中斷處理的一些通用框架和概念,下面我們來探討一下VxWorks/MIPS具體平臺下的中斷處理機制。時鐘中斷作為最高優先順序的中斷,其處理不失一般性,可作為分析理解VxWorks中斷處理機制的切入點。

所謂“硬體搭臺,軟體唱戲”,在展開具體軟體實現之前,先釐清既有的硬體資源支援,是本人一貫秉承的思維習慣。本文以VxWorks中的tick起源問題為引子,沿著“MIPS中斷資源->SoC硬體Timer配置->VxWorks時鐘中斷掛接->VxWorks中斷排程->tick脈 動”的線索鋪開,試圖系統闡述VxWorks/MIPS下的中斷掛接處理機制。

通用中斷處理框架包括了進入ISR之前的很多進入路徑

(entry path),MIPS中斷(異常)處理步驟大概如下:設定或遮蔽相關暫存器;進入異常入口點取指;現場保護;異常分類處理;查詢中斷向量表呼叫ISR。這些進入路徑是跟硬體預設定和作業系統記憶體管理佈局緊密相關,很多都是用匯編語言寫就。限於時間、精力和資源,本文沒有去深究這些底層路徑的完整實現,而是站在開發者的角度,重點關注日常工作中需要動手操刀的介面部分及周邊實現。

1 MIPS異常

在MIPS中,中斷、陷進、系統呼叫和任何可以中斷程式正常執行流的情況都稱之為異常。典型的MIPS R4k及以後的處理器用同一個中斷入口地址處理冷啟動和熱啟動,因此係統重置通常被看作是一種異常。

(1)精確異常


《MIPS體系結構透視》中將MIPS的異常機制稱為“精確異常”(precise exception)。何為精確異常呢?由於異常是在執行指令時同步發生,因此在造成異常的指令之前執行的指令,無疑均是有效的。然而,由於MIPS的高度流水線設計,在引發異常的指令執行時,後面一條指令已經完成了讀取和譯碼的預備工作,萬事俱備,只待ALU部件空閒即執行之。當異常產生時,這些預備工作便被廢棄。CPU從異常中返回時,再重新做讀取和譯碼的工作,因此可以保證,在異常發生時,異常指令之後所有的指令均不會被執行。這樣,就不需要在MIPS的異常處理例程(Exception Handler)中為延遲槽(Delay Slot)指令而煩惱了。但MIPS精確異常的代價是非常高的,因為它限制了流水線的深度。這對FPU影響很大,因為浮點運算通常需要更多的流水線階段。

(2)異常向量:異常處理開始的地方

所有的異常入口點,都位於MIPS記憶體對映不需要地址轉換的區域——非快取的kseg1和快取的kseg0段。當C0_SR:BEV=1置位時,ROM/Flash中非快取的異常入口是固定的:R_VEC=0xBFC00000,BEV_VEC=0xBFC00380。當C0_SR:BEV=0清零時,C0_EBase暫存器可以通過程式設計配置異常入口基地址(Exception entry point base addres),從而實現異常向量的整體移動。BASE預設為K0BASE=0x80000000,可通過C0_EBase暫存器改變。

(3)異常處理步驟(Exception Handle Procedure)

產生異常時MIPS CPU所要做的工作:

<1>.設定EPC,指向重新啟動的位置

<2>.設定C0_SR:EXL位迫使CPU進入核心模式(高特權級)並且禁用中斷

<3>.設定C0_CAUSE暫存器,使得軟體能看到異常原因。地址異常時,也要設定BadVAddr暫存器。儲存管理系統異常還要設定一些MMU暫存器。

<4>.CPU從異常入口點取指執行。例如,啟動後(C0_SR:BEV=0),所有其他異常的入口點(excNormalVec=0x80000180)

MIPS異常處理例程都需要經過以下步驟:

<1>.儲存現場:在異常處理例程入口,需要保護被中斷的程式的現場,儲存暫存器的狀態,保證關鍵狀態不被覆蓋。一般用k0和k1這兩個暫存器索引一塊記憶體區域,用來儲存其他的暫存器。這塊記憶體區域一般被稱作中斷棧(Interrupt Stack),用於儲存暫存器狀態,並且支援複雜的C等高階語言編寫的異常處理例程。

<2>.處理異常:根據C0_CAUSE:ExcCode確定發生了什麼型別的異常,然後呼叫OS定義的不同的函式處理。假設此時再其他異常的入口點(excNormalVec=0x80000180),若確定了是中斷(C0_CAUSE:ExcCode=0),則進一步呼叫中斷處理函式excIntStub;否則呼叫excStub。

<3>.準備返回:恢復現場,修改C0_SR,設定成安全模式(核心態,禁止異常),也就是異常發生後的模式。

<4>.從異常返回:控制權交給異常victim指令,將核心特權級恢復為較低的特權級。MIPS CPU提供了原子指令“eret”做這個工作:既清除C0_SR:EXL位,也將控制權返回給儲存在EPC中的地址。

2 MIPS中斷

CPU核外部的事件,即從一些真正的“硬體連線”過來的輸入訊號,這些就是中斷。中斷用於使CPU的注意力轉向某外部事件:OS的一個基本特徵就是可以同時注意多個事件。中斷是唯一獨立於CPU正常指令流的異常條件

MIPS CPU的協處理器0(CP0)主要完成對CPU、快取控制、異常/中斷控制、儲存管理單元控制和其他一些功能配置。MIPS CPU對中斷的支援涉及CP0的兩個重要的暫存器:狀態暫存器(C0_SR)和原因暫存器(C0_CAUSE)。

(1)使能全域性中斷(IE:Interrupt Enable)

要想使能中斷,則全域性中斷位C0_SR:IE必須置1,它是一個全域性開關。

(2)中斷使能遮蔽(IM:Interrupt Mask)

C0_SR[15~8]為中斷遮蔽位,對應IM[7~0],這8個bit位決定了哪些中斷源有請求時可以觸發一個異常,實際上是對中斷訊號的使能開關。8箇中斷源中的6個(IM[7~2])可用於外部硬體裝置中斷,其他2個(IM[1~0])對應C0_CAUSE:IP[1~0],為軟體中斷遮蔽位。所謂中斷源就是產生硬中斷訊號的PIC外接裝置或者軟中斷。

(3)異常級別(EXL:Exception Level)

異常發生後,CPU立即設定C0_SR:EXL,進入異常模式。異常模式強制CPU進入核心特權級模式並遮蔽中斷,而不會理會C0_SR其他位的值。EXL位在已設定的情況下,還沒有真正準備好呼叫主核心的例程。在這種狀態下,系統不能處理其他異常。保持EXL足夠的時間儲存現場,使軟體決定CPU新的特權級別和中斷遮蔽位應該如何設定。

(4)異常型別(ExcCode:Exception Code)

PIC每個輸入引腳上的有效(IM位為1)輸入每個週期都會被取樣,如果被使能,則引起一個異常。異常處理程式檢查到C0_CAUSE:ExcCode=0,則說明發生的異常是中斷,此時將進入通用中斷處理程式。

(5)中斷掛起反饋(IP:Interrupt Pending)

C0_CAUSE[15~8]為中斷掛起狀態位,用於指示哪些裝置發生了中斷,具體來說就是識別PIC的哪個接入引腳對應的裝置發來了中斷訊號。IP[7~2]隨著CPU硬體輸入引腳上的訊號而變化,而IP[1~0]為軟體中斷位,可讀可寫並存儲最後寫入的值。當C0_SR:IM[7~0]某些位使能,且硬中斷或軟中斷觸發時,C0_CAUSE:IP[7~0]對應位將被置位,一般通過查詢C0_CAUSE:IP[7~2]的pending位,確定哪個(些)裝置發生了中斷。

(6)中斷處理步驟(Interrupt Handle Procedure)

中斷是異常的一種,所以中斷處理只是異常處理的一條分流。經過上一層異常處理例程處理後,進入excIntStub。其處理步驟如下:

<1>.將C0_CAUSE:IP與C0_SR:IM進行邏輯與運算,獲得一個或多個活躍且使能的中斷請求。

<2>.選擇一個活躍且使能的中斷來處理,優先處理最高優先順序的中斷。

<3>.儲存C0_SR:IM中的中斷遮蔽位,不過很可能在上一層異常處理例程(excNormalVec)中已經儲存過。改變C0_SR:IM,以保證禁止當前中斷以及所有優先順序小於等於本中斷的中斷在處理期間產生。

<4>.對於巢狀異常,如果在上一層異常處理例程中沒有保護現場,則此時需要保護現場。

<5>.修改CPU到合適的狀態以適應中斷處理程式的高層部分,這時通常允許一些巢狀的中斷或異常。

設定全域性中斷使能C0_SR:IE位,以允許處理高優先順序的中斷。還需要改變CPU特權級域(C0_SR:KSU),使得CPU處於核心態,清除C0_SR:EXL以離開異常模式,並把這些改動反映到狀態暫存器中。

<6>.呼叫裝置驅動程式註冊的中斷處理例程ISR。

<7>.恢復現場,retore相關暫存器,繼續被中斷的任務。

3 VxWorks/MIPS異常向量初始化

(1)位於ROM非快取(kseg1)中的異常向量初始化

《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\romMipsInit.s定義了上電覆位時(C0_SR:BEV=1)ROM中的異常入口點。

#define RVECENT(f,n) \
	b f; nop
#define XVECENT(f,bev) \
	b f; li k0,bev

promEntry:
romInit:
_romInit:
	/* MIPS_VECTOR_TABLE */
	RVECENT(__romInit,0)		/* PROM entry point */
	RVECENT(romReboot,1)		/* software reboot */
	RVECENT(romReserved,2)
	...
	RVECENT(romReserved,63)
	XVECENT(romExcHandle,0x200)	/* bfc00200: R4000 tlbmiss vector */
	...
	RVECENT(romReserved,95)
	XVECENT(romExcHandle,0x300)	/* bfc00300: R4000 cache vector */
	...
	RVECENT(romReserved,111)
	XVECENT(romExcHandle,0x380)	/* bfc00380: R4000 general vector */
	...
	/* We hope there are no more reserved vectors!
	 * 128 * 8 == 1024 == 0x400
	 * so this is address R_VEC+0x400 == 0xbfc00400
	 */
《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\romInit.s中定義了romExcHandle(),其中k0為exception type,例如k0=0x380表示BEV_VEC。

/* romExcHandle- rom based exception/interrupt handler */

可見,ROM的前1KB安排給了中斷向量。

(2)位於RAM快取(kseg0)中的異常向量初始化

usrInit()->excVecInit()中,將excTlbVec()、excNormVec()等異常處理程式碼分別拷貝到0x80000000、0x80000180地址。可參考《VxWorks Source Code》,其註釋中說“Allvectors from vector 0 (address 0x0000) to 255 (address 0x07f8) are initialized.”, 從RAM的底端從0x80000000到0x800007f8(大約4KB)安排給了中斷向量,所以我們通常將RAM_LOW_ADRS設定為0x80001000

作業系統可呼叫sysToMonitor()跳轉到ROM中的romReboot實現熱啟動。熱啟動實現了軟體重啟,不用重新初始化SDRAM等裝置;冷啟動則一切從頭再來。

4 VxWorks/MIPS中斷初始化

關於MIPS啟動,可參考《MIPS體系結構透視》5.9節<啟動>。

在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\V100R001CPE.h中定義了初始SR配置和預設SR配置:

#if (_BYTE_ORDER == _BIG_ENDIAN)
	/* initial status register */
	# define INITIAL_SR	(SR_CU0 | SR_BEV) 
	/* default status register at task level */
	# define DEFAULT_SR	(SR_CU0 | SR_IMASK0 | SR_IE)
#else
#endif

在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\sysLib.c的sysHwInit()中初始化先禁掉全域性中斷使能(SR_IE)。

/* init status register but leave interrupts disabled */
intSRSet (DEFAULT_SR & ~SR_IE);
/* !should clear IE bit, or you can not enter usrRoot! */
taskSRInit (DEFAULT_SR & ~SR_IE);

然後待硬體裝置初始化就緒,在sysHwInit2()中使能全域性中斷和8箇中斷源。

taskSRInit(DEFAULT_SR);
關於SR_CU0、SR_BEV和SR_IMASK0

在《vxworks 6.x 的全部標頭檔案》的h/arch/mips/archMips.h中可以找到它們的定義。

SR_CU0表示Coprocessor 0 usable,對CU0置位會得到使用者特權級別的程式。

SR_BEV為啟動異常向量(bootexception vectors),當BEV==1時,CPU用ROM(kseg1)空間的異常入口。啟動之初,INITIAL_SR中置位BEV;啟動之後,DEFAULT_SR中將BEV置零。

SR_IMASK0表示mask level 0,C0_SR[15~8](IM[7~0])全部置1使能。啟動之後,DEFAULT_SR中使能SR_IE,然後設定SR_IMASK0,使能8箇中斷源響應。

在講述定時器中斷的相關處理之前,先來熟悉一下頻率和時間的概念。

5 主頻=外頻x倍頻

CPU內部沒有振盪器,而是依靠外部的晶振電路(ReferenceCrystal Oscillator Frequency Synthesizer)提供基準時鐘訊號,這個外部晶振提供給CPU的時鐘頻率稱之為外頻(externalclock)。可以將晶振時鐘源看作是SoC系統的心臟,只有心臟跳動起來,SoC才有脈搏進而驅動數字邏輯掉路。

為了降低電磁干擾和降低板間佈線要求,晶片外接的晶振頻率通常很低(比如25MHz),可以通過鍾控制邏輯的鎖相環PLL(Phase Lock Loop)提高系統時鐘,即我們通常所說的倍頻技術。

CPU主頻率就是CPU流水線頻率(the CPU’s pipeline clock rate),即每秒能夠處理的指令條數,它決定了處理器的運算速度。CPU的主頻可由晶振提供的頻率經過CPU內部的PLL倍頻得到,一般有主頻=外頻×倍頻。同樣,前端匯流排(FSB)的頻率也是這麼得到的。

以下給出一種可能的SoC頻率配置。假設Reference Crystal Oscillator Frequency=25MHz,降頻係數Mdiv=1,則參考頻率fref=25MHz。假設倍頻係數為32,則VCO frequency為800MHz。假設Ndiv=2,則分頻後的PLL frequency=400MHz。

s3c2440的時鐘詳解》中給出了S3C2440A datasheet中一段關於FCLK/AHB/APB的描述:

--------------------------------------------------------------

The Clock control logic in S3C2440A can generate the required clock signals including FCLK for CPU, HCLK for the AHB bus peripherals, andPCLK for the APB bus peripherals.

FCLKCPU用的;HCLKAHB匯流排用的,該slave其服務的master包括SDRAM/USB/MAC/DMA;PCLKAPB匯流排用的,該slave服務的master包括SPI Flash/ROM/GPIO/UART/hardware timer/watchdog timer。

--------------------------------------------------------------

假設CPU的分頻係數(division ratio)為1,FCLK=MPLL=400MHz;假設AHB的分頻係數為2,則HCLK=FCLK/2=200MHz。

CPU上的計時器:高精度時間戳部件(Time Stamp Counter,TSC)

高精度時間戳主要用於剖析和監測程式碼,在某些應用場合,例如基於令牌桶的IP頻寬控制中的時間片度量可能微秒級別的計時解析度,此時需要高精度時鐘戳支援。

Intel Pentium級以上的CPU都提供了時間戳計數器部件,用於記錄自啟動以來處理器消耗的時鐘週期數。MIPS CPU提供了計數/比較暫存器(Count/Compare),這兩個暫存器提供一個簡單的連續執行的通用內部計數器,可以通過程式設計來導致中斷。Count是一個不停計數的32位暫存器,計數的頻率與CPU流水線的時鐘週期相同,也可以是一半或是其他分頻比例。

6 初始化硬體Timer

核心的許多工作都高度依賴於時間資訊,例如中斷底半部基於時間的任務延期處理。這種將在一定時間之後發生的事情就是典型的超時應用,而定時則主要是基於高分辨的應用。

上面梳理了頻率和時間的概念,下面來看一下時間控制系統的底層硬體支援。典型的系統都會有提供定時功能的時鐘晶片。例如IA-32和AMD64系統有一個PIT(Programmable Interrupt Timer,可程式設計中斷計時器,由8235晶片實現)。微控制器MCU和嵌入式SoC晶片往往會提供多個通用hardware timer和watchdog timer。VxWorks一般在BSP標準例程sysLib.c\sysHwInit()中配置硬體Timer暫存器引數。

假設CPU Clock Frequency為400MHz,Peripheral APB Bus Clock Frequency為200MHz,若將分頻暫存器Division(有的晶片有這個Timer Control暫存器)配置為200,則Timer的頻率為1MHz,計時週期為1us。接下來要配置計時暫存器(Timer Value),它將決定timer發起中斷的頻率。這裡涉及到一個重要的引數——SYS_CLK_RATE,通過usrRoot()中的sysClkRateSet()設定,它表示每秒的時間片數量(ticksPerSecond)。預設=SYS_CLK_RATE=60,則timeout=1MHz/sysClkRateGet()=1/60MHz將配置到計時暫存器,每次timeout時將發起一次中斷,每秒產生60次時鐘中斷。時鐘中斷的間隔為1/60s,這個時間間隔就是VxWorks下的時鐘嘀嗒——tick。

在概念上,VxWorks中的SYS_CLK_RATE對應Linux中的HZ,VxWorks中的tick對應Linux中的jiffy(jiffies)。

7 掛接定時器中斷

(1)為定時器中斷分配IRQ向量號

《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\au1500Int.c中定義了PRIO_TABLE intPrioTable[]:

#define IV_TIMER_VEC    70
#define IV_UART0_VEC    90

typedef struct 
{
	ULONG	intCause;		/* cause of interrupt	*/
	ULONG	bsrTableOffset; /* index to BSR table	*/
	ULONG	statusReg;		/* mask bit */
	ULONG	pad;			/* intNum or demux routine? */
} PRIO_TABLE;

PRIO_TABLE intPrioTable[] = 
{
	{CAUSE_SW1,(ULONG) IV_SWTRAP0_VEC,	0x0100, 0}, /* sw trap 0 */
	{CAUSE_SW2,(ULONG) IV_SWTRAP1_VEC,	0x0200, 0}, /* sw trap 1 */
	{CAUSE_IP3,(ULONG) sysCtrl0Req0IntDemux,0x0400, 1}, /* DeMultiplex */
	{CAUSE_IP4,(ULONG) sysCtrl0Req1IntDemux,0x0800, 1}, /* DeMultiplex */
	{CAUSE_IP5,(ULONG) sysCtrl1Req0IntDemux,0x1000, 1}, /* DeMultiplex */
	{CAUSE_IP6,(ULONG) sysCtrl1Req1IntDemux,0x2000, 1}, /* DeMultiplex */
	{CAUSE_IP7,(ULONG) IV_HW4_VEC,		0x4000, 0}, /* Available */
	{CAUSE_IP8,(ULONG) IV_TIMER_VEC, 	0x8000, 0}  /* Timer share */
};
PRIO_TABLE::intCause為cause ofinterrupt,對應C0_CAUSE:IP[7~0],PRIO_TABLE::statusReg為其掩碼位。

intPrioTable[0~1]軟中斷,對應C0_CAUSE:IP[1~0]。intPrioTable[2~5]中的PRIO_TABLE::pad均為1,則說明為中斷共享,PRIO_TABLE::bsrTableOffset存放的不是intNum,而是demux routine。所謂demux就是解複用。intPrioTable[6~7]中的PRIO_TABLE::pad為0,則說明為普通中斷。intPrioTable[]是硬體相關的,外設中斷源與C0_CAUSE:IP[7~2]的對應關係取決於硬體中斷源與PIC輸入引腳的連線順序。

VxWorks一般在sysHwInit()中初始化IRR暫存器,將所有SoC硬體定義的中斷源(Interrupt Source)對映到控制暫存器CAUSE_IP[7~2]這六位。intPrioTable[]體現了IRR建立的硬體中斷源(device)與CPU中斷使能/狀態位(C0_CAUSE:IP[7~2])的對映關係,同時建立了CPU中斷使能/狀態位(C0_CAUSE:IP[7~2])與IRQ向量號(vector)之間的對映關係。

(2)掛接定時器中斷到中斷向量表

<1>中斷向量表——excBsrTbl[]

intPrioTable[7]定義了Timer的中斷對映,PRIO_TABLE::intCause=C0_CAUSE:IP[7]為中斷源編號,PRIO_TABLE::bsrTableOffset=IV_TIMER_VEC為IRQ向量號(vector),它是BSR table的索引(index to BSR table)。那麼什麼是BSR table呢?這裡的BSRtable就是通常所說的中斷向量表。

在VxWorks 5.5的原始碼excArchLib.c中可以看到excBsrTbl的定義:

VOIDFUNCPTR excBsrTbl[] = 
{
	excIntHandle,  /* 0 - interrupt exception */
	excExcHandle,  /* 1 - tlb mod exception */
	...
	excIntHandle, /* 70 - timer 0 interrupt */
	excIntHandle, /* 71 - timer 1 interrupt */
	...
	excIntHandle, /* 90 - uart 0 interrupt */
	excIntHandle, /* 91 - uart 1 interrupt */
	...
	excIntHandle, /* 255 */
};
基本上,excBsrTbl[256]是一個函式指標陣列,初始化元素只有excExcHandle、excIntHandle兩種選項,它們是對沒有安裝處理程式的異常和中斷的預設處理。此時針對timer0的預設處理是excIntHandle。除了前面預留的一些向量位置,可安裝裝置IRQ處理程式到其他向量表槽位。

<2>掛接定時器時鐘中斷

IV_TIMER_VE意即TimerInterrupt Vector,除了在intPrioTable中看到了它,在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\sysLib.c中可以看到sysHwInit2()中的intConnect()呼叫也涉及到了IV_TIMER_VE。

sysHwInit2()
{
	
	/* connect sys clock and aux clock interrupts */
	(void) intConnect (INUM_TO_IVEC(IV_TIMER_VEC), sysClkInt, 0);
	
}
顧名思義,intConnect意即中斷掛接,有點類似Linux中的request_irq。那麼這個中斷到底掛接到哪裡去了,當發生時鐘中斷時,是如何排程到sysClkInt()的呢?

檢視intArchLib.c找到了intConnect()原型:

/*
* intConnect - connect a C routine to a hardware interrupt
*
* This routine connects a specified C routine to a specified 
* interrupt vector.  The address of <routine> is stored at <vector>
*/
STATUS intConnect
    (
    VOIDFUNCPTR *vector,	/* interrupt vector to attach to */
    VOIDFUNCPTR routine,	/* routine to be called */
    int parameter		/* parameter to be passed to routine */
    )
{
    FUNCPTR intDrvRtn = intHandlerCreate ((FUNCPTR) routine, parameter);
    
    if (intDrvRtn == NULL)
    	return (ERROR);
    
    /* make vector point to synthesized code */
    intVecSet ((FUNCPTR *) vector, (FUNCPTR) intDrvRtn);
    
    return (OK);
}
在intConnect()中,首先通過intHandlerCreate()為使用者提供的回撥函式與指標分配(malloc)一小塊記憶體。這塊記憶體中存放的是5條指令(intConnectCode),用於儲存中處理函式與引數的地址,以及一條跳轉到該中斷處理函式地址的指令。然後,呼叫intVecSet()將這段記憶體的地址設定到excBsrTbl[vec],其地址為(int)excBsrTbl+vec*4,巨集INUM_TO_IVEC實現了右移2。intVecSet()有點類似Linux中的set_except_vector()。

sysHwInit2()呼叫intConnect()後,excBsrTbl[IV_TIMER_VEC]=sysClkInt()。

<3>時鐘中斷處理例程

intConnect()指定timer0的ISR為sysClkInt(),其中呼叫sysClkRoutine。

VxWorksBSP for AMD's AU1500(MIPS)》的V100R001CPE\bootConfig.c中定義了usrRoot(),其中呼叫sysClkConnect()指定了最終的時鐘中斷處理例程。

STATUS	sysClkRateSet (int ticksPerSecond);
	int ticksPerSecond  /* number of clock interrupts per second */

usrRoot()
{

	/* set up system timer */
	sysClkConnect ((FUNCPTR) usrClock, 0);	/* connect clock ISR */
	sysClkRateSet (SYS_CLK_RATE);		/* set system clock rate */
	sysClkEnable ();			/* start it */

}

sysClkConnect()指定sysClkRoutine為usrClock()。sysClkEnable()使能中斷後,每隔1個tick,硬體timer發起一次中斷,最終回撥usrClock()響應時鐘中斷。

8 VxWorks中斷排程

(1)中斷響應與分發

usrInit()->excVecInit()中,將excTlbVec()、excNormVec()等異常處理程式碼分別拷貝到0x80000000、0x80000180地址。(這裡的excVecInit()對應於Linux中的trap_init(),excNormVec對應Linux中的except_vec3_generic。)

中斷到來時,在入口點excNormVec(0x80000180)中會根據CAUSE_ExcCode位判斷是否為中斷型別的異常(ExcCode位0),如果否則跳轉到excStub;如果是則跳轉到excIntStub()(對應於Linux中的handle_int()->irq_dispatch()->...->do_IRQ()),在該函式除了儲存以及恢復中斷現場之外,主要做了以下工作:

<1>. 通過CAUSE和SR判斷產生中斷的中斷源;

<2>. 將CAUSE[IP0~7]的值作為sysHashOrder表(ffsMsbTbl[256])的下標,可以得到優先執行的中斷源號碼;

<3>. 將以上中斷源號碼作為intPrioTable[]表的下標,可以得到該中斷源對應的異常向量偏移vec(相對於excBsrTbl);(這裡的excBsrTbl對應Linux中的exception_handlers。)

<4>. 跳轉到以上偏移地址處(excBsrTbl+vec*4)所儲存的值的地址,可以得到5條執行時(通過intConnect())構建的指令;

<5>. 這5條指令中包含了intConnect()執行時所註冊的中斷處理函式及其地址,並跳轉到該函式去執行;


(2)中斷處理流程

Linux中的struct irq_desc irq_desc[NR_IRQS]相當於intPrioTable[],這兩個管理IRQ的全域性陣列的size為硬體支援的中斷源數量,MIPS中為8,IA-32中為16。

Linux對於共享的IRQ設定irq_desc::irqaction::flags=IRQF_SHARED,然後通過irq_desc::irqaction::next指標將IRQ處理程式鏈化。當有對應IRQ中斷髮生時,將呼叫irq_desc[irq]::irqaction,檢測到IRQF_SHARED,則依次next呼叫各共享中斷裝置的IRQ處理程式。在每個irqaction中通過GIMR&GISR判決該裝置是否為實際的中斷來源,如果是則進一步action處理該中斷。

VxWorks所有裝置都通過intConnect()指定一個IRQ(vector)並將ISR掛接到全域性陣列excBsrTbl[]中。中斷向量表excBsrTbl[]包含了所有的異常和中斷處理例程入口地址,IRQ(vector)用於索引。PRIO_TABLE::intCause/PRIO_TABLE::intMask和PRIO_TABLE:: bsrTableOffset建立了中斷源與IRQ號(vector)的對映關係。當多個裝置共享中斷時,PRIO_TABLE::pad引數置1表示中斷複用,引數二指定解複用例程,而非IRQ(Vector),需要解複用獲得實際發生了中斷的裝置IRQ(vector)。

當有中斷髮生時,excIntStub()將通過C0_CAUSE:IP位查詢intPrioTable[],通過PRIO_TABLE::pad判決是否為共享中斷,如果是則首先呼叫解複用例程,通過GIMR&GISR判決是哪個裝置發生了中斷,返回相應IRQ(vector)。如果非共享中斷,則無需解複用,直接通過IRQ(vector)索引excBsrTbl[]找到當初註冊的ISR並呼叫。

9 tickAnnounce

當timer計時到期(1tick=1/60s),將會發起時鐘中斷。經中斷路由和中斷排程後,最終回撥usrClock()函式。

/*usrClock - user defined system clock interrupt routine*/
void usrClock (void)
{
	tickAnnounce ();	/* announce system tick to kernel */
}
/*
* tickAnnounce - announce a clock tick to the kernel
*
* This routine informs the kernel of the passing of time.  It should be called 
* from an interrupt service routine that is connected to the system clock.  
*/
void tickAnnounce (void);

tickAnnounce-將呼叫windTickAnnounce,程式碼參考《VxWorks Source Code》。

/*
* windTickAnnounce - acknowledge the passing of time
*
* Process delay list.  Make tasks at the end of their delay, ready.
* Perform round robin scheduling if selected.
* Call any expired watchdog routines.
*/
void windTickAnnounce (void);

每隔1個tick,將announce aclock tick to the kernel,windTickAnnounce中將遞增全域性變數vxAbsTicks(類似Linux中的全域性變數jiffies),這個全域性變數記錄了自最近一次系統啟動成功初始化時鐘中斷以來的時鐘嘀嗒數(absolute time since power on in ticks)。我們可以通過tickGet()獲取返回它。時間嘀嗒是任務排程的時間片度量單位(the system time-slice),有了tick脈動以後,就可以開展基於時間片的任務排程以及定時延時等操作。

作為高效能的嵌入式硬實時作業系統,任務排程是VxWorks的核心模組。VxWorks的wind核心預設排程機制為基於優先順序的搶佔式排程,在相同優先順序的多個任務之間,則採用時間片輪轉排程機制。關於VxWorks的硬實時特性和任務排程機制,可參考《什麼是真正的實時作業系統》。

參考: