ARM+Linux中斷系統(tǒng)詳細(xì)分析
Linux中斷系統(tǒng)到底是否支持優(yōu)先級,可否嵌套,中斷號又是怎么來確定的,中斷產(chǎn)生時又是如何一步步執(zhí)行到中斷處理函數(shù)的。為了徹底搞懂Linux中斷系統(tǒng),我決定從最原始材料出發(fā),一探究竟。(s3c2440+linux2.6.21)
本文引用地址:http://m.butianyuan.cn/article/201611/317905.htm先來看看ARM的硬件執(zhí)行流程
異常是ARM處理器模式分類,ARM有七種運(yùn)行模式USR,SYS,SVC,IRQ,FIQ,UND,ABT
五種異常模式:SVC,IRQ,FIQ,UND,ABT
中斷模式是ARM異常模式之一(IRQ模式,F(xiàn)IQ模式),是一種異步事件,如外部按鍵產(chǎn)生中斷,內(nèi)部定時器產(chǎn)生中斷,通信數(shù)據(jù)口數(shù)據(jù)收發(fā)產(chǎn)生中斷等。
1.當(dāng)一個異常產(chǎn)生時,以FIQ為例,CPU切入FIQ模式時,
①將原來執(zhí)行程序的下一條指令地址保存到LR中,就是將R14保存到R14_fiq里面。
②拷貝CPSR到SPSR_fiq。
③改變CPSR模式位的值,改到FIQ模式。
④改變PC值,將其指向相應(yīng)的異常處理向量表。
離開異常處理的時候,
①將LR(R14_fiq)賦給PC。
②將SPSR(SPSR_fiq)拷貝到CPSR。
③清除中斷禁止標(biāo)志(如果開始時置位了)。
ARM一般在某個固定地址中有一個異常向量表,比如0x0
當(dāng)一個外部IRQ中斷產(chǎn)生時
①處理器切換到IRQ模式
②PC跳到0x18處運(yùn)行,因?yàn)檫@是IRQ的中斷入口。
③通過0x18:LDR PC, IRQ_ADDR,跳轉(zhuǎn)到相應(yīng)的中斷服務(wù)程序。這個中斷服務(wù)程序就要確定中斷源,每個中斷源會有自己獨(dú)立的中斷服務(wù)程序。
④得到中斷源,然后執(zhí)行相應(yīng)中斷服務(wù)程序
⑤清除中斷標(biāo)志,返回
這就是一個外部中斷完整的執(zhí)行流程了,下面以具體寄存器來更具體的了解ARM的中斷機(jī)制。
假設(shè)ARM核有兩個中斷引腳,一根是irq pin,一根是fiq pin,正常情況下,ARM核只是機(jī)械地隨著PC指示去執(zhí)行,當(dāng)CPSR中的I位和F位都為1時,IRQ和FIQ都處于禁止?fàn)顟B(tài),這時候無論發(fā)什么信號,ARM都不會理睬。
當(dāng)I位或F位為0時,irq pin有中斷信號過來時,ARM當(dāng)前工作就會被打斷,切換到IRQ模式,并且跳轉(zhuǎn)到異常向量表的中斷入口0x18,SRCPND中相應(yīng)位置1,經(jīng)過檢查中斷優(yōu)先級寄存器以及屏蔽寄存器,確定中斷源,INTPND相應(yīng)位置1(經(jīng)過仲裁,只有一位置1),這過程由ARM自動完成。0x18存放的是總的中斷處理函數(shù),在這個函數(shù)里,可以建立一個二級中斷向量表,先清除SRCPND相應(yīng)位,然后根據(jù)中斷源執(zhí)行相應(yīng)中斷服務(wù)程序,清除INTPND,返回。
及時清除中斷Pending寄存器的標(biāo)志位是為了避免兩個問題:①發(fā)生中斷返回后,立即又被中斷,不斷的重復(fù)響應(yīng)②丟失中斷處理過程中發(fā)生的中斷,返回后不響應(yīng)。
在某個IRQ中斷程序執(zhí)行過程中,有另外一個外部IRQ中斷產(chǎn)生,會將SRCPND相應(yīng)位置1,等該中斷服務(wù)執(zhí)行完,經(jīng)過仲裁決定下一個要響應(yīng)的中斷。但是假如當(dāng)產(chǎn)生的是FIQ,則保存當(dāng)前IRQ的現(xiàn)場,嵌套響應(yīng)FIQ,F(xiàn)IQ服務(wù)程序執(zhí)行完,再繼續(xù)執(zhí)行IRQ服務(wù)。那么當(dāng)一個FIQ正在服務(wù),產(chǎn)生另外一個FIQ,會怎樣呢,答案是不會被打斷,跟IRQ一樣等當(dāng)前中斷服務(wù)完成,再仲裁剩余需要相應(yīng)的中斷。
所以得出這樣的結(jié)論:
①關(guān)于中斷嵌套:IRQ模式只能被FIQ模式打斷,F(xiàn)IQ模式下誰也打不斷。
②關(guān)于優(yōu)先級:ARM核對中斷優(yōu)先級,有明確的可編程管理。
下面再來看看Linux對ARM是怎么處理的,記住一個前提:Linux對ARM的硬件特性可以取舍,但不可更改。
1.建立異常向量表:
系統(tǒng)從arch/arm/kernel/head.S的ENTRY(stext)開始執(zhí)行,__lookup_processor_type檢查處理器ID,__lookup_machine_type檢查機(jī)器ID,__create_page_tables創(chuàng)建頁表,啟動MMU,然后由arch/arm/kernel/head_common.S跳到start_kernel()->trap_init()
點(diǎn)擊(此處)折疊或打開
- void __init trap_init(void)
- {
- unsigned long vectors=CONFIG_VECTORS_BASE;
- …
- memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start);
- memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start);
- memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz);
- …
- }
- #define CONFIG_VECTORS_BASE 0xffff0000
CONFIG_VECTORS_BASE在autoconf.h定義,在ARMV4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0000。可以通過CP15協(xié)處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應(yīng)關(guān)系如下:
V=0~0x00000000~0x0000001C
V=1~0xffff0000~0xffff001C
__vectors_end至__vectors_start之間為異常向量表,位于arch/arm/kernel/entry-armv.S
點(diǎn)擊(此處)折疊或打開
- .globl __vectors_start
- __vectors_start:
- swi SYS_ERROR0
- b vector_und + stubs_offset//復(fù)位異常
- ldr pc, .LCvswi + stubs_offset //未定義異常
- b vector_pabt + stubs_offset//軟件中斷異常
- b vector_dabt + stubs_offset//數(shù)據(jù)異常
- b vector_addrexcptn + stubs_offset//保留
- b vector_irq + stubs_offset //普通中斷異常
- b vector_fiq + stubs_offset//快速中斷異常
- .globl __vectors_end
- __vectors_end:
stubs_offset值如下:
.equstubs_offset,__vectors_start+0x200-__stubs_start
stubs_offset是如何確定的呢?(引用網(wǎng)絡(luò)上的一段比較詳細(xì)的解釋)
當(dāng)匯編器看到B指令后會把要跳轉(zhuǎn)的標(biāo)簽轉(zhuǎn)化為相對于當(dāng)前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發(fā)生了代碼搬移,所以如果中斷向量表中仍然寫成bvector_irq,那么實(shí)際執(zhí)行的時候就無法跳轉(zhuǎn)到搬移后的vector_irq處,因?yàn)橹噶畲a里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。我們把搬移前的中斷向量表中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start,vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后vectors_start在0xffff0000處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷向量中的中斷入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再減去中斷入口在向量表中的偏移量,即200+vector_irq-stubs_start-irq_PC+vectors_start=(vector_irq-irq_PC)+vectors_start+200-stubs_start,對于括號內(nèi)的值實(shí)際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的vectors_start+200-stubs_start就應(yīng)該是stubs_offset,實(shí)際上在entry-armv.S中也是這樣定義的。
2.中斷響應(yīng)
當(dāng)有外部中斷產(chǎn)生時,跳轉(zhuǎn)到異常向量表的“bvector_irq + stubs_offset //普通中斷異常”
進(jìn)入異常處理函數(shù),跳轉(zhuǎn)的入口位置archarmkernelentry-armv.S代碼簡略如下
點(diǎn)擊(此處)折疊或打開
- .globl __stubs_start
- __stubs_start:
- /*
- * Interrupt dispatcher
- */
- vector_stub irq, IRQ_MODE, 4
- .long __irq_usr @ 0 (USR_26 / USR_32)
- .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
- .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
- .long __irq_svc @ 3 (SVC_26 / SVC_32)
- vector_stub dabt, ABT_MODE, 8
- vector_stub pabt, ABT_MODE, 4
- vector_stub und, UND_MODE
- /*
- * Undefined FIQs
- */
- vector_fiq:
- disable_fiq
- subs pc, lr, #4
- vector_addrexcptn:
- b vector_addrexcptn
vector_stub是個函數(shù)調(diào)用宏,根據(jù)中斷前的工作模式?jīng)Q定進(jìn)入__irq_usr,__irq_svc。這里入__irq_svc,同時看到這里FIQ產(chǎn)生時,系統(tǒng)未做任何處理,直接返回,即Linux沒有提供對FIQ的支持,繼續(xù)跟進(jìn)代碼
點(diǎn)擊(此處)折疊或打開
- __irq_svc:
- svc_entry
- …
- irq_handler
svc_entry是一個宏,主要實(shí)現(xiàn)了將SVC模式下的寄存器、中斷返回地址保存到堆棧中。然后進(jìn)入最核心的中斷響應(yīng)函數(shù)irq_handler,irq_handler實(shí)現(xiàn)過程archarmkernelentry-armv.S
點(diǎn)擊(此處)折疊或打開
- .macro irq_handler
- get_irqnr_preamble r5, lr
- 1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節(jié)有實(shí)現(xiàn)過程
- movne r1, sp
- @
- @ routine called with r0 = irq number, r1 = struct pt_regs *
- @
- adrne lr, 1b
- bne asm_do_IRQ @進(jìn)入中斷處理。
- ……
- .endm
get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s
點(diǎn)擊(此處)折疊或打開
- .macro get_irqnr_and_base, irqnr, irqstat, base, tmp
- mov base, #S3C24XX_VA_IRQ
- @@ try the interrupt offset register, since it is there
- ldr irqstat, [ base, #INTPND ]
- teq irqstat, #0
- beq 1002f
- ldr irqnr, [ base, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置
- …
- @@ we have the value
- 1001:
- adds irqnr, irqnr, #IRQ_EINT0 @加上中斷號的基準(zhǔn)數(shù)值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關(guān)的代碼決定的,和硬件中斷掛起寄存器中的中斷號是不等的。
- 1002:
- @@ exit here, Z flag unset if IRQ
- .endm
asm_do_IRQ實(shí)現(xiàn)過程,arch/arm/kernel/irq.c
點(diǎn)擊(此處)折疊或打開
- asmlinkage void asm_do_IRQ(unsignedintirq,struct pt_regs*regs)
- {
- struct pt_regs*old_regs=set_irq_regs(regs);
- struct irq_desc*desc=irq_desc+irq;//根據(jù)中斷號,找到響應(yīng)的irq_desc
- /*
- *Some hardware gives randomly wrong interrupts.Rather
- *than crashing,dosomething sensible.
- */
- if(irq>=NR_IRQS)
- desc=&bad_irq_desc;
- irq_enter();
- desc_handle_irq(irq,desc);//根據(jù)irq和desc進(jìn)入中斷處理
- /*AT91 specific workaround*/
- irq_finish(irq);
- irq_exit();
- set_irq_regs(old_regs);
- }
- static inline void desc_handle_irq(unsignedintirq,struct irq_desc*desc)
- {
- desc->handle_irq(irq,desc);//中斷處理
- }
上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標(biāo)識。那么這個標(biāo)識的含義如何理解呢?
該符號定義在kernel/include/linux/linkage.h中,如下所示:
點(diǎn)擊(此處)折疊或打開
- #ifdef __cplusplus
- #define CPP_ASMLINKAGE extern"C"
- #else
- #define CPP_ASMLINKAGE
- #endif
- #ifndef asmlinkage//如果以前沒有定義asmlinkage
- #define asmlinkage CPP_ASMLINKAGE
- #endif
對于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數(shù)是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調(diào)用標(biāo)準(zhǔn),通過寄存器傳遞的)。
但對于X86處理器的中是這樣定義的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表示函數(shù)的參數(shù)傳遞是通過堆棧完成的。
中斷處理過程代碼就跟到這了,那么最后一個問題desc->handle_irq(irq, desc);是怎么跟我們注冊的中斷函數(shù)相關(guān)聯(lián)的呢?再從中斷模型注冊入手:
中斷相關(guān)的數(shù)據(jù)結(jié)構(gòu):在include/asm/arch/irq.h中定義。
irq_desc[]是一個指向irq_desc_t結(jié)構(gòu)的數(shù)組,irq_desc_t結(jié)構(gòu)是各個設(shè)備中斷服務(wù)例程的描述符。Irq_desc_t結(jié)構(gòu)體中的成員action指向該中斷號對應(yīng)的irqaction結(jié)構(gòu)體鏈表。Irqaction結(jié)構(gòu)體定義在include/linux/interrupt.h中,如下:
點(diǎn)擊(此處)折疊或打開
- truct irqaction{
- irq_handler_t handler;//中斷處理函數(shù),注冊時提供
- unsigned long flags;//中斷標(biāo)志,注冊時提供
- cpumask_t mask;//中斷掩碼
- constchar*name;//中斷名稱
- void*dev_id;//設(shè)備id,本文后面部分介紹中斷共享時會詳細(xì)說明這個參數(shù)的作用
- struct irqaction*next;//如果有中斷共享,則繼續(xù)執(zhí)行,
- intirq;//中斷號,注冊時提供
- struct proc_dir_entry*dir;//指向IRQn相關(guān)的/proc/irq/n目錄的描述符
- };
在注冊中斷號為irq的中斷服務(wù)程序時,系統(tǒng)會根據(jù)注冊參數(shù)封裝相應(yīng)的irqaction結(jié)構(gòu)體。并把中斷號為irq的irqaction結(jié)構(gòu)體寫入irq_desc [irq]->action。這樣就把設(shè)備的中斷請求號與該設(shè)備的中斷服務(wù)例程irqaction聯(lián)系在一起了。當(dāng)CPU接收到中斷請求后,就可以根據(jù)中斷號通過irq_desc []找到該設(shè)備的中斷服務(wù)程序。
3.中斷共享的處理模型
共享中斷的不同設(shè)備的iqraction結(jié)構(gòu)體都會添加進(jìn)該中斷號對應(yīng)的irq_desc結(jié)構(gòu)體的action成員所指向的irqaction鏈表內(nèi)。當(dāng)內(nèi)核發(fā)生中斷時,它會依次調(diào)用該鏈表內(nèi)所有的handler函數(shù)。因此,若驅(qū)動程序需要使用共享中斷機(jī)制,其中斷處理函數(shù)必須有能力識別是否是自己的硬件產(chǎn)生了中斷。通常是通過讀取該硬件設(shè)備提供的中斷flag標(biāo)志位進(jìn)行判斷。也就是說不是任何設(shè)備都可以做為中斷共享源的,它必須能夠通過的它的中斷flag判斷出是否發(fā)生了中斷。
中斷共享的注冊方法是:
int request_irq(unsigned int irq, irq_handler_t handler,IRQF_SHARED, const char *devname, void *dev_id)
很多權(quán)威資料中都提到,中斷共享注冊時的注冊函數(shù)中的dev_id參數(shù)是必不可少的,并且dev_id的值必須唯一。那么這里提供唯一的dev_id值的究竟是做什么用的?
根據(jù)我們前面中斷模型的知識,可以看出發(fā)生中斷時,內(nèi)核并不判斷究竟是共享中斷線上的哪個設(shè)備產(chǎn)生了中斷,它會循環(huán)執(zhí)行所有該中斷線上注冊的中斷處理函數(shù)(即irqaction->handler函數(shù))。因此irqaction->handler函數(shù)有責(zé)任識別出是否是自己的硬件設(shè)備產(chǎn)生了中斷,然后再執(zhí)行該中斷處理函數(shù)。通常是通過讀取該硬件設(shè)備提供的中斷flag標(biāo)志位進(jìn)行判斷。那既然kernel循環(huán)執(zhí)行該中斷線上注冊的所有irqaction->handler函數(shù),把識別究竟是哪個硬件設(shè)備產(chǎn)生了中斷這件事交給中斷處理函數(shù)本身去做,那request_irq的dev_id參數(shù)究竟是做什么用的?
很多資料中都建議將設(shè)備結(jié)構(gòu)指針作為dev_id參數(shù)。在中斷到來時,迅速地根據(jù)硬件寄存器中的信息比照傳入的dev_id參數(shù)判斷是否是本設(shè)備的中斷,若不是,應(yīng)迅速返回。這樣的說法沒有問題,也是我們編程時都遵循的方法。但事實(shí)上并不能夠說明為什么中斷共享必須要設(shè)置dev_id。
下面解釋一下dev_id參數(shù)為什么必須的,而且是必須唯一的。
當(dāng)調(diào)用free_irq注銷中斷處理函數(shù)時(通常卸載驅(qū)動時其中斷處理函數(shù)也會被注銷掉),因?yàn)閐ev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多個中斷處理程序中刪除指定的一個。如果沒有這個參數(shù),那么kernel不可能知道給定的中斷線上到底要刪除哪一個處理程序。
注銷函數(shù)定義在Kernel/irq/manage.c中定義:
void free_irq(unsigned int irq, void *dev_id)
4.S3C2410子中斷的注冊的實(shí)現(xiàn)
前面判斷中斷號的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC等帶有子中斷的向量,INTOFFSET無法判斷出具體的中斷號。平臺留給我們的注冊方法如下:
在include/asm/arch/irqs.h中有類似如下定義:
點(diǎn)擊(此處)折疊或打開
- /*interrupts generated from the external interrupts sources*/
- #define IRQ_EINT4 S3C2410_IRQ(32)/*48*/
- #define IRQ_EINT5 S3C2410_IRQ(33)
- #define IRQ_EINT6 S3C2410_IRQ(34)
- #define IRQ_EINT7 S3C2410_IRQ(35)
- #define IRQ_EINT8 S3C2410_IRQ(36)
- #define IRQ_EINT9 S3C2410_IRQ(37)
- #define IRQ_EINT10 S3C2410_IRQ(38)
- #define IRQ_EINT11 S3C2410_IRQ(39)
- #define IRQ_EINT12 S3C2410_IRQ(40)
- #define IRQ_EINT13 S3C2410_IRQ(41)
- #define IRQ_EINT14 S3C2410_IRQ(42)
- #define IRQ_EINT15 S3C2410_IRQ(43)
- #define IRQ_EINT16 S3C2410_IRQ(44)
- #define IRQ_EINT17 S3C2410_IRQ(45)
- #define IRQ_EINT18 S3C2410_IRQ(46)
- #define IRQ_EINT19 S3C2410_IRQ(47)
- #define IRQ_EINT20 S3C2410_IRQ(48)/*64*/
- #define IRQ_EINT21 S3C2410_IRQ(49)
- #define IRQ_EINT22 S3C2410_IRQ(50)
- #define IRQ_EINT23 S3C2410_IRQ(51)
可以看到平臺為每種子中斷都定義了中斷號,如果你想實(shí)現(xiàn)EINT10的中斷注冊,直接按照IRQ_EINT10這個中斷號注冊都可以了。那么平臺代碼是如何實(shí)現(xiàn)這部分中斷注冊的呢?
5.S3C2410子中斷注冊問題的解決
點(diǎn)擊(此處)折疊或打開
- /*arch/arm/plat-s3c24xx/irq.c*/
- void __init s3c24xx_init_irq(void)
- {……
- set_irq_chained_handler(IRQ_EINT4t7,s3c_irq_demux_extint4t7);
- set_irq_chained_handler(IRQ_EINT8t23,s3c_irq_demux_extint8);
- set_irq_chained_handler(IRQ_UART0,s3c_irq_demux_uart0);
- set_irq_chained_handler(IRQ_UART1,s3c_irq_demux_uart1);
- set_irq_chained_handler(IRQ_UART2,s3c_irq_demux_uart2);
- set_irq_chained_handler(IRQ_ADCPARENT,s3c_irq_demux_adc);
- ……
- }
平臺在初始化時會調(diào)用到s3c24xx_init_irq,在此函數(shù)中實(shí)現(xiàn)了對EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的注冊。下面看看這些帶有子中斷的中斷號對應(yīng)的處理函數(shù)的內(nèi)容。以IRQ_EINT4t7為例,其它情況類似。
點(diǎn)擊(此處)折疊或打開
- /*arch/arm/plat-s3c24xx/irq.c*/
- s3c_irq_demux_extint4t7(unsignedintirq,
- struct irq_desc*desc)
- {
- unsigned long eintpnd=__raw_readl(S3C24XX_EINTPEND);
- unsigned long eintmsk=__raw_readl(S3C24XX_EINTMASK);
- eintpnd&=~eintmsk;
- eintpnd&=0xff;/*only lower irqs*/
- /*eintpnd中可以有多個位同時置1,這一點(diǎn)和intpnd的只能有1個位置1是不一樣的*/
- while(eintpnd){//循環(huán)執(zhí)行所有置位的子中斷
- irq=__ffs(eintpnd);//算出第一個不為0的位,類似arm v5后的clz前導(dǎo)0的作用
- eintpnd&=~(1<
- irq+=(IRQ_EINT4-4);//算出對應(yīng)的中斷號
- desc_handle_irq(irq,irq_desc+irq);//執(zhí)行對應(yīng)子中斷的注冊函數(shù)
- }
- }
從上面的函數(shù)可以看出子中斷是如何注冊及被調(diào)用到的。有人可能會問為何不在include/asm/arch-s3c2410/entry-macro.s文件中g(shù)et_irqnr_and_base函數(shù)判斷中斷號時,直接算出對應(yīng)的子中斷號,就可以直接找到子中斷處理了呢?
原因是: get_irqnr_and_base是平臺給系統(tǒng)提供的函數(shù),對于多個子中斷同時置位的情況無法通過一個值返回(因?yàn)樽又袛嘀?,如eintpnd是可以多個位同時置位的))。而intpnd則沒有這個問題。
至此,對于s3c2440/10+linux2.6得出以下結(jié)論:
①不支持中斷嵌套(因?yàn)镕IQ不支持)
②有明確中斷優(yōu)先級(可編程)
③中斷號是根據(jù)硬件特性固定的,riq號通過某種轉(zhuǎn)換得到與寄存器相應(yīng)位,一般在irqs.h文件定義
中斷的用法見Ldd3的筆記:http://blog.chinaunix.net/uid-24708340-id-3035617.html
評論