新聞中心

        EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > arm linux 下中斷流程簡要分析中斷處理流程

        arm linux 下中斷流程簡要分析中斷處理流程

        作者: 時間:2016-11-09 來源:網絡 收藏
        三 響應中斷

        首先在分析源碼之前,讓我們了解一些原理性的東西,我們都知道在處理中斷要保存當前現場狀態(tài),然后才能處理中斷,處理完之后還要把現場狀態(tài)恢復過來才能返回到被中斷的地方繼續(xù)執(zhí)行,這里要說明的是在指令跳轉到中斷向量的地方開始執(zhí)行之前,CPU幫我們做了哪些事情:

        本文引用地址:http://www.104case.com/article/201611/317838.htm


        R14_irq =要執(zhí)行的下條指令地址+ 4//這里的下條指令是相對于被中斷指令的下條。即返回地址

        SPSR_irq = CPSR//保存的現場狀態(tài),r0到r12要由我們軟件來保存(如果需要的話)。

        CPSR[4:0] = 0b10010//進入中斷模式

        CPSR[5] = 0//在ARM模式下執(zhí)行(不是Thumb下)

        CPSR[7] = 1//關掉IRQ中斷,FIQ還是開著

        PC = 0Xffff0018/0x00000018//根據異常向量表的位置,跳轉到特定的中斷向量處去執(zhí)行。

        更詳細的關于異常處理的細節(jié)可參考<>

        接下來我們在來分析watchdog產生中斷后的處理流程:

        當watchdog超時時將會產生中斷,中斷號就是IRQ_WDT,當產生中斷時,系統(tǒng)將從跳轉表中的中斷位置開始運行,對于我們這篇文章來說:是從0xffff0000 + 24處開始運行。 這個地址的指令是:

        bvector_irq + stubs_offset

        即直接跳轉到vector_irq處去運行。這些都在中斷初始化的時候分析過了。

        我們來看vector_irq,它是通過宏vector_stub來定義的:

        arch/arm/kernel/entry-armv.S:

        /*

        * Interrupt dispatcher

        */

        vector_stubirq, IRQ_MODE, 4/*這是個宏定義*/

        /*下面這些都是不同模式下的irq處理函數*/

        .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)

        .long__irq_invalid@4

        .long__irq_invalid@5

        .long__irq_invalid@6

        .long__irq_invalid@7

        .long__irq_invalid@8

        .long__irq_invalid@9

        .long__irq_invalid@a

        .long__irq_invalid@b

        .long__irq_invalid@c

        .long__irq_invalid@d

        .long__irq_invalid@e

        .long__irq_invalid@f

        來看宏vector_stub

        arch/arm/kernel/entry-armv.S:

        .macrovector_stub, name, mode, correction=0

        .align5

        vector_/name:

        .if /correction

        sublr, lr, #/correction

        .endif

        @

        @ Save r0, lr_ (parent PC) and spsr_

        @ (parent CPSR)

        @

        stmiasp, {r0, lr}@ save r0, lr

        mrslr, spsr

        strlr, [sp, #8]@ save spsr

        @

        @ Prepare for SVC32 mode.IRQs remain disabled.

        @

        mrsr0, cpsr

        eorr0, r0, #(/mode ^ SVC_MODE)

        msrspsr_cxsf, r0

        @

        @ the branch table must immediately follow this code

        @

        andlr, lr, #0x0f

        movr0, sp

        ldrlr, [pc, lr, lsl #2]

        movspc, lr@ branch to handler in SVC mode

        .endm

        這樣展開后vector_irq如下所示:

        arch/arm/kernel/entry-armv.S:

        vector_irq:

        .if 4

        @ lr保存的是被打斷指令處地址+8的值,(看上面的分析,由PC得到), 這里-4則就是中斷

        @處理完后的返回地址,在中斷處理完后該值會賦給PC

        sublr, lr, #4

        .endif

        @

        @ Save r0, lr_ (parent PC) and spsr_

        @ (parent CPSR)

        @ r0后面會用到所以要保存。

        stmiasp, {r0, lr}@ save r0, lr,保存r0,lr到棧上,這里的棧是中斷模式下的。

        mrslr, spsr@獲取spsr的值,該值保存了被中斷處執(zhí)行環(huán)境的狀態(tài)(參考上面的分析)

        strlr, [sp, #8]@ save spsr, 保存到棧上

        @

        @ Prepare for SVC32 mode.IRQs remain disabled.

        @

        mrsr0, cpsr

        eorr0, r0, #( IRQ_MODE ^ SVC_MODE)

        msrspsr_cxsf, r0@把spsr設置成管理模式

        @

        @ the branch table must immediately follow this code

        @

        andlr, lr, #0x0f

        movr0, sp

        ldrlr, [pc, lr, lsl #2]

        movspc, lr@ branch to handler in SVC mode @ pc = lr, cpsr = spsr

        .endm

        movs的目的對象如果是pc的話,則還會把spsr賦值給cpsr,上面我們看到spsr被設成管理模式,因此這條語句過后的代碼也就跑在了管理模式下。

        此時的棧情況如下:



        S_FRAME_SIZE,S_PC在arch/arm/kernel/Asm-offsets.c:中定義

        DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));

        DEFINE(S_PC,offsetof(struct pt_regs, ARM_pc));

        include/asm-arm/Ptrace.h:

        struct pt_regs {

        long uregs[18];

        };

        #define ARM_pcuregs[15]

        ,pt_regs中對應的就是上面棧上的18個寄存器,ARM_pc是pc寄存器存放在這個數組中的偏移。

        接著看get_thread_info, 它也是個宏,用來獲取當前線程的地址。在我的一篇linux啟動代碼分析里曾寫過線程的定義方式:

        include/linux/Sched.h:

        union thread_union {

        struct thread_info thread_info;/*線程屬性*/

        unsigned long stack[THREAD_SIZE/sizeof(long)];/*棧*/

        };

        由它定義的線程是8K字節(jié)對齊的, 并且在這8K的最低地址處存放的就是thread_info對象,即該棧擁有者線程的對象,而get_thread_info就是通過把sp低13位清0(8K邊界)來獲取當前thread_info對象的地址。

        arch/arm/kernel/entry-armv.S:

        .macroget_thread_info, rd

        mov/rd, sp, lsr #13

        mov/rd, /rd, lsl #13

        .endm

        調用該宏后寄存器tsk里存放的就是當前線程的地址了,tsk是哪個寄存器呢,我們在看:

        arch/arm/kernel/entry-header.S:

        tsk.reqr9@ current thread_info

        ,tsk只是r9的別名而已, 因此這時r9里保存的就是當前線程的地址。

        我們接著看irq_handler:

        arch/arm/kernel/entry-armv.S:

        .macroirq_handler

        1:get_irqnr_and_base r0, r6, r5, lr@平臺相關,獲取中斷號

        movner1, sp@如果r0(中斷號)不等于0,則r1指向sp所在地址,即pt_regs對象地址(看上圖)

        @

        @ routine called with r0 = irq number, r1 = struct pt_regs *

        @

        adrnelr, 1b@如果r0(中斷號)不等于0,lr(返回地址)等于標號1處,即

        @ get_irqnr_and_base r0, r6, r5, lr的那行,即循環(huán)處理所有的中斷。

        bneasm_do_IRQ@處理該中斷

        #ifdef CONFIG_SMP

        /*

        * XXX

        *

        * this macro assumes that irqstat (r6) and base (r5) are

        * preserved from get_irqnr_and_base above

        */

        test_for_ipi r0, r6, r5, lr

        movner0, sp

        adrnelr, 1b

        bnedo_IPI

        #ifdef CONFIG_LOCAL_TIMERS

        test_for_ltirq r0, r6, r5, lr

        movner0, sp

        adrnelr, 1b

        bnedo_local_timer

        #endif

        #endif

        .endm

        get_irqnr_and_base是平臺相關的,這里就不列出來了,對于s3c2410,代碼在include/asm-arm/s3c2410/entry-macro.S里,該宏處理完后,r0 =中斷號,接下來r1賦值為sp地址(pt_regs對象地址), 最后調用c函數asm_do_IRQ, r0, r1作為參數被傳遞進去。asm_do_IRQ()處理完后將返回到lr指向的地址處即上面匯編部分標號為1的地址處繼續(xù)執(zhí)行。

        我們把__irq_usr的匯編部分分析完后再來分析asm_do_IRQ()等c函數。

        Arch/arm/kernel/entry-armv.S:

        __irq_usr:

        ……

        ……

        mov why, #0@ why = 0, why是r8的別名,

        b ret_to_user@返回到用戶模式下

        我們看ret_to_user

        arch/arm/kernel/entry-common.S:

        ENTRY(ret_to_user)

        ret_slow_syscall:

        disable_irq@ disable interrupts@關中斷,

        ldrr1, [tsk, #TI_FLAGS] @獲取thread_info中flags域的值

        tstr1, #_TIF_WORK_MASK@判斷task是否被阻塞

        bnework_pending@根據需要進行進程的切換。

        no_work_pending:

        @ slow_restore_user_regs

        ldrr1, [sp, #S_PSR]@ get calling cpsr獲取被中斷代碼處的狀態(tài)(cpsp)

        ldrlr, [sp, #S_PC]!@ get pc獲取返回地址(被中斷代碼的下條代碼處的地址)

        msrspsr_cxsf, r1@ save in spsr_svc, spsr里保存好被中斷代碼處的狀態(tài)(cpsp)

        ldmdbsp, {r0 - lr}^@ get calling r1 – lr從棧上獲取用戶態(tài)下的r0到lr的值

        movr0, r0

        addsp, sp, #S_FRAME_SIZE - S_PC@棧地址恢復,避免多個中斷后溢出

        movspc, lr@ return & move spsr_svc into cpsr, 返回被中斷代碼處繼續(xù)執(zhí)行,并把spsr賦給cpsp,即恢復被中斷處的現場狀態(tài)。這樣CPU又可以從被中斷的地方繼續(xù)執(zhí)行了,而且這個時候所有的寄存器值(r0到r12),包括狀態(tài)寄存器值(cpsr)都是源碼被中斷時的值。

        我們順便看下work_pending

        arch/arm/kernel/entry-common.S:

        work_pending:

        tstr1, #_TIF_NEED_RESCHED@判斷是否需要調度進程

        bnework_resched@進程調度

        tstr1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING

        beqno_work_pending@無需調度,返回

        movr0, sp@ regs

        movr2, why@ syscall

        bldo_notify_resume

        bret_slow_syscall@ Check work again

        由該匯編可知,如果在用戶模式下產生中斷的話,在返回的時候,會根據需要進行進程調度,而從代碼可知,如果中斷發(fā)生在管理等內核模式下的話是不會進行進程調度的。

        Ok,中斷的流程大體就是這樣的,下面我們就開始分析c函數里的中斷流程。

        先來看asm_do_IRQ

        arch/arm/kernel/Irq.c:

        /*

        * do_IRQ handles all hardware IRQs.Decoded IRQs should not

        * come via this function.Instead, they should provide their

        * own handler

        */

        asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

        {

        struct irqdesc *desc = irq_desc + irq; /*獲取中斷描述符*/

        /*

        * Some hardware gives randomly wrong interrupts.Rather

        * than crashing, do something sensible.

        */

        if (irq >= NR_IRQS)/*參數檢查*/

        desc = &bad_irq_desc;

        irq_enter();

        desc_handle_irq(irq, desc, regs);/*中斷處理*/

        /* AT91 specific workaround */

        irq_finish(irq);

        irq_exit();

        }

        該函數的調用desc_handle_irq()來繼續(xù)處理中斷。

        include/asm-arm/mach/Irq.h:

        /*

        * Obsolete inline function for calling irq descriptor handlers.

        */

        static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc,

        struct pt_regs *regs)

        {

        desc->handle_irq(irq, desc, regs);

        }

        調用中斷描述符的handler_irq函數來處理該中斷,對于IRQ_WDT就是do_edge_IRQ(前面分析過)。

        include/asm-arm/mach/Irq.h:

        #define do_edge_IRQhandle_edge_irq

        kernel/irq/Chip.c:

        /

        *handle_edge_irq - edge type IRQ handler

        *@irq:the interrupt number

        *@desc:the interrupt description structure for this irq

        *@regs:pointer to a register structure

        *

        *Interrupt occures on the falling and/or rising edge of a hardware

        *signal. The occurence is latched into the irq controller hardware

        *and must be acked in order to be reenabled. After the ack another

        *interrupt can happen on the same source even before the first one

        *is handled by the assosiacted event handler. If this happens it

        *might be necessary to disable (mask) the interrupt depending on the

        *controller hardware. This requires to reenable the interrupt inside

        *of the loop which handles the interrupts which have arrived while

        *the handler was running. If all pending interrupts are handled, the

        *loop is left.

        */

        void fastcall

        handle_edge_irq(unsigned int irq, struct irq_desc *desc, struct pt_regs *regs)

        {

        const unsigned int cpu = smp_processor_id();

        spin_lock(&desc->lock);

        desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

        /*

        * If were currently running this IRQ, or its disabled,

        * we shouldnt process the IRQ. Mark it pending, handle

        * the necessary masking and go out

        */

        /*

        *如果該中斷正在處理或者該中斷被disable掉了的話,就不處理該中斷,并清掉pending

        *寄存器里的相應位

        */

        if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||

        !desc->action)) {

        desc->status |= (IRQ_PENDING | IRQ_MASKED);

        mask_ack_irq(desc, irq);/*mask該中斷,清pending標志位*/

        goto out_unlock;

        }

        kstat_cpu(cpu).irqs[irq]++;/*統(tǒng)計中斷數量*/

        /* Start handling the irq */

        /*開始處理中斷,先清掉pending標志位*/

        desc->chip->ack(irq);

        /* Mark the IRQ currently in progress.*/

        desc->status |= IRQ_INPROGRESS;/*標上正在處理的標記*/

        do {

        struct irqaction *action = desc->action;/*獲取該中斷的action*/

        irqreturn_t action_ret;

        if (unlikely(!action)) {

        desc->chip->mask(irq)/*如果沒有注冊action,則mask該中斷*/;

        goto out_unlock;

        }

        /*

        * When another irq arrived while we were handling

        * one, we could have masked the irq.

        * Renable it, if it was not disabled in meantime.

        */

        /*

        *如果以前被mask掉的話,在這里把它打開

        */

        if (unlikely((desc->status &

        (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==

        (IRQ_PENDING | IRQ_MASKED))) {

        desc->chip->unmask(irq); /*unmask該中斷*/

        desc->status &= ~IRQ_MASKED;

        }

        desc->status &= ~IRQ_PENDING;

        spin_unlock(&desc->lock);

        action_ret = handle_IRQ_event(irq, regs, action);/*處理中斷事件*/

        if (!noirqdebug)

        note_interrupt(irq, desc, action_ret, regs);

        spin_lock(&desc->lock);

        /*如果有IRQ_PENDING狀態(tài),則說明又有中斷產生過,則繼續(xù)執(zhí)行*/

        } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

        desc->status &= ~IRQ_INPROGRESS;

        out_unlock:

        spin_unlock(&desc->lock);

        }

        該函數的大體功能都在函數體內解釋出來了,這里我們對調用的每個函數在進行分析。

        先看mask_ack_irq

        kernel/irq/Chip.c:

        static inline void mask_ack_irq(struct irq_desc *desc, int irq)

        {

        if (desc->chip->mask_ack)/*對于IRQ_WDT, 該函數沒定義*/

        desc->chip->mask_ack(irq);

        else {

        desc->chip->mask(irq); /*對于IRQ_WDT,該函數就是s3c_irq_mask*/

        desc->chip->ack(irq);/*對于IRQ_WDT,該函數就是s3c_irq_ack*/

        }

        }

        可以看到它調用具體平臺相關的mask函數來處理該中斷。

        我們來看s3c_irq_mask

        arch/arm/mach-s3c2410/Irq.c:

        static void

        s3c_irq_mask(unsigned int irqno)

        {

        unsigned long mask;

        irqno -= IRQ_EINT0;

        mask = __raw_readl(S3C2410_INTMSK);

        mask |= 1UL << irqno;/*mask掉對應的中斷號*/

        __raw_writel(mask, S3C2410_INTMSK);/*寫MASK寄存器*/

        }

        改函數僅僅是把MASK寄存器中對應的中斷mask掉,即不再響應該中斷

        arch/arm/mach-s3c2410/Irq.c:

        static inline void

        s3c_irq_ack(unsigned int irqno)

        {

        unsigned long bitval = 1UL << (irqno - IRQ_EINT0);

        /*清除pending寄存器的相應位*/

        __raw_writel(bitval, S3C2410_SRCPND);

        __raw_writel(bitval, S3C2410_INTPND);

        }

        由上面這兩個函數可以看出來mask_ack_irq的作用是先mask掉該中斷,并清除pending位,中斷被mask掉后系統(tǒng)就不再響應了, 而pending位被清掉說明系統(tǒng)中該中斷沒有觸發(fā)。一般在中斷處理完后都要清pending位, 要不然系統(tǒng)會認為該中斷又被觸發(fā)了。

        handle_edge_irq()里調用的unmask函數,其實就是打開相應的中斷,讓系統(tǒng)響應這個中斷,代碼就不列出來了。

        接下來中斷看handle_IRQ_event(),它才是真正的中斷處理函數。

        kernel/irq/handle.c:

        /

        * handle_IRQ_event - irq action chain handler

        * @irq:the interrupt number

        * @regs:pointer to a register structure

        * @action:the interrupt action chain for this irq

        *

        * Handles the action chain of an irq event

        */

        irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,

        struct irqaction *action)

        {

        irqreturn_t ret, retval = IRQ_NONE;

        unsigned int status = 0;

        handle_dynamic_tick(action);

        /*下面這個if判斷:當執(zhí)行action操作時是否可以打開中斷*/

        if (!(action->flags & IRQF_DISABLED))

        local_irq_enable_in_hardirq();/*打開中斷*/

        do {

        /*

        *中斷handler,也就是我們通過request_irq注冊的中斷函數,對于IRQ_WDT就是

        * s3c2410wdt_irq

        */

        ret = action->handler(irq, action->dev_id, regs);

        if (ret == IRQ_HANDLED)

        status |= action->flags;

        retval |= ret;

        action = action->next; /*記得嗎,如果該中斷可以共享的話,它就不為NULL*/

        } while (action);

        if (status & IRQF_SAMPLE_RANDOM)

        add_interrupt_randomness(irq);

        local_irq_disable();

        return retval;

        }

        該函數主要就是調用了action的handler函數,也就是我們用request_irq注冊的中斷例程。這里需要注意的是:如果我們注冊中斷的時候指明可以共享的話,則必須在我們的中斷例程里判斷當前產生的中斷是否就是我們自己的中斷,這可以通過傳進來的參數來判斷(該參數就是我們注冊時提供的)。

        OK,到這里整個中斷的流程就大致分析完了。



        關鍵詞: armlinux中斷處

        評論


        技術專區(qū)

        關閉
        主站蜘蛛池模板: 新巴尔虎右旗| 珲春市| 河北省| 共和县| 武陟县| 青岛市| 康定县| 高安市| 汉寿县| 泾源县| 遵化市| 东光县| 沂源县| 宕昌县| 平凉市| 合作市| 浮山县| 霍城县| 芦山县| 咸宁市| 商都县| 镇巴县| 荥经县| 霍邱县| 临安市| 丽水市| 喀什市| 灵璧县| 兴隆县| 许昌县| 青龙| 庐江县| 米脂县| 乌兰浩特市| 吉水县| 贡山| 航空| 和龙市| 光泽县| 南江县| 景德镇市|