现在的位置: 首页 > 综合 > 正文

ARM Linux中断机制分析

2013年09月17日 ⁄ 综合 ⁄ 共 21981字 ⁄ 字号 评论关闭
文章目录

 

ARM Linux中断机制分析

——以用户模式产生irq中断为例

以下代码基于内核linux2.6.38.3(trimslice官网下载)

本文主要分析ARM发生中断时的处理流程,以在usr态发生IRQ为例,即usr—>irq为例讨论。

 

 

 

1.内核异常向量表的初始化

1.1初始化大致流程

ARM linux内核启动时,首先运行的是linux/arch/arm/kernel/head.S,进行一些初始化工作,然后调用main.c->start_kernel()函数,进而调用trap_init()(或者调用early_trap_init()函数进行初始化)、init_IRQ()函数进行中断初始化、建立异常向量表.

1.2异常向量表的建立

异常向量表的建立过程就是拷贝过程,为了将内核代码写成位置无关的,有很多地方需要注意。

1.2.1异常向量表基地址确定

在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0x00000000,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:

V=0        ~        0x00000000~0x0000001C

V=1        ~        0xffff0000~0xffff001C

注:CP15控制寄存器说明详见ARM ARMB4-1690.

在异常向量表初始化前运行的文件linux/arch/arm/kernel/head.S中设置了CP15寄存器(在 ~/arch/arm/mm/proc-v7.S文件中的__v7_setup函数中设置),这里通过设置CP15c1寄存器已经确定了异常向量表的基地址(0xffff0000)。

 

1.2.2 异常向量表拷贝过程

内核代码编译生成后,需要将异常向量表拷贝到指定位置(0x00000000 or 0xffff0000),这就需要将内核中的异常向量表设计成与位置无关的。

本文所使用内核版本使用了early_trap_init()代替trap_init()来初始化异常。

early_trap_init()在linux/arch/arm/kernel/traps.c中,代码如下:

 

1、CONFIG_VECTORS_BASE在处理器型号确定后就已经确定,其值在内核配置完成后自动生成,保存在.config文件中。本文使用内核版本在maketrimslice_deconfig后自动生成的.config中定义:CONFIG_VECTORS_BASE=0xffff0000,也就是说,异常向量表的基地址0xffff0000。

~/arch/arm/kernel/traps.c   line783

 

void __init early_trap_init(void)

{

#if defined(CONFIG_CPU_USE_DOMAINS)

         unsigned long vectors = CONFIG_VECTORS_BASE;                //vectors是中断向量基地址

#else

         unsigned long vectors = (unsigned long)vectors_page;

#endif

/*以下这些都在arch/arm/kernel/entry-armv.S下定义*/

         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;

 

         /*

          * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

          * into the vector page, mapped at 0xffff0000, and ensure these

          * are visible to the instruction stream.

          */

/*__vectors_end __vectors_start之间为异常向量表。__stubs_end
__stubs_start
之间是异常处理的位置。这些变量定义都在arch/arm/kernel/entry-armv.S*/

 

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

         /*

          * Do processor specific fixups for the kuser helpers

          */

         kuser_get_tls_init(vectors);

         /*

          * Copy signal return handlers into the vector page, and

          * set sigreturn to be a pointer to these.

          */

         memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),

                sigreturn_codes, sizeof(sigreturn_codes));

         memcpy((void *)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),

                syscall_restart_code, sizeof(syscall_restart_code));

         flush_icache_range(vectors, vectors + PAGE_SIZE);

         modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

} 


以下是__vectors_start, __vectors_end,__stubs_end__stubs_start的定义。

  

arch/arm/kernel/entry-armv.S

         .globl       __vectors_start

__vectors_start:

 ARM(     swi   SYS_ERROR0   )

 THUMB(         svc   #0             )

 THUMB(         nop                   )

         W(b)         vector_und + stubs_offset

         W(ldr)      pc, .LCvswi + stubs_offset

         W(b)         vector_pabt + stubs_offset

         W(b)         vector_dabt + stubs_offset

         W(b)         vector_addrexcptn + stubs_offset

         W(b)         vector_irq + stubs_offset

         W(b)         vector_fiq + stubs_offset

         .globl       __vectors_end

 

         .globl       __stubs_start

__stubs_start:

/*

 * Interrupt dispatcher

 */

        vector stub irq,IRQ_MODE,4    //vector_stub是一个宏,展开后是一块代码,后面紧跟着跳转表

          .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

……

……

……

……

         .globl       __stubs_end

__stubs_end:

 

         .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 

vector_stub     irq, IRQ_MODE, 4  展开后如下:

// -------------------------------- begin 展开

.align       5                //将异常入口强制进行2^5字节对齐,即一个cache line大小对齐,出于性能考虑

vector_irq:

       sub  lr, lr, 4         //需要调整pc返回值,对于irq异常,将lr减去4,对于其他异常需要作出不同调整

      

       @ Save r0, lr_<exception> (parent PC) and spsr_<exception>

       @ (parent CPSR)

       @

       stmia       sp, {r0, lr}            @ save r0, lr

       mrs  lr, spsr

       str   lr, [sp, #8]             @ save spsr 

      

       @ Prepare for SVC32 mode.  IRQs remain disabled.

       @

       mrs  r0, cpsr

       eor   r0, r0, IRQ_MODE ^ SVC_MODE)

       msr  spsr_cxsf, r0

      

       @ the branch table must immediately follow this code

       @

       and  lr, lr, #0x0f

       mov r0, sp

       ldr   lr, [pc, lr, lsl #2]

       movs      pc, lr                     @ branch to handler in SVC mode

       // -------------------------------- end 展开

 

异常向量表的拷贝过程用图表示比较清晰,如下图所示:

图一  向量表搬移及offset偏移量计算示图

图一说明:上面两条有方向的横线,横线方向代表地址生长方向,下面那个是Code/Load视图,是搬移前的代码在生成的二进制内核中的组织情况,上面的Exec view是代码在内存中开始执行后的分配情况。

 

2.linux对ARM异常、中断的处理流程

2.1当IRQ发生时,硬件完成的操作

R14_irq= address of next instruction to be executed + 4/*将寄存器lr_mode设置成返回地址*/

       SPSR_irq = CPSR                /*保存处理器当前状态、中断屏蔽位以及各条件标志位*/

       CPSR[4:0] = 0b10010         /*设置当前程序状态寄存器CPSR中相应的位进入IRQ模式*/

       CPSR[5] = 0                        /*在ARM状态执行*/

                                                 /*CPSR[6] 不变*/

       CPSR[7] = 1                       /*禁止正常中断*/

       If high vectors configured then

              PC=0xFFFF0018          /*将程序计数器(PC)值设置成该异常中断的中断向量地址,从

*而跳转到相应的异常中断处理程序处执行,对于ARMv7向量表普遍是0xFFFF0018

*/

       else

              PC=0x00000018

 

2.2 指令流跳转过程

以上CPU操作完成后,PC跳转到0xFFFF0018,该地址就是指令W(b) vector_irq + stubs_offset所在地址。然后跳转到vector_stub         irq,IRQ_MODE, 4去执行相应的异常、中断处理函数。

接下来具体看代码:

         .globl        __vectors_start                      
//
异常向量表开始0xFFFF0000

__vectors_start:

 ARM(     swi   SYS_ERROR0   )

 THUMB(         svc   #0             )

 THUMB(         nop                    )

         W(b)         vector_und + stubs_offset

         W(ldr)      pc, .LCvswi + stubs_offset

         W(b)         vector_pabt + stubs_offset

         W(b)         vector_dabt + stubs_offset

         W(b)         vector_addrexcptn + stubs_offset

         W(b)         vector_irq + stubs_offset       //中断发生后的跳转地址0xFFFF0018

         W(b)         vector_fiq + stubs_offset

         .globl        __vectors_end

__vectors_end:

 

stubs_offset只是个偏移量,用来修正跳转地址的,主要的操作是vector_irq执行。vector_irq是由宏vector_stub  irq,IRQ_MODE,4(IRQ_MODE在include\asm\ptrace.h中定义:0x12)生成。以下是vector_irq生成后的代码(汇编代码中,@开始的语句、///**/都代表注释)

.align       5

vector_irq:

       sub  lr, lr, 4                                        //因为异常发生时,cpupc地址+4赋值给lr,这里做修正。     

       @ Save r0, lr_<exception> (parent PC) and spsr_<exception>

       @ (parent CPSR)

       @

       stmia       sp, {r0, lr}      
//
保存r0, lr,到irq堆栈中(每个异常都有属于自己的堆栈)

       mrs  lr, spsr                                
//lr
保存spsr_irq的值,即usr状态的cpsr的值(见2.1

       str   lr, [sp, #8]            
//
保存 spsr[sp+8]

     

       @ Prepare for SVC32 mode.  IRQs remain disabled.

       @

       mrs  r0, cpsr

       eor   r0, r0,#( IRQ_MODE ^ SVC_MODE| PSR_ISETSTATE)    //  PSR_ISETSTATE:选择ARM/Thumb指令集
  
                                          

       msr  spsr_cxsf, r0                            
 //
这里的cxsf表示从低到高分别占用的48bit的数据域

 

 

异或运算是可以交换位置的,也即A^B^C等价于A^C^B。所以这里的r0^( IRQ_MODE ^ SVC_MODE| PSR_ISETSTATE)等价于r0^ IRQ_MODE ^SVC_MODE,由于r0的低5位模式位与IRQ_MODE相同,所以r0^ IRQ_MODE的运算结果的低5位全被清零,然后再^SVC_MODE,也即低5位被设置为SVC_MODE其它位保持不变。

       @ the branch table must immediately follow this code

       and  lr, lr, #0x0f              
//
提取发生异常前的处理器模式,这里也就是usr模式

       mov  r0, sp              

       ldr   lr, [pc, lr, lsl #2]                                             

       movs      pc, lr 
                  

 

 


sp是SVC32模式下的堆栈指针,这里将它移到r0中,就可以作为C函数的第一个参数,即C函数中的pt_regs参数。

pc是当前地址+8,也就是本段代码后面紧跟的跳转表的基地址,lr用于在跳转表中索引,lr左移两位等同于*4,因为每个条目是4字节。从usr模式进入irq模式,则lr=pc+4*0,若从svc模式进入irq,则lr=pc+4*3,即__irq_svc的地址,其他地址进入__irq_invalid出错处理,因为不能从其他模式进入irq异常。

假设这里是从usr进入irq,则执行跳转表中的第一条指令。跳转的基准地址为当前pc,因为ARMv4是三级流水线结构的,它总是指向当前指令的下两条指令的地址,尽管以后版本的指令流水线扩展为5级和8级,但是这一特性一直被兼容处理,也即pc(excute)=pc(fetch) + 8,其中:pc(fetch)是当前正在执行的指令,就是之前取该指令时候的PC的值;pc(execute):当前指令执行的,计算中如果用到pc,是指此时pc的值。

mov指令的目标寄存器是PC,且指令以S结束,则它会把spsr的值恢复给cpsr,上面说到当前的spsr中保存的是r0的值,即svc模式。所以本条指令是跳转到__irq_usr的同时将处理器模式转为svc模式。异常处理一定要进入svc模式的原因是:异常处理一定要进入PL1特权级;另一个原因是使能嵌套中断。具体原因在问题4中解释。关于__irq_svc和__irq_invalid暂时不讨论。

/*

 * Interrupt dispatcher以下跳转表必须紧跟在vector_irq之后

 */

vector_stub                                                                         irq, IRQ_MODE, 4   //生成vector_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)

   /* SVC进入中断的处理函数 */

.long                                                                                     __irq_svc                    @  3  (SVC_26 / SVC_32)

.long                                                                                     __irq_invalid                       @  4

.long                                                                                     __irq_invalid                       @  5

.long                                                                                     __irq_invalid                       @  6

 

 

图2IRQ中断处理跳转示意图

 

注意,以下操作都是在svc模式中,因为要借用SVC模式进行ISP处理,所以需要保存所有SVC模式下的寄存器到SVC堆栈中,最后才去调用中断服务例程(ISPirq_handler

 

2.2.1 __irq_usr

         .align        5

__irq_usr:

         usr_entry         //用于用户模式下发生中断时初始化中断处理堆栈,同时保存所有SVC态寄存器到堆栈。

         kuser_cmpxchg_check   //对低版本的ARM核来说,用户态无法实现原子比较交换。如果用户态在处理原

                        //子比较交换的过程中发生中断,需要特殊处理,略过

         get_thread_info tsk   //根据当前sp指针,将该指针最右边13位清0,获得当前任务的thread_info

#ifdef CONFIG_PREEMPT   //如果可以抢占,递增任务的抢占计数

         ldr    r8, [tsk, #TI_PREEMPT]           //T被定义为offsetof(struct thread_info, preempt_count),显然通过tsk

                                                                     可以很容易得到进程preempt_count成员的地址了

         add  r7, r8, #1                   @ increment it

         str    r7, [tsk, #TI_PREEMPT]

#endif

         irq_handler              //中断服务例程,后面分析

#ifdef CONFIG_PREEMPT 

         ldr    r0, [tsk, #TI_PREEMPT]   // 获得当前的抢占计数

         str    r8, [tsk, #TI_PREEMPT]  //并将r8中的值保存回去。相当于将前一步递增的抢占计数减回去了

         teq   r0, r7                                 
//r0,r7
是调用irq_handler前后的抢占计数,这里进行比较,是防止驱动的ISR

                            //程序没有配对操作抢占计数导致系统错误

 ARM(     strne        r0, [r0, -r0]      ) //如果抢占计数被破坏,则强制写入0.

 THUMB(         movne     r0, #0                )

 THUMB(         strne        r0, [r0]     )

#endif

         mov why, #0

         b       ret_to_user     //返回到用户态

 UNWIND(.fnend            )

ENDPROC(__irq_usr)

 

 


接下来分别看各个函数的功能

arch/arm/include/asm/ptrace.h

struct pt_regs {

         unsigned long uregs[18];

};

#endif /* __KERNEL__ */

#define ARM_cpsr           uregs[16]

#define ARM_pc              uregs[15]

#define ARM_lr                uregs[14]

#define ARM_sp              uregs[13]

#define ARM_ip               uregs[12]

#define ARM_fp              uregs[11]

#define ARM_r10            uregs[10]

#define ARM_r9              uregs[9]

……

#define ARM_ORIG_r0   uregs[17]

 

 pt_regs结构体定义,后面要用到。

.macro      usr_entry  //usr_entry宏定义

 UNWIND(.fnstart )

 UNWIND(.cantunwind )        @ don't unwind the user space

sub sp, sp, #S_FRAME_SIZE          @ S_FRAME_SIZE定义在 trimslice-kernel\arch\arm\kernel\arm-offsets.cS_FRAME_SIZE被定义为
sizeof(struct pt_regs)
的大小=18*4=72字节,将svc32堆栈指针向低地址方向移动一个pt_regs结构大小,用于保存svc模式下的寄存器现场。

ARM(        stmib        sp, {r1 - r12}    )  @svc32堆栈中保存寄存器现场

 THUMB(         stmia        sp, {r0 - r12}    )

 

         ldmia        r0, {r3 - r5}     
@
前面r0存放的是irq模式下的栈指针sp的值,从栈中取出r0-r2存放到r3-r5

         add  r0, sp, #S_PC           
@ here for interlock avoidance
S_PC定义为offsetof(struct pt_regs, ARM_pc),所

                                                    以这里通过add指令将r0指向ARM_pc

         mov r6, #-1                       @ 
r6
中保存-1

 

         str    r3, [sp]             @ save the "real" r0 copied从中断栈中取出真实的r0存放到pt_regs->r0中。

                                            @ from the exception stack

 

2.2.2 usr_entry

 

         @ We are now ready to fill in the remaining blanks on the stack:

         @

         @  r4 - lr_<exception>, already fixed up for correct return/restart

         @  r5 - spsr_<exception>

         @  r6 - orig_r0 (see pt_regs definition in ptrace.h)

         @

         @ Also, separately save sp_usr and lr_usr

         @

         stmia        r0, {r4 - r6}  
//stmia
svc模式下的寄存器r4-r6保存到ARM_pcARM_cpsr

                                                             ARM_ORIG_r0,显然ARM_ORIG_r0保存了-1r6)这个常量

 ARM(     stmdb      r0, {sp, lr}^                        )
//stmdb
指令的^标志表示存储发生中断的模式下的sp,lr寄存器

                                                                    ARM_spARM_lr中。

 THUMB(         store_user_sp_lr r0, r1, S_SP - S_PC      )

         @ Enable the alignment trap while in kernel mode

         alignment_trap r0          
//
alignment_trap在配置CONFIG_ALIGNMENT_TRAP时有效,如果开启了该选

                                                     //项,中断处理中将支持对齐跟踪

         @ Clear FP to mark the first stack frame

         zero_fp                             
//zero_fp
用来设置fp栈帧寄存器为0

#ifdef CONFIG_IRQSOFF_TRACER

         bl      trace_hardirqs_off

#endif

         .endm                       
@usr_entry
宏定义结束

 

 

以上的指令的作用可以总结如下,其中将普通寄存器r1到r12保存到ARM_r1- ARM_r12,这相当于把发生中断时的寄存器r1-r12进行了保存。接下来保存中断发生时的r0,lr_irq和spsr_irq保存到r1-r3,r4赋值为-1,它们接下来将被使用。接下来保存r0到ARM_r0,lr_irq,spsr_irq和-1到ARM_pc ARM_cpsr ARM_ORIG_R0,注意到stmdb指令中的"^",它保存sp_usr和lr_usr分别到ARM_sp和ARM_lr,显然这里将所有中断发生时的寄存器都进行了保存。

 

图3  保存中断堆栈

2.2.3 get_thread_info

get_thread_info宏用来根据当前的sp值,通过lsr和lsl分别右移左移13位,相当于对sp向下圆整到8K对齐。这里也就是thread_info所在的地址。

arch/arm/kernel/entry-header.S

  .macro  get_thread_info, rd

  mov     \rd, sp, lsr #13

  mov     \rd, \rd, lsl #13

  .endm

 

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

/*

 * Interrupt handling.  Preserves r7, r8, r9

 */

         .macro     irq_handler

#ifdef CONFIG_MULTI_IRQ_HANDLER

         ldr    r5, =handle_arch_irq

         mov r0, sp

         ldr    r5, [r5]

         adr   lr, BSYM(9997f)

         teq   r5, #0

         movne     pc, r5

#endif

         arch_irq_handler_default

9997:

         .endm

2.2.4 irq_handler

 

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

/*

 * Interrupt handling.  Preserves r7, r8, r9

 */

         .macro     irq_handler

#ifdefCONFIG_MULTI_IRQ_HANDLER

         ldr    r5,=handle_arch_irq

         mov r0,sp

         ldr    r5,[r5]

         adr   lr,BSYM(9997f)

         teq   r5,#0

         movne     pc,r5

#endif

         arch_irq_handler_default

9997:

         .endm

2.2.5 arch_irq_handler_default

irq_handler是真正的IRQ中断处理入口,在中断处理中需要预留r7,r8和r9寄存器。它们被用来处理内核抢占。在没有配置MULTI_IRQ_HANDLER 的情况下,irq_handler的逻辑很简单,就是简单的调用arch_irq_handler_default。

如果配置了该选项,平台代码可以修改全局变量:handle_arch_irq,这里只讨论默认实现.

arch/arm/include/asm/entry_macro_multi.S

/*

 * Interrupt handling.  Preserves r7, r8, r9

 */

         .macro     arch_irq_handler_default    //get_irqnr_preamble用来获取中断状态寄存器基地址

         get_irqnr_preamble  r5, lr   //将中断控制器的状态寄存器的地址存储到r5

1:     get_irqnr_and_base r0, r6, r5, lr //判断中断号,通过r0返回

         movne     r1, sp  //如果还存在中断,就将sp作为第二个参数,调用asm_do_IRQsp目前指向pt_regs

         @

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

         @

         adrne       lr, BSYM(1b) //这里将lr设置为get_irqnr_and_base的第二条指令,因为第二次循环时,不必执行其第一条指令(加载寄存器基址)

         bne  asm_do_IRQ   //将中断号、pt_regs(中断前的寄存器现场)传递给asm_do_IRQasm_do_IRQ返回时,

                                            //会返回到get_irqnr_and_base处,直到所有中断都已经处理完毕才退出循环。

#ifdef CONFIG_SMP  //针对SMP系统的处理

         /*

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

          * preserved from get_irqnr_and_base above

          */

         ALT_SMP(test_for_ipi r0, r6, r5, lr) //
这里是从寄存器中读取ipi标志

         ALT_UP_B(9997f)

         movne     r1, sp

         adrne       lr, BSYM(1b)  //同理,这里也是将返回地址设置为ALT_SMP的第二条指令,构造成一个循环

         bne  do_IPI    //只要存在IPI就调用do_IPI,并循环直到处理完所有IPI

 

#ifdef CONFIG_LOCAL_TIMERS   // 同理,这里循环处理多核系统中的本地时钟中断。

         test_for_ltirq r0, r6, r5, lr

         movne     r0, sp

         adrne       lr, BSYM(1b)

         bne  do_local_timer

#endif

#endif

9997:

         .endm

 

 

2.2.6 get_irqnr_preamble

get_irqnr_preamble用于获得中断状态寄存器基地址,特定于CPU,这里CPU用的是tegra,其定义如下

 

 

/* arch/arm/mach-tegra/include/mach/entry-macro.S     

         /* Uses the GIC interrupt controller built into the cpu */

#define ICTRL_BASE (IO_CPU_VIRT + 0x40100)  // #define IO_CPU_VIRT     0xFE000000

.macro     get_irqnr_preamble, base, tmp

         movw \base, #(ICTRL_BASE & 0x0000ffff)

         movt \base, #((ICTRL_BASE & 0xffff0000) >> 16)

         .endm

 

 



2.2.7 get_irqnr_and_base

get_irqnr_and_base用来获取中断号。

/* arch/arm/mach-tegra/include/mach/entry-macro.S     

.macro     get_irqnr_and_base, irqnr, irqstat, base, tmp

         ldr \irqnr, [\base, #0x20]        @ EVT_IRQ_STS

         cmp \irqnr, #0x80

         .endm

 

 

get_irqnr_preamble和get_irqnr_and_base两个宏由machine级的代码定义,目的就是从中断控制器中获得IRQ编号,紧接着就调用asm_do_IRQ,从这个函数开始,中断程序进入C代码中,传入的参数是IRQ编号和寄存器结构指针,

2.2.8 asm_do_IRQ

 

图4 asm_do_IRQ流程图

asm_do_IRQ是ARM处理硬件中断的核心函数,第一个参数指定了硬中断的中断号,第二个参数是寄存器备份组成的一个结构体,保存了中断发生时的模式对应的寄存器的值,在中断返回时使用。

 

linux/arch/arm/kernel/irq.c

asmlinkage void __exception_irq_entry

asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

         struct pt_regs *old_regs = set_irq_regs(regs);
//
获得寄存器值

         irq_enter();  

         /*

          * Some hardware gives randomly wrong interrupts.  Rather

          * than crashing, do something sensible.

          */

         if (unlikely(irq >= nr_irqs)) {

                  if (printk_ratelimit())

                          printk(KERN_WARNING "Bad IRQ%u\n", irq);

                  ack_bad_irq(irq);

         } else {

                  generic_handle_irq(irq);

         }

         /* AT91 specific workaround */

         irq_finish(irq);

         irq_exit();

         set_irq_regs(old_regs);

}

 

 

 

2.2.9 irq_enter

irq_enter是更新一些系统的统计信息,同时在__irq_enter宏中禁止了进程的抢占。虽然在产生IRQ时,ARM会自动把CPSR中的I位置位,禁止新的IRQ请求,直到中断控制转到相应的流控层后才通过local_irq_enable()打开。那为何还要禁止抢占?这是因为要考虑中断嵌套的问题,一旦流控层或驱动程序主动通过local_irq_enable打开了IRQ,而此时该中断还没处理完成,新的irq请求到达,这时代码会再次进入irq_enter,在本次嵌套中断返回时,内核不希望进行抢占调度,而是要等到最外层的中断处理完成后才做出调度动作,所以才有了禁止抢占这一处理。

linux/kernel/softirq.c

void irq_enter(void)

{

         int cpu = smp_processor_id();

         rcu_irq_enter();

         if (idle_cpu(cpu) && !in_interrupt()) {

                  /* Prevent raise_softirq from needlessly waking up ksoftirqd

                   * here, as softirq will be serviced on return from interrupt.*/

                  local_bh_disable();

                  tick_check_idle(cpu);

                  _local_bh_enable();

         }

         __irq_enter();

}

#define __irq_enter()                                        \

         do {                                             \

                  account_system_vtime(current);           \

                  add_preempt_count(HARDIRQ_OFFSET);     \

                  trace_hardirq_enter();                     \

         } while (0)

 

2.2.10 generic_handle_irq

~/include /linux/Irqdesc.h

/*

 * Architectures call this to let the generic IRQ layer

 * handle an interrupt. If the descriptor is attached to an

 * irqchip-style controller then we call the ->handle_irq() handler,

 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.

 */

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

{

         desc->handle_irq(irq, desc);  //调用该irq注册的函数处理,该函数在注册中断时填写irq_desc结构体时指定。

}                                                           
// handle_irq
是个函数指针,它用来实现中断处理器的电流处理。电流处理分为边

                                                             //沿跳变处理和电平处理。

 

static inline void generic_handle_irq(unsigned int irq) //该函数是与体系结构无关的通用逻辑层API

{

         generic_handle_irq_desc(irq, irq_to_desc(irq));

}

 

 

2.2.11 ret_to_user

以上内容处理结束后,退回用户层。

 

arch/arm/kernel/entry-common.S

/*

 * "slow" syscall return path.  "why" tells us if this was a real syscall.

 */

ENTRY(ret_to_user)

ret_slow_syscall:

         disable_irq                                 @ disable interrupts

         ldr    r1, [tsk, #TI_FLAGS]   //从任务的TI_FLAGS标志判断是否需要处理抢占或者信号。

         tst    r1, #_TIF_WORK_MASK

         bne  work_pending    //处理抢占或者信号

no_work_pending:         //没有抢占或者信号需要处理,或者已经处理完毕,开始退回用户态

#if defined(CONFIG_IRQSOFF_TRACER)  //退回用户态必然会打开中断,这里记录下打开中断的事实,供调试用。

         asm_trace_hardirqs_on

#endif

         /* perform architecture specific actions before user return */

         arch_ret_to_user r1, lr   //在返回用户态前,处理各个体系结构的钩子

 

         restore_user_regs fast = 0, offset = 0  //恢复寄存器现场,并切回用户态。这里不再具体分析恢复方式。

ENDPROC(ret_to_user)

 

 

 



3.问题及解答

问题1:vector_irq已经是异常、中断处理的入口函数了,为什么还要加stubs_offset?(  b    vector_irq + stubs_offset)

答:(1)内核刚启动时(head.S文件)通过设置CP15的c1寄存器已经确定了异常向量表的起始地址(例如0xffff0000),因此需要把已经写好的内核代码中的异常向量表考到0xffff0000处,只有这样在发生异常时内核才能正确的处理异常。

(2)从上面代码看出向量表和stubs(中断处理函数)都发生了搬移,如果还用b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。至于为什么搬移后的地址是vector_irq+stubs_offset,如图一所示。下图是搬移示意图更加清晰说明了搬移过程。。

 

 

问题2:为什么在异常向量表中,用b指令跳转而不是用ldr绝对跳转?

答:因为使用b指令跳转比绝对跳转(ldr pc,XXXX)效率高,正因为效率高,所以把__stubs_start~__stubs_end之间的代码考到了0xffff0200起始处。

注意:

因为b跳转指令只能在+/-32MB之内跳转,所以必须拷贝到0xffff0000附近。

b指令是相对于当前PC的跳转,当汇编器看到 B 指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。

             

 

 

经过Uboot的启动,内核跳入linux/arch/arm/kernel/head.S开始执行。

问题1:为什么首先进入head.S开始执行?

 

 

 

 

问题3:为什么首先进入head.S开始执行?

答:内核源代码顶层目录下的Makefile制定了vmlinux生成规则:

# vmlinux image - includingupdated kernel symbols

vmlinux: $(vmlinux-lds)$(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o)FORCE

其中$(vmlinux-lds)是编译连接脚本,对于ARM平台,就是arch/arm/kernel/vmlinux-lds文件。vmlinux-init也在顶层Makefile中定义:

vmlinux-init := $(head-y)$(init-y)

head-y 在arch/arm/Makefile中定义:

head-y:=arch/arm/kernel/head$(MMUEX T).o arch/arm/kernel/init_task.o

ifeq ($(CONFIG_MMU),)

MMUEXT := -nommu

endif

对于有MMU的处理器,MMUEXT为空白字符串,所以arch/arm/kernel/head.O 是第一个连接的文件,而这个文件是由arch/arm/kernel/head.S编译产生成的。

综合以上分析,可以得出结论,非压缩ARM Linux内核的入口点在arch/arm/kernel/head.s中。

 

问题4: 中断为什么必须进入svc模式?

一个最重要原因是:

如果一个中断模式(例如从usr进入irq模式,在irq模式中)中重新允许了中断,并且在这个中断例程中使用了BL指令调用子程序,BL指令会自动将子程序返回地址保存到当前模式的sp(即r14_irq)中,这个地址随后会被在当前模式下产生的中断所破坏,因为产生中断时CPU会将当前模式的PC保存到r14_irq,这样就把刚刚保存的子程序返回地址冲掉。为了避免这种情况,中断例程应该切换到SVC或者系统模式,这样的话,BL指令可以使用r14_svc来保存子程序的返回地址。

 

问题5:为什么跳转表中有的用了b指令跳转,而有的用了ldr  px,xxxx?

         W(b)         vector_und+ stubs_offset

         W(ldr)      pc, .LCvswi + stubs_offset

         W(b)         vector_pabt+ stubs_offset

         W(b)         vector_dabt+ stubs_offset

         W(b)         vector_addrexcptn+ stubs_offset

         W(b)         vector_irq+ stubs_offset      

         W(b)         vector_fiq+ stubs_offset

 

.LCvswi:

         .word       vector_swi

由于系统调用异常的代码编译在其他文件中,其入口地址与异常向量相隔较远,使用b指令无法跳转过去(b指令只能相对当前pc跳转32M范围)。因此将其地址存放到LCvswi中,并从内存地址中加载其入口地址,原理与其他调用是一样的。这也就是为什么系统调用的速度稍微慢一点的原因。

问题6:为什么ARM能处理中断?

因为ARM架构的CPU有一个机制,只要中断产生了,CPU就会根据中断类型自动跳转到某个特定的地址(即中断向量表中的某个地址)。如下表所示,既是中断向量表。


 ARM中断向量表及地址

问题7:什么是High vector?

A:在Linux3.1.0,arch/arm/include/asm/system.hline121 有定义如下:

#if __LINUX_ARM_ARCH__ >=4

#define vectors_high()  (cr_alignment & CR_V)

#else

#define vectors_high()  (0)

#endif

意思就是,如果使用的ARM架构大于等于4,则

抱歉!评论已关闭.