<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > ARM+Linux中斷系統(tǒng)詳細分析

          ARM+Linux中斷系統(tǒng)詳細分析

          作者: 時間:2016-11-09 來源:網(wǎng)絡 收藏
          ULK第四章里明確講到“Linux實現(xiàn)了一種沒有優(yōu)先級的中斷模型”,并且“Linux中斷和異常都支持嵌套”。這個我不太理解了,這兩種說法都與我以前的理解剛好相反,核對了原書,翻譯沒有錯。

          Linux中斷系統(tǒng)到底是否支持優(yōu)先級,可否嵌套,中斷號又是怎么來確定的,中斷產生時又是如何一步步執(zhí)行到中斷處理函數(shù)的。為了徹底搞懂Linux中斷系統(tǒng),我決定從最原始材料出發(fā),一探究竟。(s3c2440+linux2.6.21)

          本文引用地址:http://www.ex-cimer.com/article/201611/317905.htm

          先來看看ARM的硬件執(zhí)行流程

          異常是ARM處理器模式分類,ARM有七種運行模式USR,SYS,SVC,IRQ,FIQ,UND,ABT

          五種異常模式:SVC,IRQ,FIQ,UND,ABT

          中斷模式是ARM異常模式之一(IRQ模式,F(xiàn)IQ模式),是一種異步事件,如外部按鍵產生中斷,內部定時器產生中斷,通信數(shù)據(jù)口數(shù)據(jù)收發(fā)產生中斷等。

          1.當一個異常產生時,以FIQ為例,CPU切入FIQ模式時,

          ①將原來執(zhí)行程序的下一條指令地址保存到LR中,就是將R14保存到R14_fiq里面。

          ②拷貝CPSR到SPSR_fiq。

          ③改變CPSR模式位的值,改到FIQ模式。

          ④改變PC值,將其指向相應的異常處理向量表。

          離開異常處理的時候,

          ①將LR(R14_fiq)賦給PC。

          ②將SPSR(SPSR_fiq)拷貝到CPSR。

          ③清除中斷禁止標志(如果開始時置位了)。

          ARM一般在某個固定地址中有一個異常向量表,比如0x0

          當一個外部IRQ中斷產生時

          ①處理器切換到IRQ模式

          ②PC跳到0x18處運行,因為這是IRQ的中斷入口。

          ③通過0x18:LDR PC, IRQ_ADDR,跳轉到相應的中斷服務程序。這個中斷服務程序就要確定中斷源,每個中斷源會有自己獨立的中斷服務程序。

          ④得到中斷源,然后執(zhí)行相應中斷服務程序

          ⑤清除中斷標志,返回

          這就是一個外部中斷完整的執(zhí)行流程了,下面以具體寄存器來更具體的了解ARM的中斷機制。

          假設ARM核有兩個中斷引腳,一根是irq pin,一根是fiq pin,正常情況下,ARM核只是機械地隨著PC指示去執(zhí)行,當CPSR中的I位和F位都為1時,IRQ和FIQ都處于禁止狀態(tài),這時候無論發(fā)什么信號,ARM都不會理睬。

          當I位或F位為0時,irq pin有中斷信號過來時,ARM當前工作就會被打斷,切換到IRQ模式,并且跳轉到異常向量表的中斷入口0x18,SRCPND中相應位置1,經(jīng)過檢查中斷優(yōu)先級寄存器以及屏蔽寄存器,確定中斷源,INTPND相應位置1(經(jīng)過仲裁,只有一位置1),這過程由ARM自動完成。0x18存放的是總的中斷處理函數(shù),在這個函數(shù)里,可以建立一個二級中斷向量表,先清除SRCPND相應位,然后根據(jù)中斷源執(zhí)行相應中斷服務程序,清除INTPND,返回。

          及時清除中斷Pending寄存器的標志位是為了避免兩個問題:①發(fā)生中斷返回后,立即又被中斷,不斷的重復響應②丟失中斷處理過程中發(fā)生的中斷,返回后不響應。

          在某個IRQ中斷程序執(zhí)行過程中,有另外一個外部IRQ中斷產生,會將SRCPND相應位置1,等該中斷服務執(zhí)行完,經(jīng)過仲裁決定下一個要響應的中斷。但是假如當產生的是FIQ,則保存當前IRQ的現(xiàn)場,嵌套響應FIQ,F(xiàn)IQ服務程序執(zhí)行完,再繼續(xù)執(zhí)行IRQ服務。那么當一個FIQ正在服務,產生另外一個FIQ,會怎樣呢,答案是不會被打斷,跟IRQ一樣等當前中斷服務完成,再仲裁剩余需要相應的中斷。

          所以得出這樣的結論:

          ①關于中斷嵌套:IRQ模式只能被FIQ模式打斷,F(xiàn)IQ模式下誰也打不斷。

          ②關于優(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檢查機器ID,__create_page_tables創(chuàng)建頁表,啟動MMU,然后由arch/arm/kernel/head_common.S跳到start_kernel()->trap_init()

          點擊(此處)折疊或打開

          1. void __init trap_init(void)
          2. {
          3. unsigned long vectors=CONFIG_VECTORS_BASE;
          4. memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start);
          5. memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start);
          6. memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz);
          7. }
          8. #define CONFIG_VECTORS_BASE 0xffff0000

          CONFIG_VECTORS_BASE在autoconf.h定義,在ARMV4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0000??梢酝ㄟ^CP15協(xié)處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應關系如下:
          V=0~0x00000000~0x0000001C
          V=1~0xffff0000~0xffff001C

          __vectors_end至__vectors_start之間為異常向量表,位于arch/arm/kernel/entry-armv.S

          點擊(此處)折疊或打開

          1. .globl __vectors_start
          2. __vectors_start:
          3. swi SYS_ERROR0
          4. b vector_und + stubs_offset//復位異常
          5. ldr pc, .LCvswi + stubs_offset //未定義異常
          6. b vector_pabt + stubs_offset//軟件中斷異常
          7. b vector_dabt + stubs_offset//數(shù)據(jù)異常
          8. b vector_addrexcptn + stubs_offset//保留
          9. b vector_irq + stubs_offset //普通中斷異常
          10. b vector_fiq + stubs_offset//快速中斷異常
          11. .globl __vectors_end
          12. __vectors_end:

          stubs_offset值如下:
          .equstubs_offset,__vectors_start+0x200-__stubs_start
          stubs_offset是如何確定的呢?(引用網(wǎng)絡上的一段比較詳細的解釋)
          當匯編器看到B指令后會把要跳轉的標簽轉化為相對于當前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發(fā)生了代碼搬移,所以如果中斷向量表中仍然寫成bvector_irq,那么實際執(zhí)行的時候就無法跳轉到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。我們把搬移前的中斷向量表中的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,對于括號內的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的vectors_start+200-stubs_start就應該是stubs_offset,實際上在entry-armv.S中也是這樣定義的。


          2.中斷響應

          當有外部中斷產生時,跳轉到異常向量表的“bvector_irq + stubs_offset //普通中斷異常”

          進入異常處理函數(shù),跳轉的入口位置archarmkernelentry-armv.S代碼簡略如下

          點擊(此處)折疊或打開

          1. .globl __stubs_start
          2. __stubs_start:
          3. /*
          4. * Interrupt dispatcher
          5. */
          6. vector_stub irq, IRQ_MODE, 4
          7. .long __irq_usr @ 0 (USR_26 / USR_32)
          8. .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
          9. .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
          10. .long __irq_svc @ 3 (SVC_26 / SVC_32)
          11. vector_stub dabt, ABT_MODE, 8
          12. vector_stub pabt, ABT_MODE, 4
          13. vector_stub und, UND_MODE
          14. /*
          15. * Undefined FIQs
          16. */
          17. vector_fiq:
          18. disable_fiq
          19. subs pc, lr, #4
          20. vector_addrexcptn:
          21. b vector_addrexcptn

          vector_stub是個函數(shù)調用宏,根據(jù)中斷前的工作模式?jīng)Q定進入__irq_usr,__irq_svc。這里入__irq_svc,同時看到這里FIQ產生時,系統(tǒng)未做任何處理,直接返回,即Linux沒有提供對FIQ的支持,繼續(xù)跟進代碼

          點擊(此處)折疊或打開

          1. __irq_svc:
          2. svc_entry
          3. irq_handler

          svc_entry是一個宏,主要實現(xiàn)了將SVC模式下的寄存器、中斷返回地址保存到堆棧中。然后進入最核心的中斷響應函數(shù)irq_handler,irq_handler實現(xiàn)過程archarmkernelentry-armv.S

          點擊(此處)折疊或打開

          1. .macro irq_handler
          2. get_irqnr_preamble r5, lr
          3. 1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節(jié)有實現(xiàn)過程
          4. movne r1, sp
          5. @
          6. @ routine called with r0 = irq number, r1 = struct pt_regs *
          7. @
          8. adrne lr, 1b
          9. bne asm_do_IRQ @進入中斷處理。
          10. ……
          11. .endm

          get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s

          點擊(此處)折疊或打開

          1. .macro get_irqnr_and_base, irqnr, irqstat, base, tmp
          2. mov base, #S3C24XX_VA_IRQ
          3. @@ try the interrupt offset register, since it is there
          4. ldr irqstat, [ base, #INTPND ]
          5. teq irqstat, #0
          6. beq 1002f
          7. ldr irqnr, [ base, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置
          8. @@ we have the value
          9. 1001:
          10. adds irqnr, irqnr, #IRQ_EINT0 @加上中斷號的基準數(shù)值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關的代碼決定的,和硬件中斷掛起寄存器中的中斷號是不等的。
          11. 1002:
          12. @@ exit here, Z flag unset if IRQ
          13. .endm

          asm_do_IRQ實現(xiàn)過程,arch/arm/kernel/irq.c

          點擊(此處)折疊或打開

          1. asmlinkage void asm_do_IRQ(unsignedintirq,struct pt_regs*regs)
          2. {
          3. struct pt_regs*old_regs=set_irq_regs(regs);
          4. struct irq_desc*desc=irq_desc+irq;//根據(jù)中斷號,找到響應的irq_desc
          5. /*
          6. *Some hardware gives randomly wrong interrupts.Rather
          7. *than crashing,dosomething sensible.
          8. */
          9. if(irq>=NR_IRQS)
          10. desc=&bad_irq_desc;
          11. irq_enter();
          12. desc_handle_irq(irq,desc);//根據(jù)irq和desc進入中斷處理
          13. /*AT91 specific workaround*/
          14. irq_finish(irq);
          15. irq_exit();
          16. set_irq_regs(old_regs);
          17. }
          18. static inline void desc_handle_irq(unsignedintirq,struct irq_desc*desc)
          19. {
          20. desc->handle_irq(irq,desc);//中斷處理
          21. }

          上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標識。那么這個標識的含義如何理解呢?
          該符號定義在kernel/include/linux/linkage.h中,如下所示:

          #include //各個具體處理器在此文件中定義asmlinkage

          點擊(此處)折疊或打開

          1. #ifdef __cplusplus
          2. #define CPP_ASMLINKAGE extern"C"
          3. #else
          4. #define CPP_ASMLINKAGE
          5. #endif
          6. #ifndef asmlinkage//如果以前沒有定義asmlinkage
          7. #define asmlinkage CPP_ASMLINKAGE
          8. #endif

          對于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數(shù)是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調用標準,通過寄存器傳遞的)。

          但對于X86處理器的中是這樣定義的:

          #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

          表示函數(shù)的參數(shù)傳遞是通過堆棧完成的。

          中斷處理過程代碼就跟到這了,那么最后一個問題desc->handle_irq(irq, desc);是怎么跟我們注冊的中斷函數(shù)相關聯(lián)的呢?再從中斷模型注冊入手:

          中斷相關的數(shù)據(jù)結構:在include/asm/arch/irq.h中定義。

          irq_desc[]是一個指向irq_desc_t結構的數(shù)組,irq_desc_t結構是各個設備中斷服務例程的描述符。Irq_desc_t結構體中的成員action指向該中斷號對應的irqaction結構體鏈表。Irqaction結構體定義在include/linux/interrupt.h中,如下:

          點擊(此處)折疊或打開

          1. truct irqaction{
          2. irq_handler_t handler;//中斷處理函數(shù),注冊時提供
          3. unsigned long flags;//中斷標志,注冊時提供
          4. cpumask_t mask;//中斷掩碼
          5. constchar*name;//中斷名稱
          6. void*dev_id;//設備id,本文后面部分介紹中斷共享時會詳細說明這個參數(shù)的作用
          7. struct irqaction*next;//如果有中斷共享,則繼續(xù)執(zhí)行,
          8. intirq;//中斷號,注冊時提供
          9. struct proc_dir_entry*dir;//指向IRQn相關的/proc/irq/n目錄的描述符
          10. };

          在注冊中斷號為irq的中斷服務程序時,系統(tǒng)會根據(jù)注冊參數(shù)封裝相應的irqaction結構體。并把中斷號為irq的irqaction結構體寫入irq_desc [irq]->action。這樣就把設備的中斷請求號與該設備的中斷服務例程irqaction聯(lián)系在一起了。當CPU接收到中斷請求后,就可以根據(jù)中斷號通過irq_desc []找到該設備的中斷服務程序。


          3.中斷共享的處理模型

          共享中斷的不同設備的iqraction結構體都會添加進該中斷號對應的irq_desc結構體的action成員所指向的irqaction鏈表內。當內核發(fā)生中斷時,它會依次調用該鏈表內所有的handler函數(shù)。因此,若驅動程序需要使用共享中斷機制,其中斷處理函數(shù)必須有能力識別是否是自己的硬件產生了中斷。通常是通過讀取該硬件設備提供的中斷flag標志位進行判斷。也就是說不是任何設備都可以做為中斷共享源的,它必須能夠通過的它的中斷flag判斷出是否發(fā)生了中斷。
          中斷共享的注冊方法是:

          int request_irq(unsigned int irq, irq_handler_t handler,IRQF_SHARED, const char *devname, void *dev_id)

          很多權威資料中都提到,中斷共享注冊時的注冊函數(shù)中的dev_id參數(shù)是必不可少的,并且dev_id的值必須唯一。那么這里提供唯一的dev_id值的究竟是做什么用的?

          根據(jù)我們前面中斷模型的知識,可以看出發(fā)生中斷時,內核并不判斷究竟是共享中斷線上的哪個設備產生了中斷,它會循環(huán)執(zhí)行所有該中斷線上注冊的中斷處理函數(shù)(即irqaction->handler函數(shù))。因此irqaction->handler函數(shù)有責任識別出是否是自己的硬件設備產生了中斷,然后再執(zhí)行該中斷處理函數(shù)。通常是通過讀取該硬件設備提供的中斷flag標志位進行判斷。那既然kernel循環(huán)執(zhí)行該中斷線上注冊的所有irqaction->handler函數(shù),把識別究竟是哪個硬件設備產生了中斷這件事交給中斷處理函數(shù)本身去做,那request_irq的dev_id參數(shù)究竟是做什么用的?

          很多資料中都建議將設備結構指針作為dev_id參數(shù)。在中斷到來時,迅速地根據(jù)硬件寄存器中的信息比照傳入的dev_id參數(shù)判斷是否是本設備的中斷,若不是,應迅速返回。這樣的說法沒有問題,也是我們編程時都遵循的方法。但事實上并不能夠說明為什么中斷共享必須要設置dev_id。

          下面解釋一下dev_id參數(shù)為什么必須的,而且是必須唯一的。

          當調用free_irq注銷中斷處理函數(shù)時(通常卸載驅動時其中斷處理函數(shù)也會被注銷掉),因為dev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多個中斷處理程序中刪除指定的一個。如果沒有這個參數(shù),那么kernel不可能知道給定的中斷線上到底要刪除哪一個處理程序。

          注銷函數(shù)定義在Kernel/irq/manage.c中定義:
          void free_irq(unsigned int irq, void *dev_id)


          4.S3C2410子中斷的注冊的實現(xiàn)

          前面判斷中斷號的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC等帶有子中斷的向量,INTOFFSET無法判斷出具體的中斷號。平臺留給我們的注冊方法如下:

          在include/asm/arch/irqs.h中有類似如下定義:

          點擊(此處)折疊或打開

          1. /*interrupts generated from the external interrupts sources*/
          2. #define IRQ_EINT4 S3C2410_IRQ(32)/*48*/
          3. #define IRQ_EINT5 S3C2410_IRQ(33)
          4. #define IRQ_EINT6 S3C2410_IRQ(34)
          5. #define IRQ_EINT7 S3C2410_IRQ(35)
          6. #define IRQ_EINT8 S3C2410_IRQ(36)
          7. #define IRQ_EINT9 S3C2410_IRQ(37)
          8. #define IRQ_EINT10 S3C2410_IRQ(38)
          9. #define IRQ_EINT11 S3C2410_IRQ(39)
          10. #define IRQ_EINT12 S3C2410_IRQ(40)
          11. #define IRQ_EINT13 S3C2410_IRQ(41)
          12. #define IRQ_EINT14 S3C2410_IRQ(42)
          13. #define IRQ_EINT15 S3C2410_IRQ(43)
          14. #define IRQ_EINT16 S3C2410_IRQ(44)
          15. #define IRQ_EINT17 S3C2410_IRQ(45)
          16. #define IRQ_EINT18 S3C2410_IRQ(46)
          17. #define IRQ_EINT19 S3C2410_IRQ(47)
          18. #define IRQ_EINT20 S3C2410_IRQ(48)/*64*/
          19. #define IRQ_EINT21 S3C2410_IRQ(49)
          20. #define IRQ_EINT22 S3C2410_IRQ(50)
          21. #define IRQ_EINT23 S3C2410_IRQ(51)

          可以看到平臺為每種子中斷都定義了中斷號,如果你想實現(xiàn)EINT10的中斷注冊,直接按照IRQ_EINT10這個中斷號注冊都可以了。那么平臺代碼是如何實現(xiàn)這部分中斷注冊的呢?


          5.S3C2410子中斷注冊問題的解決

          點擊(此處)折疊或打開

          1. /*arch/arm/plat-s3c24xx/irq.c*/
          2. void __init s3c24xx_init_irq(void)
          3. {……
          4. set_irq_chained_handler(IRQ_EINT4t7,s3c_irq_demux_extint4t7);
          5. set_irq_chained_handler(IRQ_EINT8t23,s3c_irq_demux_extint8);
          6. set_irq_chained_handler(IRQ_UART0,s3c_irq_demux_uart0);
          7. set_irq_chained_handler(IRQ_UART1,s3c_irq_demux_uart1);
          8. set_irq_chained_handler(IRQ_UART2,s3c_irq_demux_uart2);
          9. set_irq_chained_handler(IRQ_ADCPARENT,s3c_irq_demux_adc);
          10. ……
          11. }

          平臺在初始化時會調用到s3c24xx_init_irq,在此函數(shù)中實現(xiàn)了對EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的注冊。下面看看這些帶有子中斷的中斷號對應的處理函數(shù)的內容。以IRQ_EINT4t7為例,其它情況類似。

          點擊(此處)折疊或打開

          1. /*arch/arm/plat-s3c24xx/irq.c*/
          2. s3c_irq_demux_extint4t7(unsignedintirq,
          3. struct irq_desc*desc)
          4. {
          5. unsigned long eintpnd=__raw_readl(S3C24XX_EINTPEND);
          6. unsigned long eintmsk=__raw_readl(S3C24XX_EINTMASK);
          7. eintpnd&=~eintmsk;
          8. eintpnd&=0xff;/*only lower irqs*/
          9. /*eintpnd中可以有多個位同時置1,這一點和intpnd的只能有1個位置1是不一樣的*/
          10. while(eintpnd){//循環(huán)執(zhí)行所有置位的子中斷
          11. irq=__ffs(eintpnd);//算出第一個不為0的位,類似arm v5后的clz前導0的作用
          12. eintpnd&=~(1<
          13. irq+=(IRQ_EINT4-4);//算出對應的中斷號
          14. desc_handle_irq(irq,irq_desc+irq);//執(zhí)行對應子中斷的注冊函數(shù)
          15. }
          16. }

          從上面的函數(shù)可以看出子中斷是如何注冊及被調用到的。有人可能會問為何不在include/asm/arch-s3c2410/entry-macro.s文件中get_irqnr_and_base函數(shù)判斷中斷號時,直接算出對應的子中斷號,就可以直接找到子中斷處理了呢?

          原因是: get_irqnr_and_base是平臺給系統(tǒng)提供的函數(shù),對于多個子中斷同時置位的情況無法通過一個值返回(因為子中斷中,如eintpnd是可以多個位同時置位的))。而intpnd則沒有這個問題。

          至此,對于s3c2440/10+linux2.6得出以下結論:

          ①不支持中斷嵌套(因為FIQ不支持)

          ②有明確中斷優(yōu)先級(可編程)

          ③中斷號是根據(jù)硬件特性固定的,riq號通過某種轉換得到與寄存器相應位,一般在irqs.h文件定義

          中斷的用法見Ldd3的筆記:http://blog.chinaunix.net/uid-24708340-id-3035617.html



          關鍵詞: ARMLinux中斷系

          評論


          技術專區(qū)

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();