新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > ARM Linux中斷機制之中斷處理

        ARM Linux中斷機制之中斷處理

        作者: 時間:2016-11-09 來源:網絡 收藏
        //現在來看看中斷初始化的另一個函數early_trap_init(),該函數在文件arch/arm/kernel/traps.c中實現。

        void __init early_trap_init(void)
        {

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

        //CONFIG_VECTORS_BASE在autoconf.h中定義(該文件自動成生),值為0xffff0000,
        unsigned long vectors = CONFIG_VECTORS_BASE;
        extern char __stubs_start[], __stubs_end[];
        extern char __vectors_start[], __vectors_end[];
        extern char __kuser_helper_start[], __kuser_helper_end[];
        int kuser_sz = __kuser_helper_end - __kuser_helper_start;

        /* 異常向量表拷貝到 0x0000_0000(或 0xFFFF_0000) ,
        異常處理程序的 stub 拷貝到 0x0000_0200(或 0xFFFF_0200) */
        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);

        /* 拷貝信號處理函數 */
        memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
        sizeof(sigreturn_codes));

        /* 刷新 Cache,修改異常向量表占據的頁面的訪問權限*/

        flush_icache_range(vectors, vectors + PAGE_SIZE);
        modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
        }

        這個函數把定義在 arch/arm/kernel/entry-armv.S 中的異常向量表和異常處理程序的 stub 進行
        重定位:異常向量表拷貝到 0xFFFF_0000,異常向量處理程序的 stub 拷貝到 0xFFFF_0200。
        然后調用 modify_domain()修改了異常向量表所占據的頁面的訪問權限,這使得用戶態無法
        訪問該頁,只有核心態才可以訪問。

        arm處理器發生異常時總會跳轉到 0xFFFF_0000(設為“高端向量配置”時)處的異常向量
        表,因此進行這個重定位工作。

        異常向量表,在文件arch/arm/kernel/entry-armv.S 中

        .equstubs_offset, __vectors_start + 0x200 - __stubs_start

        .globl__vectors_start
        __vectors_start:
        swiSYS_ERROR0
        bvector_und + stubs_offset//復位異常:
        ldrpc, .LCvswi + stubs_offset//未定義指令異常:
        bvector_pabt + stubs_offset//軟件中斷異常:
        bvector_dabt + stubs_offset//數據異常:
        bvector_addrexcptn + stubs_offset//保留:
        bvector_irq + stubs_offset//普通中斷異常:
        bvector_fiq + stubs_offset//快速中斷異常:

        .globl__vectors_end
        __vectors_end:

        ARM 處理器發生異常(中斷是一種異常)時,會跳轉到異常向量表,在向量表中找到相應的異常,并跳轉到

        該異常處理程序處執行。

        stubs_offset,定義為__vectors_start + 0x200 - __stubs_start。

        在中斷初始化函數early_trap_init()中向量表被拷到0xFFFF_0000處,異常處理程序段被拷到0xFFFF_0200處。

        比如此時發生中斷異常bvector_irq + stubs_offset 將跳轉到中斷異常處理程序段去執行,由于vector_irq,

        在異常處理程序段__stubs_start到__stubs_end之間此時跳轉的位置將是__vectors_start + 0x200 + vector_irq - __stubs_start處。

        異常處理程序段如下:

        當 ARM 處理器發生異常(中斷是一種異常)時,會跳轉到異常向量表,在向量表中找到相應的異常,并跳轉到

        該異常處理程序處執行,這些異常處理程序即是放在以下異常處理程序段中。

        .globl__stubs_start
        __stubs_start:

        //vector_stub是一個宏,它代表有一段程序放在此處。irq, IRQ_MODE, 4是傳遞給宏vector_stub的參數。
        vector_stubirq, IRQ_MODE, 4

        //以下是跳轉表,在宏vector_stub代表的程序段中要用到該表來查找程序要跳轉的位置。

        //如果在進入終中斷時是用戶模式,則調用__irq_usr例程,如果為系統模式,則調用__irq_svc,如果是其他模式,則說明出錯了,

        //則調用__irq_invalid。

        .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_stubdabt, ABT_MODE, 8

        .。。。。。。


        vector_stubpabt, ABT_MODE, 4

        。。。。。。


        vector_stubund, UND_MODE

        。。。。。。


        vector_fiq:
        disable_fiq
        subspc, lr, #4

        vector_addrexcptn:
        bvector_addrexcptn
        .align5

        .LCvswi:
        .wordvector_swi

        .globl__stubs_end
        __stubs_end:

        宏vector_stub代表的程序段如下:name, mode, correction存儲傳入的參數之

        .macrovector_stub, name, mode, correction=0
        .align5

        vector_name:
        .if correction
        sublr, lr, #correction//修正返回地址,也就是中斷處理完之后要執行的指令的地址
        .endif

        @
        @ Save r0, lr_ (parent PC) and spsr_
        @ (parent CPSR)
        @

        ///保存返回地址到堆棧,因為很快要使用r0寄存器,所以也要保存r0。sp后沒有!所以sp指向的位置并沒有變化。

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

        mrslr, spsr
        strlr, [sp, #8]@ save spsr

        // 向上增長的棧。

        // 此時的這個棧是中斷模式下的棧,ARM下中斷模式下和系統模式下的

        // 棧是不同的。雖然ARM提供了七個模式,但Linux只使用了兩個,一

        // 個是用戶模式,另一個為系統模式,所以這個棧只是一個臨時性的棧。

        /*

        在arch/arm/include/asm/ptrace.h中有處理器的七種工作模式的定義

        #define USR_MODE0x00000010
        #define FIQ_MODE0x00000011
        #define IRQ_MODE0x00000012
        #define SVC_MODE0x00000013
        #define ABT_MODE0x00000017
        #define UND_MODE0x0000001b
        #define SYSTEM_MODE0x0000001f

        */
        mrsr0, cpsr
        eorr0, r0, #(mode ^ SVC_MODE)
        msrspsr_cxsf, r0////把spsr設置為管理模式。//對spsr的所有控制為進行寫操作,將r0的值全部注入spsr

        @
        @ the branch table must immediately follow this code
        @
        //andlr, lr, #0x0f// 這條指令之后lr中位spsr的低4位,上面跳轉表有16項就是對應這16個狀態
        //movr0, sp//用r0保存堆棧指針的地址

        //在對這段程序分析時要記住這段程序是以宏vector_stub的形式放在跳轉表前面的。

        //將跳轉表中對應的地址條目存入lr。因為跳轉表中每一個條目都是4個字節long,所以此處左移兩位
        ldrlr, [pc, lr, lsl #2]

        movspc, lr@ branch to handler in SVC mode//程序跳轉。
        ENDPROC(vector_name)
        .endm

        在此我們以在用戶空間發生中斷異常為例,即程序跳轉到__irq_usr處。

        .align5
        __irq_usr:
        usr_entry//usr_entry是一個宏代表一段程序插入此處,宏usr_entry所代表的程序段將在下面分析(1)

        kuser_cmpxchg_check

        #ifdef CONFIG_TRACE_IRQFLAGS
        bltrace_hardirqs_off
        #endif

        //接著看get_thread_info, 它也是個宏,用來獲取當前線程的地址。也將在后續分析。tsk存放的是線程結構體的地址。

        /*

        線程結構體原型如下在文件include/linux/sched.h中

        struct thread_info {
        struct task_struct*task;/* main task structure */
        unsigned longflags;
        struct exec_domain*exec_domain;/* execution domain */
        intpreempt_count;/* 0 => preemptable, <0 => BUG */
        __u32 cpu; /* should always be 0 on m68k */
        struct restart_block restart_block;
        };

        */
        get_thread_info tsk(2)
        #ifdef CONFIG_PREEMPT

        //TI_PREEMPT在文件archarmkernelasm-offsets.c中定義是線程結構體thread_info 的成員preempt_count在

        //結構體thread_info中的偏移

        /*

        內核態可剝奪內核,只有在 preempt_count 為 0 時, schedule() 才會被調用,其檢查
        是否需要進行進程切換,需要的話就切換。

        */
        ldrr8, [tsk, #TI_PREEMPT]//獲取preempt_count
        addr7, r8, #1@ increment it//將該成員加一
        strr7, [tsk, #TI_PREEMPT]//間改變后的值存入preempt_count
        #endif

        irq_handler//調用中斷操作函數,irq_handler是一個宏,在后續描述(3)
        #ifdef CONFIG_PREEMPT
        ldrr0, [tsk, #TI_PREEMPT]
        strr8, [tsk, #TI_PREEMPT]
        teqr0, r7
        strner0, [r0, -r0]
        #endif
        #ifdef CONFIG_TRACE_IRQFLAGS
        bltrace_hardirqs_on
        #endif

        movwhy, #0//why在文件arch/arm/kernel/entry-header.S中定義為r8。:why.reqr8
        bret_to_user//返回到用戶態,該宏在文件 linux/arch/arm/kernel/entry-common.S中定義。(4)
        UNWIND(.fnend)
        ENDPROC(__irq_usr)

        下面分別對上面四處宏進行分析。(usr_entry,get_thread_info tsk,irq_handler,ret_to_user)

        (1)

        .macrousr_entry
        UNWIND(.fnstart)
        UNWIND(.cantunwind)@ dont unwind the user space

        //S_FRAME_SIZE在文件archarmkernelasm-offsets.c中定義表示 寄存器結構體pt_regs的大小結構體

        //pt_regs中有 r0~cpsr 18個寄存器即72個字節。
        subsp, sp, #S_FRAME_SIZE//為寄存器pt_regs結構體建立堆棧空間,讓堆棧指針sp 指向r0 。

        //stmib為存儲前加,所以此處留出了用于存儲r0的空間,將r1 - r12存入堆棧。sp后沒加!

        //所以sp指向的堆棧位置沒有變,一直指向用于存儲r0的存儲空間。

        stmibsp, {r1 - r12}

        //將中斷前r0,lr,spsr的值取出存放在r1 - r3中,此時的r0是作為堆棧的sp在使用的。

        //它的值是指向中斷前r0的值在堆棧中存放的位置。在寄存器結構體pt_regs在堆棧中的位置上面。

        ldmiar0, {r1 - r3}

        //S_PC即是pt_regs中的PC寄存器位置,讓r0指向該位置。雖然S_PC還沒有存入堆棧但它在堆棧中的位置存在
        addr0, sp, #S_PC

        movr4, #-1//在r4中放入一個無效值。

        strr1, [sp]//r1中存放的是中斷前r0的值,此時將該值存入堆棧,上面已解釋過在堆棧中流出r0的位置的問題。

        //此時r2-r4存放的是中斷前的lr, spsr的值和無效之。

        //此時將這些值存入pt_regs中寄存器在堆棧中對應的位置,即此時將中斷前的lr, spsr的值和無效之

        //存入寄存器結構體pt_regs的ARM_pc,ARM_cpsr,ARM_ORIG_r0中。
        stmiar0, {r2 - r4}
        stmdbr0, {sp, lr}^//stmdb是遞減取值,將ARM_lr,ARM_sp存入lr,sp中。


        alignment_trap r0

        //宏zero_fp在文件arch/arm/kernel/entry-header.S中定義,清零fp。
        zero_fp
        .endm

        上面的提到的struct pt_regs,在include/asm/ptrace.h中定義

        struct pt_regs {
        long uregs[18];
        };

        #define ARM_cpsruregs[16]
        #define ARM_pcuregs[15]
        #define ARM_lruregs[14]
        #define ARM_spuregs[13]
        #define ARM_ipuregs[12]
        #define ARM_fpuregs[11]
        #define ARM_r10uregs[10]
        #define ARM_r9uregs[9]
        #define ARM_r8uregs[8]
        #define ARM_r7uregs[7]
        #define ARM_r6uregs[6]
        #define ARM_r5uregs[5]
        #define ARM_r4uregs[4]
        #define ARM_r3uregs[3]
        #define ARM_r2uregs[2]
        #define ARM_r1uregs[1]
        #define ARM_r0uregs[0]
        #define ARM_ORIG_r0uregs[17]

        (2)

        //宏macroget_thread_info在文件arch/arm/kernel/entry-header.S中定義。用來獲取當前線程的地址。

        /*

        include/linux/sched.h中:

        union thread_union {

        struct thread_info thread_info; // 線程屬性

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

        };

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

        THREAD_SIZE在文件arch/arm/include/asm/thread_info.h中定義:#define THREAD_SIZE8192

        */

        .macroget_thread_info, rd
        movrd, sp, lsr #13
        movrd, rd, lsl #13
        .endm

        (3)

        //宏irq_handler文件arch/arm/kernel/entry-armv.S中定義:

        .macroirq_handler

        //宏get_irqnr_preamble是一個空操作,在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S中定義
        get_irqnr_preamble r5, lr

        //宏get_irqnr_and_base通過讀取寄存器INTPND來獲得中斷號。在該宏中獲取的一些參量將存于這些寄存器中r0, r6, r5, lr。

        //宏get_irqnr_and_base定義在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S,這個宏后續講到。
        1:get_irqnr_and_base r0, r6, r5, lr
        movner1, sp
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        adrnelr, 1b

        /*

        // 通過上面的宏get_irqnr_and_base為調用asm_do_IRQ準備了參數中斷號。

        于是調用asm_do_IRQ來處理中斷。函數asm_do_IRQ()是中斷處理函數的C語言入口。此函數將在后續討論。

        函數asm_do_IRQ()在文件linux/arch/arm/kernel/irq.c中實現。

        */
        bneasm_do_IRQ

        #ifdef CONFIG_SMP
        。。。。。。

        #endif

        .endm

        get_irqnr_and_base是平臺相關的,這個宏查詢ISPR(IRQ掛起中斷服務寄存器,當有需要處理的中斷時,這個寄存器的相應位會置位,任意時刻,最多一個位會置位),計算出的中斷號放在irqnr指定的寄存器中;這個宏在不同的ARM芯片上是不一樣的,這個宏主要作用在于就是獲得發生中斷的中斷號,對于s3c2440,代碼在arch/arm/mach-s3c2410/include/entry-macro.S里,該宏處理完后,r0 = 中斷號。

        .macroget_irqnr_and_base, irqnr, irqstat, base, tmp

        movbase, #S3C24XX_VA_IRQ

        @@ try the interrupt offset register, since it is there

        ldrirqstat, [ base, #INTPND ]
        teqirqstat, #0
        beq1002f
        ldrirqnr, [ base, #INTOFFSET ]
        movtmp, #1
        tstirqstat, tmp, lsl irqnr
        bne1001f

        @@ the number specified is not a valid irq, so try
        @@ and work it out for ourselves

        movirqnr, #0@@ start here

        @@ work out which irq (if any) we got

        movstmp, irqstat, lsl#16
        addeqirqnr, irqnr, #16
        moveqirqstat, irqstat, lsr#16
        tstirqstat, #0xff
        addeqirqnr, irqnr, #8
        moveqirqstat, irqstat, lsr#8
        tstirqstat, #0xf
        addeqirqnr, irqnr, #4
        moveqirqstat, irqstat, lsr#4
        tstirqstat, #0x3
        addeqirqnr, irqnr, #2
        moveqirqstat, irqstat, lsr#2
        tstirqstat, #0x1
        addeqirqnr, irqnr, #1

        @@ we have the value
        1001:
        addsirqnr, irqnr, #IRQ_EINT0
        1002:
        @@ exit here, Z flag unset if IRQ

        .endm

        (4)

        宏ret_to_user在文件arch/arm/kernel/entry-common.S下定義:

        ENTRY(ret_to_user)
        ret_slow_syscall:
        disable_irq//禁止中斷
        ldrr1, [tsk, #TI_FLAGS]//獲取線程結構體thread_union的flags成員
        tstr1, #_TIF_WORK_MASK//判斷task是否被阻塞
        bnework_pending //根據需要進行進程的切換,該段代碼在下面講述。
        no_work_pending://不需要進程切換
        /* perform architecture specific actions before user return */
        arch_ret_to_user r1, lr

        @ slow_restore_user_regs
        ldrr1, [sp, #S_PSR]@ get calling cpsr
        ldrlr, [sp, #S_PC]!@ get pc
        msrspsr_cxsf, r1@ save in spsr_svc //// spsr里保存好被中斷代碼處的狀態(cpsp)
        ldmdbsp, {r0 - lr}^//恢復中斷前寄存器的值恢復到各個寄存器。
        movr0, r0
        addsp, sp, #S_FRAME_SIZE - S_PC
        movspc, lr//返回用戶態
        ENDPROC(ret_to_user)

        在arch/arm/kernel/entry-common.S中

        work_pending:
        tstr1, #_TIF_NEED_RESCHED//判斷是否需要調度進程
        bnework_resched//進程調度
        tstr1, #_TIF_SIGPENDING
        beqno_work_pending//無需調度,返回
        movr0, sp@ regs
        movr2, why@ syscall
        bldo_notify_resume
        bret_slow_syscall@ Check work again

        work_resched:
        blschedule//調用進程切換函數。

        這里只講了在用戶模式下的中斷處理,在內核模式下的處理方式也大抵相仿,就不再贅言了。

        中斷處理函數的C語言入口

        asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
        {
        struct pt_regs *old_regs = set_irq_regs(regs);

        irq_enter();//進入中斷上下文


        if (irq >= NR_IRQS)
        handle_bad_irq(irq, &bad_irq_desc);
        else
        generic_handle_irq(irq);//根據中斷號獲取中斷描述結構體,并調用其中斷處理函數。


        irq_finish(irq);//退出中斷上下文

        irq_exit();
        set_irq_regs(old_regs);
        }

        //函數generic_handle_irq()是函數generic_handle_irq_desc()的包裝。

        static inline void generic_handle_irq(unsigned int irq)
        {
        generic_handle_irq_desc(irq, irq_to_desc(irq));
        }

        /*

        如果實現了上層中斷處理函數desc->handle_irq就調用它,實際上在中斷處理函數s3c24xx_init_irq()中已為每一個

        中斷線分配了一個上層中斷處理函數。

        如果desc->handle_irq為空就調用通用中斷處理函數__do_IRQ(irq);,在干函數中調用了函數handle_IRQ_event(),

        在函數handle_IRQ_event()中執行了該條中斷線上的每一個中斷例程。

        */

        static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
        {
        #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
        desc->handle_irq(irq, desc);
        #else
        if (likely(desc->handle_irq))
        desc->handle_irq(irq, desc);
        else
        __do_IRQ(irq);
        #endif
        }



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 遂溪县| 彝良县| 郴州市| 全州县| 白城市| 龙里县| 桃园市| 马鞍山市| 广水市| 喜德县| 西青区| 乐清市| 大悟县| 长宁县| 武隆县| 陆丰市| 巨野县| 高邮市| 呼伦贝尔市| 衡山县| 兴和县| 兴隆县| 蒙山县| 南京市| 江华| 惠来县| 平遥县| 浦县| 军事| 安图县| 东方市| 囊谦县| 北宁市| 枞阳县| 合江县| 纳雍县| 中江县| 安宁市| 潜江市| 监利县| 郎溪县|