Chinaunix首页 | 论坛 | 博客
  • 博客访问: 23967
  • 博文数量: 7
  • 博客积分: 1616
  • 博客等级: 上尉
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-28 19:47
文章分类
文章存档

2010年(7)

最近访客

分类: 嵌入式

2010-05-18 15:13:54

三 响应中断

首先在分析源码之前,让我们了解一些原理性的东西我们都知道在处理中断要保存当前现场状态,然后才能处理中断,处理完之后还要把现场状态恢复过来才能返回到被中断的地方继续执行,这里要说明的是在指令跳转到中断向量的地方开始执行之前,CPU帮我们做了哪些事情:

 
   R14_irq = 
要执行的下条指令地址 + 4   //这里的下条指令是相对于被中断指令的下条。即返回地址

   SPSR_irq = CPSR    //保存的现场状态,r0r12要由我们软件来保存(如果需要的话)

   CPSR[4:0] = 0b10010   //进入中断模式

   CPSR[5] = 0     //ARM模式下执行(不是Thumb)

   CPSR[7] = 1      //关掉IRQ中断, FIQ还是开着

   PC = 0Xffff0018  /  0x00000018  //根据异常向量表的位置,跳转到特定的中断向量处去执行。

  

   更详细的关于异常处理的细节可参考<>

    接下来我们在来分析watchdog产生中断后的处理流程:

watchdog超时时将会产生中断,中断号就是IRQ_WDT,当产生中断时,系统将从跳转表中的中断位置开始运行,对于我们这篇文章来说:是从0xffff0000 + 24处开始运行。 这个地址的指令是:

 b  vector_irq + stubs_offset

即直接跳转到 vector_irq 处去运行。这些都在中断初始化的时候分析过了。

我们来看 vector_irq,它是通过宏vector_stub来定义的:

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

/*

 * Interrupt dispatcher

 */

    vector_stub irq, 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:

    .macro  vector_stub, name, mode, correction=0

    .align  5

 

vector_\name:

    .if \correction

    sub lr, lr, #\correction

    .endif

 

    @

    @ Save r0, lr_ (parent PC) and spsr_

    @ (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, #(\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

    .endm

 

这样展开后 vector_irq 如下所示:

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

vector_irq:

    .if 4

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

处理完后的返回地址在中断处理完后该值会赋给 PC

    sub lr, lr, #4 

    .endif

 

    @

    @ Save r0, lr_ (parent PC) and spsr_

    @ (parent CPSR)

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

    stmia   sp, {r0, lr}       @ save r0, lr,保存r0,lr到栈上,这里的栈是中断模式下的。

    mrs lr, spsr    @获取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   @spsr设置成管理模式

 

    @

    @ 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 @ pc = lr, cpsr = spsr

    .endm

 

movs 的目的对象如果是pc的话,则还会把spsr赋值给cpsr,上面我们看到spsr被设成管理模式,因此这条语句过后的代码也就跑在了管理模式下。

此时的栈情况如下:

可以看到该汇编代码主要是把被中断的代码在执行过程中的状态(cpsr), 返回地址(lr)等保存在中断模式下的栈里,然后进入到管理模式下去执行中断,同时令r0 = sp,这样可以在管理模式下找到该地址,进而获取spsr等信息。该汇编代码最终根据被中断的代码所处的模式跳转到相应的处理程序中去。

注意管理模式下的栈和中断模式下的栈不是同一个。同时由于在上面的代码中栈指针(sp)没有进行移位,因此即使后面的代码没对这个栈进行出栈操作,也不会因为不断的产生中断而导致栈溢出。

下面我们以用户模式产生中断为例,它将跳转到__irq_usr(vector_irq的定义)

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

__irq_usr:

    usr_entry  @宏,保存各寄存器,便于返回的时候恢复

 

    get_thread_info tsk  @获取保存当前task信息的地址

#ifdef CONFIG_PREEMPT

    ldr r8, [tsk, #TI_PREEMPT]     @ get 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]

    teq r0, r7

    strne   r0, [r0, -r0]

#endif

 

    mov why, #0

    b   ret_to_user  @返回

 

先看usr_entry

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

    .macro  usr_entry

    sub sp, sp, #S_FRAME_SIZE  @栈指针下移,空出一段空间存放各寄存器

    stmib   sp, {r1 - r12}   @保存r1r12的值

 

    ldmia   r0, {r1 - r3}    @r0指向的地址处的值传给r1r13,即r1=r0,r2=lr,r3=spsr

    add r0, sp, #S_PC      @ here for interlock avoidance @不会描述,看下面的图吧

    mov r4, #-1         @  ""  ""     ""        ""

 

    str r1, [sp]        @ save the "real" r0 copied @r1存放的是实际r0的值,这里就是存储

                   @ from the exception stack      @r0的值

 

#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)

#ifndef CONFIG_MMU

#warning "NPTL on non MMU needs fixing"

#else

    @ make sure our user space atomic helper is aborted

    cmp r2, #TASK_SIZE

    bichs   r3, r3, #PSR_Z_BIT

#endif

#endif

 

    @

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

    @

    @  r2 - lr_, already fixed up for correct return/restart

    @  r3 - spsr_

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

    @

    @ Also, separately save sp_usr and lr_usr

    @

    stmia   r0, {r2 - r4}   @存储返回地址,现场状态等(被中断代码处的)

    stmdb   r0, {sp, lr}^   @存储用户模式下的sp,lr,好像这里的顺序和规范上倒了一下J

 

    @

    @ Enable the alignment trap while in kernel mode

    @

    alignment_trap r0

 

    @

    @ Clear FP to mark the first stack frame

    @

    zero_fp

    .endm

这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执行完后的栈如下所示:

S_FRAME_SIZE, S_PCarch/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_pc     uregs[15]

呵呵,pt_regs中对应的就是上面栈上的18个寄存器,ARM_pcpc寄存器存放在这个数组中的偏移。

 

接着看get_thread_info, 它也是个宏,用来获取当前线程的地址。在我的一篇linux启动代码分析里曾写过线程的定义方式:

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就是通过把sp13位清0(8K边界)来获取当前thread_info对象的地址。

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

    .macro  get_thread_info, rd

    mov \rd, sp, lsr #13

    mov \rd, \rd, lsl #13

    .endm

调用该宏后寄存器tsk里存放的就是当前线程的地址了, tsk是哪个寄存器呢,呵呵我们在看:

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

tsk .req    r9      @ current thread_info

呵呵,tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。


阅读(963) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~