Linux中断处理流程 - 中断向量表跳来跳去跳到C

时间:2021-05-10 04:22:06

转自:http://blog.csdn.net/coldsnow33/article/details/12917759

参考:http://lwn.net/Articles/302043/

一 中断的处理流程

1 发生中断时,CPU执行异常向量vector_irq的代码。
2 在vertor_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ。
3 asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。
4 handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等。
5 handle_irq逐个调用用户在action链表中注册的处理函数(针对共享中断)。
现在的内核中断的入口函数asm_do_IRQ也可以换成自己定义的了。
 

二 中断系统分层

看到别人这样分层挺好的。
1 与中断向量、中断标志有关的arch封装硬件层;
2 各种触发方式、响应方式控制的流控制层;
3 开关中断、中断封装的逻辑层;
4 留给驱动编程接口的驱动层。

 

三  中断向量表

1 异常向量表

arch/arm/kernel/entry-armv.S 中_vectors_start和__vectors_end之间保存了异常向量表。
[cpp]  view plain copy
  1.     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start  
  2.   
  3.     .globl  __vectors_start  
  4. __vectors_start:  
  5.  ARM(   swi SYS_ERROR0  )  
  6.  THUMB( svc #0      )  
  7.  THUMB( nop         )  
  8.     W(b)    vector_und + stubs_offset  
  9.     W(ldr)  pc, .LCvswi + stubs_offset  
  10.     W(b)    vector_pabt + stubs_offset  
  11.     W(b)    vector_dabt + stubs_offset  
  12.     W(b)    vector_addrexcptn + stubs_offset  
  13.     W(b)    vector_irq + stubs_offset  
  14.     W(b)    vector_fiq + stubs_offset  
  15.   
  16.     .globl  __vectors_end  
  17. __vectors_end:  
arm的8个异常向量与7种工作模式不是一一对应,但是相关联的。向量0是reset,如果是cpu运行到了向量0说明是系统出错,用软件中断SYS_ERROR0来处理;向量2也是跳到软中断;软中断会陷入svc模式。向量3和4都会陷入abt模式。什么是svc模式?什么是abt模式?

2 arm工作模式

7种工作模式分别是:
1 用户模式(usr):正常程序的执行状态;
2 快速中断模式(fiq);
3 中断模式(irq);
4 管理模式(svc):超级用户,操作系统的一种保护模式;
5 系统模式(sys):运行特权级的系统任务;
6 数据访问终止模式(abt):数据或指令预取;
7 未定义指令终止模式(und):执行未定义指令。
除了用户模式,其余6种都是特权模式;特权模式中除了系统模式外的其余5种被称为异常模式。这些模式在哪里定义呢?在程序状态寄存器cpsr中,还有个spsr是它的备份,叫备份程序状态寄存器,它们格式相同。cpsr格式如下:
Linux中断处理流程 - 中断向量表跳来跳去跳到C
M[4:0]5个bit用来确定处理器模式,bit[4]表示是26bit,还是32bit寻址,只用了低4bit就能区别mode了;这样每个处理器都可以定义16种mode。PSR bits定义的代码在arch/arm/include/uapi/asm/ptrace.h中。
[cpp]  view plain copy
  1. #define USR26_MODE  0x00000000  
  2. #define FIQ26_MODE  0x00000001  
  3. #define IRQ26_MODE  0x00000002  
  4. #define SVC26_MODE  0x00000003  
  5. #define USR_MODE    0x00000010  
  6. #define FIQ_MODE    0x00000011  
  7. #define IRQ_MODE    0x00000012  
  8. #define SVC_MODE    0x00000013  
  9. #define ABT_MODE    0x00000017  
  10. #define HYP_MODE    0x0000001a  
  11. #define UND_MODE    0x0000001b  
  12. #define SYSTEM_MODE 0x0000001f  
  13. #define MODE32_BIT  0x00000010  
  14. #define MODE_MASK   0x0000001f  
  15. #define PSR_T_BIT   0x00000020  
  16. #define PSR_F_BIT   0x00000040  
  17. #define PSR_I_BIT   0x00000080  
  18. #define PSR_A_BIT   0x00000100  
  19. #define PSR_E_BIT   0x00000200  
  20. #define PSR_J_BIT   0x01000000  
  21. #define PSR_Q_BIT   0x08000000  
  22. #define PSR_V_BIT   0x10000000  
  23. #define PSR_C_BIT   0x20000000  
  24. #define PSR_Z_BIT   0x40000000  
  25. #define PSR_N_BIT   0x80000000  

3 异常向量表跳转

根据异常向量表,有异常的时候就可以跳转了;但是跳到哪里呢?有的标号是找不到的,比如vector_irq是哪里?

3.1 vector_srub宏定义

[cpp]  view plain copy
  1. .macro  vector_stub, name, mode, correction=0  
  2.     .align  5  
  3.   
  4. vector_\name: //定义了一个vector_name的label,如果参数name是irq,那就是vector_irq  
  5.     .if \correction//如果要修正lr PC指针,它是返回地址  
  6.     sub lr, lr, #\correction  
  7.     .endif  
  8.   
  9.     @  
  10.     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>  
  11.     @ (parent CPSR)  
  12.     @  
  13.     stmia   sp, {r0, lr}        @ save r0, lr  
  14.     mrs lr, spsr  
  15.     str lr, [sp, #8]        @ save spsr  
  16.   
  17.     @  
  18.     @ Prepare for SVC32 mode.  IRQs remain disabled.  
  19.     @  
  20.     mrs r0, cpsr  
  21.     eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)//异常模式  
  22.     msr spsr_cxsf, r0  
  23.   
  24.     @  
  25.     @ the branch table must immediately follow this code  
  26.     @  
  27.     and lr, lr, #0x0f  
  28.  THUMB( adr r0, 1f          )  
  29.  THUMB( ldr lr, [r0, lr, lsl #2]    )  
  30.     mov r0, sp  
  31.  ARM(   ldr lr, [pc, lr, lsl #2]    )  
  32.     movs    pc, lr          @ branch to handler in SVC mode  
  33. ENDPROC(vector_\name)  
  34.   
  35.     .align  2  
  36.     @ handler addresses follow this label  
  37. 1:  
  38.     .endm  
根据这个宏,如果要找到vector_irq,那么找到vector_stub irq, mode, correction就可以了。它就是中断向量表了。我们会看到这个宏基本被定义在了_stubs_start和__stubs_end之间,是的,这里定义了各种异常的入口,当然向量0直接陷入swi SYS_ERROR0,不需要再跳来跳去了。
[cpp]  view plain copy
  1.     .globl  __stubs_start  
  2. __stubs_start:  
  3. /* 
  4.  * Interrupt dispatcher 
  5.  */  
  6.     vector_stub irq, IRQ_MODE, 4  
  7.   
  8.     .long   __irq_usr           @  0  (USR_26 / USR_32)  
  9.     .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)  
  10.     .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)  
  11.     .long   __irq_svc           @  3  (SVC_26 / SVC_32)  
  12.     .long   __irq_invalid           @  4  
  13.     .long   __irq_invalid           @  5  
  14.     .long   __irq_invalid           @  6  
  15.     .long   __irq_invalid           @  7  
  16.     .long   __irq_invalid           @  8  
  17.     .long   __irq_invalid           @  9  
  18.     .long   __irq_invalid           @  a  
  19.     .long   __irq_invalid           @  b  
  20.     .long   __irq_invalid           @  c  
  21.     .long   __irq_invalid           @  d  
  22.     .long   __irq_invalid           @  e  
  23.     .long   __irq_invalid           @  f  
  24.   
  25. /* 
  26.  * Data abort dispatcher 
  27.  * Enter in ABT mode, spsr = USR CPSR, lr = USR PC 
  28.  */  
  29.     vector_stub dabt, ABT_MODE, 8  
  30.   
  31.     .long   __dabt_usr          @  0  (USR_26 / USR_32)  
  32.     .long   __dabt_invalid          @  1  (FIQ_26 / FIQ_32)  
  33.     .long   __dabt_invalid          @  2  (IRQ_26 / IRQ_32)  
  34.     .long   __dabt_svc          @  3  (SVC_26 / SVC_32)  
  35.     .long   __dabt_invalid          @  4  
  36.     .long   __dabt_invalid          @  5  
  37.     .long   __dabt_invalid          @  6  
  38.     .long   __dabt_invalid          @  7  
  39.     .long   __dabt_invalid          @  8  
  40.     .long   __dabt_invalid          @  9  
  41.     .long   __dabt_invalid          @  a  
  42.     .long   __dabt_invalid          @  b  
  43.     .long   __dabt_invalid          @  c  
  44.     .long   __dabt_invalid          @  d  
  45.     .long   __dabt_invalid          @  e  
  46.     .long   __dabt_invalid          @  f  
  47.   
  48. /* 
  49.  * Prefetch abort dispatcher 
  50.  * Enter in ABT mode, spsr = USR CPSR, lr = USR PC 
  51.  */  
  52.     vector_stub pabt, ABT_MODE, 4  
  53.   
  54.     .long   __pabt_usr          @  0 (USR_26 / USR_32)  
  55.     .long   __pabt_invalid          @  1 (FIQ_26 / FIQ_32)  
  56.     .long   __pabt_invalid          @  2 (IRQ_26 / IRQ_32)  
  57.     .long   __pabt_svc          @  3 (SVC_26 / SVC_32)  
  58.     .long   __pabt_invalid          @  4  
  59.     .long   __pabt_invalid          @  5  
  60.     .long   __pabt_invalid          @  6  
  61.     .long   __pabt_invalid          @  7  
  62.     .long   __pabt_invalid          @  8  
  63.     .long   __pabt_invalid          @  9  
  64.     .long   __pabt_invalid          @  a  
  65.     .long   __pabt_invalid          @  b  
  66.     .long   __pabt_invalid          @  c  
  67.     .long   __pabt_invalid          @  d  
  68.     .long   __pabt_invalid          @  e  
  69.     .long   __pabt_invalid          @  f  
  70.   
  71. /* 
  72.  * Undef instr entry dispatcher 
  73.  * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
  74.  */  
  75.     vector_stub und, UND_MODE  
  76.   
  77.     .long   __und_usr           @  0 (USR_26 / USR_32)  
  78.     .long   __und_invalid           @  1 (FIQ_26 / FIQ_32)  
  79.     .long   __und_invalid           @  2 (IRQ_26 / IRQ_32)  
  80.     .long   __und_svc           @  3 (SVC_26 / SVC_32)  
  81.     .long   __und_invalid           @  4  
  82.     .long   __und_invalid           @  5  
  83.     .long   __und_invalid           @  6  
  84.     .long   __und_invalid           @  7  
  85.     .long   __und_invalid           @  8  
  86.     .long   __und_invalid           @  9  
  87.     .long   __und_invalid           @  a  
  88.     .long   __und_invalid           @  b  
  89.     .long   __und_invalid           @  c  
  90.     .long   __und_invalid           @  d  
  91.     .long   __und_invalid           @  e  
  92.     .long   __und_invalid           @  f  
  93.   
  94.     .align  5  
  95.   
  96. /*============================================================================= 
  97.  * Undefined FIQs 
  98.  *----------------------------------------------------------------------------- 
  99.  * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC 
  100.  * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. 
  101.  * Basically to switch modes, we *HAVE* to clobber one register...  brain 
  102.  * damage alert!  I don't think that we can execute any code in here in any 
  103.  * other mode than FIQ...  Ok you can switch to another mode, but you can't 
  104.  * get out of that mode without clobbering one register. 
  105.  */  
  106. vector_fiq:  
  107.     subs    pc, lr, #4  
  108.   
  109. /*============================================================================= 
  110.  * Address exception handler 
  111.  *----------------------------------------------------------------------------- 
  112.  * These aren't too critical. 
  113.  * (they're not supposed to happen, and won't happen in 32-bit data mode). 
  114.  */  
  115.   
  116. vector_addrexcptn:  
  117.     b   vector_addrexcptn  
  118.   
  119. /* 
  120.  * We group all the following data together to optimise 
  121.  * for CPUs with separate I & D caches. 
  122.  */  
  123.     .align  5  
  124.   
  125. .LCvswi:  
  126.     .word   vector_swi  
  127.   
  128.     .globl  __stubs_end  
  129. __stubs_end:  
我们看到每个异常向量只有usr和svc有入口,而其他都是invalid,是因为linux只会从usr(application)和svc(kernel)两种mode跳转到exception。为什么只会从这两种mode跳转呢?因为linux异常前的状态;要么是内核态处于svc模式,执行__xxx_svc代码;要么是用户态处于usr模式,执行__xxx_usr代码。

3.2 异常向量表的copy

找到了vector_irq,只跳了一半;这个跳转是这样写的,W(b) vector_irq + stubs_offset;
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
根据前面的分析__vectors_start 是异常表向量的入口,这是一个总表;__stubs_start是具体异常向量的入口。这个0x200是哪里来的?为什么跳到vector_irq + stubs_offset就是vector_irq中断向量的地址了?这肯定与异常向量的存放地址有关。
start_kernel()-->setup_arch()-->paging_init()-->devicemaps_init()-->early_trap_init()
[cpp]  view plain copy
  1. void __init early_trap_init(void *vectors_base)  
  2. {  
  3.     unsigned long vectors = (unsigned long)vectors_base;  
  4.     extern char __stubs_start[], __stubs_end[];  
  5.     extern char __vectors_start[], __vectors_end[];  
  6.     extern char __kuser_helper_start[], __kuser_helper_end[];  
  7.     int kuser_sz = __kuser_helper_end - __kuser_helper_start;  
  8.   
  9.     vectors_page = vectors_base;  
  10.   
  11.     /* 
  12.      * Copy the vectors, stubs and kuser helpers (in entry-armv.S) 
  13.      * into the vector page, mapped at 0xffff0000, and ensure these 
  14.      * are visible to the instruction stream. 
  15.      */  
  16.     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);  
  17.     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);  
  18.     memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);  
  19.   
  20.     /* 
  21.      * Do processor specific fixups for the kuser helpers 
  22.      */  
  23.     kuser_get_tls_init(vectors);  
  24.   
  25.     /* 
  26.      * Copy signal return handlers into the vector page, and 
  27.      * set sigreturn to be a pointer to these. 
  28.      */  
  29.     memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),  
  30.            sigreturn_codes, sizeof(sigreturn_codes));  
  31.   
  32.     flush_icache_range(vectors, vectors + PAGE_SIZE);  
  33.     modify_domain(DOMAIN_USER, DOMAIN_CLIENT);  
  34. }  
这里完成了异常向量的copy;总表从vectors处开始,各种异常向量表从vectors + 0x200开始;这个0x200出现了。这个vectors是个什么地址?
[cpp]  view plain copy
  1. static void __init devicemaps_init(struct machine_desc *mdesc)  
  2. {  
  3.     struct map_desc map;  
  4.     unsigned long addr;  
  5.     void *vectors;  
  6.   
  7.     /* 
  8.      * Allocate the vector page early. 
  9.      */  
  10.     vectors = early_alloc(PAGE_SIZE);  
  11.   
  12.     early_trap_init(vectors);  
  13.   
  14.     for (addr = VMALLOC_START; addr; addr += PMD_SIZE)  
  15.         pmd_clear(pmd_off_k(addr));  
  16.   
  17.     ......  
  18.   
  19.     /* 
  20.      * Create a mapping for the machine vectors at the high-vectors 
  21.      * location (0xffff0000).  If we aren't using high-vectors, also 
  22.      * create a mapping at the low-vectors virtual address. 
  23.      */  
  24.     map.pfn = __phys_to_pfn(virt_to_phys(vectors));  
  25.     <span style="color:#cc0000;">map.virtual = 0xffff0000;</span>  
  26.     map.length = PAGE_SIZE;  
  27.     map.type = MT_HIGH_VECTORS;  
  28.     create_mapping(&map, false);  
  29.   
  30.     if (!vectors_high()) {  
  31.         map.virtual = 0;  
  32.         map.type = MT_LOW_VECTORS;  
  33.         create_mapping(&map, false);  
  34.     }  
  35.   
  36.     /* 
  37.      * Ask the machine support to map in the statically mapped devices. 
  38.      */  
  39.     if (mdesc->map_io)  
  40.         mdesc->map_io();  
  41.     fill_pmd_gaps();  
  42.   
  43.     /* Reserve fixed i/o space in VMALLOC region */  
  44.     pci_reserve_io();  
  45.   
  46.     /* 
  47.      * Finally flush the caches and tlb to ensure that we're in a 
  48.      * consistent state wrt the writebuffer.  This also ensures that 
  49.      * any write-allocated cache lines in the vector page are written 
  50.      * back.  After this point, we can start to touch devices again. 
  51.      */  
  52.     local_flush_tlb_all();  
  53.     flush_cache_all();  
  54. }  
这个vectors映射后的虚拟地址是0xffff0000,关于这个地址还有一个说法。据说这个地址是受arm中协处理器CP15中的c1控制寄存器中的v位(bit[13])控制的。Arm裸机程序一般都使用默认的值0,将中断向量表放在0x00000000~0x0000001c中;如果为1,中断向量表放在0xffff0000~0xffff001c中。arch/arm/kernel/head.S中:
[cpp]  view plain copy
  1. ENTRY(stext)    //内核入口  
  2. ......  
  3. movs    r10, r5             @ invalid processor (r5=0)?  
  4. ......  
  5. ARM(    add pc, r10, #PROCINFO_INITFUNC )  
ARM()也是一个宏,同样在文件arch/arm/include/asm/unified.h中定义,当配置内核为生成ARM镜像(#define PSR_ISETSTATE 0),则为:#define ARM(x...) x
经过前面的折腾r10中保存的是procinfo结构的地址。PROCINFO_INITFUNC符号在arch/arm/kernel/asm-offsets.c文件中定义为:
[cpp]  view plain copy
  1. DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));  
  2. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  
oiffsetof宏container_of里也用到了,返回的是MEMBER在TYPE结构中的偏移量。也就是说PROCINFO_INITFUNC就是__cpu_flush在proc_info_list中的偏移量。
[cpp]  view plain copy
  1. struct proc_info_list {  
  2.     unsigned int        cpu_val;  
  3.     unsigned int        cpu_mask;  
  4.     unsigned long       __cpu_mm_mmu_flags; /* used by head.S */  
  5.     unsigned long       __cpu_io_mmu_flags; /* used by head.S */  
  6.     unsigned long       __cpu_flush;        /* used by head.S */  
  7.     const char      *arch_name;  
  8.     const char      *elf_name;  
  9.     unsigned int        elf_hwcap;  
  10.     const char      *cpu_name;  
  11.     struct processor    *proc;  
  12.     struct cpu_tlb_fns  *tlb;  
  13.     struct cpu_user_fns *user;  
  14.     struct cpu_cache_fns    *cache;  
  15. };  
add pc, r10, #PROCINFO_INITFUNC后的PC跳到proc_info_list结构的__cpu_flush去执行。这是平台相关的了,可这个__cpu_flush是什么?是proc_info_list中的一个成员啊,proc_info_list是怎么找到的?__lookup_processor_type负责找到处理器ID号对应的proc_info_list结构。实际是通过__proc_info_begin和__proc_info_end找到的,它们定义在vmlinux.lds.S的连接脚本中,用于标注proc_info_list结构的起始和结束地址。
[cpp]  view plain copy
  1. #define PROC_INFO                           \  
  2.     . = ALIGN(4);                           \  
  3.     VMLINUX_SYMBOL(__proc_info_begin) = .;              \  
  4.     *(.proc.info.init)                      \  
  5.     VMLINUX_SYMBOL(__proc_info_end) = .;  
这段代码的意思是:__proc_info_begin的位置放所有文件“.proc.info.init”段的内容,接着的位置是__proc_info_end。也可以把它们理解为一个地址标号。既然能通过它们找到proc_info_list,说明之前放进去了啊,什么时候放的呢?arch/arm/mm/proc-v7.S中:
section ".proc.info.init", #alloc, #execinstr//
段名.proc.info.init,#alloc表示Section contains allocated data本段包含可分配数据, #execinstr表示Section contains executable instructions本段是可执行段。每一个段是以段名为开始,以下一个段为结束或者文件结束。在这个段里我们看到了__v7_proc_info这个标号,它对应的就是proc_info_list结构了,找到__cpu_flush,原来是存了一条指令b __v7_setup。__v7_setup里做了很多大事情,其中一件就是设置这个V位了。要知道怎么设置的,需要知道一个标号v7_crval。
[cpp]  view plain copy
  1.     /* 
  2.      *   AT 
  3.      *  TFR   EV X F   IHD LR    S 
  4.      * .EEE ..EE PUI. .TAT 4RVI ZWRS BLDP WCAM 
  5.      * rxxx rrxx xxx0 0101 xxxx xxxx x111 xxxx < forced 
  6.      *   11    0 110    1  0011 1100 .111 1101 < we want 
  7.      */  
  8.     .align  2  
  9.     .type   v7_crval, #object  
  10. v7_crval:  
  11.     crval   clear=0x0120c302, mmuset=0x30c23c7d, ucset=0x00c01c7c  
  12. crval是一个宏在arch/arm/mm/proc-macro.S中,这里定义了很多的宏。  
  13.     .macro  crval, clear, mmuset, ucset  
  14. #ifdef CONFIG_MMU  
  15.     .word   \clear  
  16.     .word   \mmuset  
  17. #else  
  18.     .word   \clear  
  19.     .word   \ucset  
  20. #endif  
  21.     .endm  
[cpp]  view plain copy
  1. __v7_setup:  
  2. ......  
  3.     adr r5, v7_crval //v7_crval标号地址传给r5  
  4.     ldmia   r5, {r5, r6} //clear传给r5,mmuset传给r6  
  5.     mrc p15, 0, r0, c1, c0, 0       @ read control register//把cp15 c1读到r0  
  6.     bic r0, r0, r5          @ clear bits them//clearr5中的bit  
  7.     orr r0, r0, r6          @ set them//setr6中的bit  
  8. ......  
mmuset=0x30c23c7d,bit[13]也就置1了。

3.3 异常向量的跳转

跑了很远,异常还没有跳到地方呢,还得回来接着跳。已经知道异常向量copy到了虚拟地址0xffff0000开始的一段区域,copy后vector的排列如下:
Linux中断处理流程 - 中断向量表跳来跳去跳到C

要研究的跳转指令是:W(b) vector_dabt + stubs_offset等价于b vector_dabt + __vectors_start + 0x200 - __stubs_start。
图中下轴是拷贝前,上轴是拷贝后。如果异常向量没有copy的话,发生数据预取异常时,会跳到下轴的__vectors_start异常向量表中的t2处,那在t2处放一条b vector_dabt就会跳到vector_dabt标号处执行;为什么能跳到呢?先看一下b跳转的指令格式:
bit[31:28]:条件码
bit[27:25]:101
bit24:是否链接标识
bit[23:0]:跳转的偏移量
b跳转是一个相对跳转,依赖于当前的PC值和label相对于当前PC值的偏移量,这个偏移量在编译链接的时候就已经确定了,会存在b跳转指令机器码的bit[23:0],是24bit有符号数;因为ARM指令是word对齐的,最低2bit永远为0;所以左移两位后表示有效偏移的而是26bit的有符号数,也就是可以向前和向后都可以跳转32MB的范围。
下轴是向量表原始的存储位置,b vector_dabt机器码中存储的偏移量(链接时就确定了)就是位于这条指令的PC值相对于vector_dabt标号的偏移量,所以一跳就跳到了。但是如果要跳到copy后的vector_dabt标号处,还是用那个偏移量,鬼知道跳到哪里去了;但是我们也不用找偏移量,只有找到要跳的label地址,编译的时候会自动算偏移量的。对照上轴copy后的情况,当发生数据预取异常时,会跳到异常向量表(E(__vectors_start)开始的)的异常向量上,就是t1的位置,t1里存了一条指令W(b) vector_dabt + stubs_offset,这条指令执行完就跳转到数据预取异常的入口E(vector_dabt)处了,也就是要跳到L2结束的地址。这个绝对地址为:
__vector_start + 0x200 + vector_dabt - __stubs_start//就是L1+L2
1 b是相对跳转,与位置无关;
2 这些标号都是重定向之后的标号;
3 重定位之后的向量表向量之间的相对位置没有变。
所以最终的跳转指令就变成了W(b) vector_dabt + stubs_offset。这个lalel的计算,决定了异常向量表,一定是那样copy的;异常向量表的copy,决定了这里一定是这样跳转的。

 

四 中断服务子程序

终于跳到异常向量的入口vector_irq,设置发生异常之前处理器处于usr模式(),那下一跳就是__irq_usr,再下一跳就是irq_handler。
[cpp]  view plain copy
  1.     .macro  irq_handler  
  2. #ifdef CONFIG_MULTI_IRQ_HANDLER  
  3.     ldr r1, =handle_arch_irq  
  4.     mov r0, sp  
  5.     adr lr, BSYM(9997f)  
  6.     ldr pc, [r1]  
  7. #else  
  8.     arch_irq_handler_default  
  9. #endif  
  10. 9997:  
  11.     .endm  
这里又分了两种情况:
1 默认会执行arch_irq_handler_default,arch/arm/include/asm/entry-macro-multi.S中的一个宏。
2 #ifdef CONFIG_MULTI_IRQ_HANDLER,则允许平台代码可以动态设置irq处理程序,即可以修改全局变量handle_arch_irq。

1 默认执行arch_irq_handler_default

[cpp]  view plain copy
  1.     .macro  arch_irq_handler_default  
  2.     get_irqnr_preamble r6, lr  
  3. 1:  get_irqnr_and_base r0, r2, r6, lr  
  4.     movne   r1, sp  
  5.     @  
  6.     @ routine called with r0 = irq number, r1 = struct pt_regs *  
  7.     @  
  8.     adrne   lr, BSYM(1b)  
  9.     bne asm_do_IRQ  
adrne lr, BSYM(1b)
BSYM()是一个宏,在文件arch/arm/include/asm/unified.h中定义为:
#define BSYM(sym) sym
该语句等价于 adrne lr, 1b
把lb例程的地址加载进lr寄存器中,为了方便之后调用的函数返回时,直接执行lr例程。这个1b是个什么例程呢?b就是backward,1b就是向后跳转到标号1处。
最后就跳转到中断的C入口asm_do_IRQ,该函数有两个参数;跳转之前需要填充这两个参数。而且需要r0 = irq number, r1 = struct pt_regs *这样放,因为当参数<=4的时候,会使用r0~r4寄存器来存;参数>4时,其余的会压入栈中,入栈顺序和参数顺序相反;后入的先出,所以相反。
[cpp]  view plain copy
  1. asmlinkage void __exception_irq_entry  
  2. asm_do_IRQ(unsigned int irq, struct pt_regs *regs)  
  3. {  
  4.     handle_IRQ(irq, regs);  
  5. }  
这两个参数就是通过get_irqnr_preamble和get_irqnr_and_base两个宏传的,具体代码是平台相关,都通过VIC或者GIC中断控制器。

2 动态设置handle_arch_irq

start_kernel()->setup_arch(&command_line)
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
平台相关,基本不是VIC:vic_handle_irq,就是GIC:gic_handle_irq。这个函数的参数只有一个参数struct pt_regs *regs,可见irq是控制器里分析出来的。struct pt_regs *regs结构干什么用的?其实是和一堆寄存器联系在一起的,函数执行、调用、回溯都会用到它们。
[cpp]  view plain copy
  1. #define ARM_cpsr    uregs[16]  
  2. #define ARM_pc      uregs[15]  
  3. #define ARM_lr      uregs[14]  
  4. #define ARM_sp      uregs[13]  
  5. #define ARM_ip      uregs[12]  
  6. #define ARM_fp      uregs[11]  
  7. #define ARM_r10     uregs[10]  
  8. #define ARM_r9      uregs[9]  
  9. #define ARM_r8      uregs[8]  
  10. #define ARM_r7      uregs[7]  
  11. #define ARM_r6      uregs[6]  
  12. #define ARM_r5      uregs[5]  
  13. #define ARM_r4      uregs[4]  
  14. #define ARM_r3      uregs[3]  
  15. #define ARM_r2      uregs[2]  
  16. #define ARM_r1      uregs[1]  
  17. #define ARM_r0      uregs[0]  
  18. #define ARM_ORIG_r0 uregs[17]  
折腾到现在,终于跳进了中断函数的C入口。

3 中断描述符irq_desc初始化

中断进入C后,能够顺利执行;是依赖于中断描述符irq_desc的。主要是根据中断号irq num找到对的irq_desc[irq num]继续处理。这个irq_desc[]结构是如何初始化的?

1 CONFIG_SPARSE_IRQ未定义,基于数组的方式,静态的;
2 否则,基于基数数的方式,动态的。
CONFIG_SPARSE_IRQ稀疏IRQ号支持,它允许在小型设备上(例如嵌入式设备)定义一个很高的CONFIG_NR_CPUS值,但仍然不希望占用太多内核"memory footprint"(一段可以被操作或被管理的内存区域)的场合。稀疏IRQ也更适合NUMA平台,因为它以一种对NUMA更友好的方式分发中断描述符。不确定的选"N"。
start_kernel()->early_irq_init()
数组方式: ->desc_set_defaults()静态初始化NR_IRQS个irq_desc;
基数树方式:->alloc_desc() + irq_insert_desc()动态分配一个irq_desc,其余用的时候分配;一般由中断控制器分配。
数组方式是kernel/irq/irqdesc.c中静态初始化的,所以不需要alloc和insert动作了。
[cpp]  view plain copy
  1. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {  
  2.     [0 ... NR_IRQS-1] = {  
  3.         .handle_irq = handle_bad_irq,  
  4.         .depth      = 1,  
  5.         .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),  
  6.     }  
  7. };  
这就是静态初始化的数组了。