ARM中swi(软中断)

swi 是 arm 的软件中断指令,大概是 software interrupt 的意思 执行完swi指令后,cpu会做几件事情:

  1. 将swi的下一条指令地址保存到 r14_svc 中
  2. 将当前 cpsr 保存到 spsr_svc 中
  3. 将cpu模式改为特权模式svc_mode, 即更改 cpsr 的低五位, cspr[4:0]=0b10011
  4. 切换到ARM状态, cspr[5]=0
  5. 禁止IRQ, cspr[7]=1
  6. 将 pc 置为 0x00000008, 即跳到中断向量的地方开始执行

其于CPU的这种行为,我们需要考虑几件事情:


  • cpu最后跳到0x00000008地址执行,而且该地址处只有4个字节可以使用。 
    这4个字节正好可以放置一条指令。那么显然这个指令要将pc跳到另一个地方。


如何实现呢?

首先考虑当前的环境和需求。

在我的这块开发板上,SRAM的地址空间为 0x30000000 ~ 0x34000000 所以最开始要将测试程序放到 0x30000000 开始的地方。当程序从 0x30000000 地址跑 起来之后,我们再去设置中断向量表,即在在代码中去设置 0x00000000 ~ 0x0000001c 这块地址空间的指令。

于是想到可以如下来实现:

- .global _start
- _start:
-     mov r8, #0
-     adr r9, vector_init_block
-     ldmia {r0-r7}
-     stmia {r0-r7}

- vector_init_block:
-     b    reset_addr
-     b    undefined_addr
-     b    swi_addr
-     b    prefetch_addr
-     b    abort_addr
-     b    notused_addr
-     b    irq_addr
-     b    fiq_addr

程序从 _start 开始执行,第一件事情即将 vector_init_block 开始的8条指令复制到 0x00000000 开始的地方,每条指令占4字节,共 8*4=32字节。

假想现在程序从 0x30000000 开始执行,之后调用 swi 发生了软件中断,pc开始跳到 0x00000008 开始执行,这个位置的指令是 b swi_addr , 然后再跳到swi_addr去继续 执行。

这里有一个问题,跳转指令 b 只能在 32M 空间范围内跳转。 而 swi_addr 肯定是在 0x30000000 之后的,所以这里会跳转失败。

要想在32位(4G)地址空间实现跳转,可以使用 ldr 指令。 于是可以如下实现
- .global _start
- _start:
-     mov r8, #0
-     adr r9, vector_init_block
-     ldmia {r0-r7}
-     stmia {r0-r7}
-     ldmia {r0-r7}
-     stmia {r0-r7}

- vector_init_block:
-     ldr pc, reset_addr
-     ldr pc, undefined_addr
-     ldr pc, swi_addr
-     ldr pc, prefetch_addr
-     ldr pc, abort_addr
-     ldr pc, notused_addr
-     ldr pc, irq_addr
-     ldr pc, fiq_addr

- reset_addr:     .word reset_handler
- undefined_addr: .word undefined_handler
- swi_addr:     .word swi_handler
- prefetch_addr:     .word prefetch_handler
- abort_addr:     .word abort_handler
- notused_addr:    .word 0
- irq_addr:     .word irq_handler
- fiq_addr:     .word fiq_handler

ldr 指令是将某个地址里的值读到寄存器中,而紧接着我们将几个中断处理函数 的地址放到内存中供 ldr 读取。

reset_addr .word reset_handler 就是很典型的指针用法, 将处理函数 reset_handler 的地址放到内存中存起来。

与上面不同的时,这里 _start 中不仅复制了指令,而且复制了后面的‘指针’



  • 上面构造好中断向量表之后,swi 就可以正确跳到 swi_handler 地址处开始执行了。 
    那么在 swi_handler 处理函数中,需要做些什么事情呢?


swi处理通常分为两级:

  1. 汇编实现,保存现场,计算swi中的24位立即数
  2. 可以用c实现,具体实现swi各个功能

细分如下:

  1. 保存环境,将要用到的寄存器和返回地址保存到栈中,供退出时恢复
  2. 计算中断号,swi指令中包括一个24位的立即数,用于指定特定功能
  3. 进入swi处理程序。这部分可以用c实现
  4. 中断返回,恢复寄存器
- swi_handler:
-     stmfd     {r0-r12, lr}

-     ldr    r0, [lr, #-4]
-     bic    r0, r0, #0xff000000
-     bl    c_swi_handler

-     ldmfd     {r0-r12, pc}^

看到 swi 是怎么返回的了么! ldmfd sp!, {r0-r12, pc}^ ^ 表示恢复 spsr_svc -> cpsr



  • c语言部分。 首先要注意的是上面 bl c_swi_handler 之前计算好了 r0 的值,跳到 
    c_swi_handler时,r0将作为第一个实参传给c_swi_handler, 所以在c_swi_handler中 
    可以得到中断号

- void c_swi_handler(unsigned int nr)
- {
-     /* print swi exception number */
-     puts("c_swi_handler\t");
-     put_dex(nr);
-
-     return;
- }

  • 如何触发 swi 中断呢? 这好像不是个问题,直接使用 swi 不就可以了么。 
    那在c语言里呢? 嵌入汇编!

- static inline void swi(void)
- {
-     __asm__ __volatile__ (
-         "swi 0x1\n\t"
-     );
- }

swi 带的参数 0x1 即为中断号

附件是本文例子的完整代码,运行于2410上,其中通过串口打印来方便看到测试结果

讲了这么多,swi倒底可以用来做什么呢? 通过上面的测试,对swi运行流程有了一个大概的了解。之后第一映像就是swi可以用来实 现系统调用。 如同在x86下linux中使用的  int 0x80

你可能感兴趣的:(ARM)