arm的 swi简述

ARM 的 SWI(Software Interrupt,软件中断)指令是用于实现系统调用 (System Calls)的传统机制。它允许运行在用户模式 (User mode)下的应用程序请求运行在特权模式(如 Supervisor mode)下的操作系统内核提供服务。

现在,SWI 指令在较新的 ARM 架构(ARMv7 及以后)中已被 SVC(Supervisor Call,监管调用)指令取代,但两者的概念和用法几乎完全相同。SWI 在 ARMv6 及之前的架构中更常见。


概念

  1. 触发异常 :当 CPU 执行 SWI / SVC 指令时,它会产生一个异常(Exception),导致处理器模式切换到监管模式(Supervisor Mode)。
  2. 跳转到异常向量表 :处理器自动跳转到异常向量表 (Exception Vector Table)中预定义的地址(对于 SWI,通常是 0x000000080xFFFF0008)。
  3. 执行异常处理程序 :该地址处存放着软件中断处理程序(Software Interrupt Handler)的代码,也就是操作系统内核中处理系统调用的部分。
  4. 识别和分发 :处理程序会检查 SWI 指令中自带的立即数 (Number),这个数被称为 SWI numberSVC number。操作系统根据这个号码来查询系统调用表(Syscall Table),确定用户程序请求的是哪个服务(如打开文件、写入数据、创建进程等)。
  5. 返回用户模式 :服务执行完毕后,处理程序会执行一条特殊的返回指令(如 MOVS PC, LR),将处理器模式恢复回用户模式,并返回到用户程序中 SWI 指令的下一条指令继续执行。

语法和编码

1. 汇编语法 (ARM/Thumb)
c 复制代码
SWI #<immed_24>    ; ARM 指令集格式 (32位指令)
SVC #<immed_8>     ; Thumb 指令集格式 (16位指令)
  • <immed_24><immed_8> 是一个数字 ,用于标识具体的系统调用功能。
    • 例如,SWI #0 可能用于写入数据,SWI #1 可能用于读取数据。
2. 机器码格式

SWI 指令的ARM 32位编码格式如下:

31 - 28 27 - 24 23 - 0
Cond 1111 Imm24 (注释)
  • Cond:条件执行字段(如 EQ, NE)。
  • 1111 :这是 SWI 指令的操作码。
  • Imm24:一个 24 位的立即数,这就是传递给操作系统的功能号。

示例:指令 SWI 0x123456 的编码

  • 条件字段 Cond1110(AL,表示始终执行)。
  • 操作码 1111
  • 立即数 0x123456
  • 组合起来就是:1110 1111 0001 0010 0011 0100 0101 0110b -> 0xEF123456

在swi_handler中如何获取 SWI 编号

SWI 处理程序开始执行时,它需要从触发异常的指令本身提取出这个编号。步骤如下:

  1. 获取返回地址 :处理器在进入异常时,会将 SWI 指令下一条指令 的地址保存到监管模式的链接寄存器 LR_svc 中。
  2. 计算 SWI 指令地址SWI 指令的地址就是 LR_svc - 4
  3. 读取指令 :从内存中 LR_svc - 4 这个地址读出完整的 32 位机器码。
  4. 提取立即数屏蔽 掉高 8 位(条件码和操作码),保留低 24 位,就得到了 SWI 编号。

示例 C 伪代码:

c 复制代码
// 在 SWI 异常处理函数中
void SWI_Handler(void) {
    // 1. 获取返回地址 (LR minus 4 for SWI in ARM state)
    uint32_t *lr_svc = get_special_register(LR_svc);
    uint32_t swi_instruction_address = lr_svc - 4;

    // 2. 从该地址读取指令
    uint32_t swi_instruction = *((uint32_t *)swi_instruction_address);

    // 3. 提取 24 位立即数 (屏蔽高8位)
    uint32_t swi_number = swi_instruction & 0x00FFFFFF;

    // 4. 根据 swi_number 调用相应的服务函数
    switch(swi_number) {
        case 0: syscall_write(); break;
        case 1: syscall_read(); break;
        // ... 其他系统调用
        default: handle_unknown_syscall(); break;
    }
}

从用户模式发起系统调用的示例

下面是一个简单的汇编示例,展示用户程序如何通过 SWI 请求服务。

c 复制代码
; 假设系统调用编号 0x1 是 "写入字符串"
; 我们需要在寄存器中设置好参数,通常遵循 ATPCS(ARM-Thumb Procedure Call Standard)
MOV R0, #1          ; 参数1: 文件描述符 (1 = 标准输出 stdout)
LDR R1, =my_message ; 参数2: 要写入的字符串地址
MOV R2, #12         ; 参数3: 字符串长度

SWI #0x1            ; 发起软件中断,调用编号为 1 的服务

; 程序继续执行...

my_message:
    .asciz "Hello World\n"

对应的内核处理程序会根据 swi_number=0x1 找到 sys_write 函数,并从寄存器 R0, R1, R2 中读取参数。


现代发展:SVC 与 ABI

  1. SVC 指令 :在 ARMv7 和 AArch32 中,SWI 被重新命名为 SVC,但其功能和编码方式基本保持不变。你现在更常看到的是 SVC #0

  2. AAPCS :现代系统调用通常遵循更严格的 EABIAAPCS 标准,它规定了哪个系统调用号放在哪个寄存器(通常是 R7),以及参数如何传递(R0-R6)。

    armasm 复制代码
    ; 现代 Linux EABI 系统调用示例 (系统调用号在 R7)
    MOV R7, #4       ; 系统调用号 4 = sys_write
    MOV R0, #1       ; fd = stdout
    LDR R1, =message
    MOV R2, #len
    SVC #0           ; 触发监管调用
  3. AArch64 :在 64 位的 ARM 架构中,使用 SVC #0 指令,但寄存器约定完全不同(系统调用号放在 X8,参数放在 X0-X5)。

总结

方面 说明
目的 实现用户模式到特权模式的切换,用于系统调用。
机制 触发一个预定义的异常,使 CPU 跳转到内核的异常处理程序。
关键点 指令中嵌入的立即数用于标识所需的服务。
参数传递 通过通用寄存器 (如 R0-R3)在用户程序和内核之间传递参数和返回值。
现代名称 SVC(Supervisor Call)。
重要性 是应用程序与操作系统内核交互的根本基础

理解 SWI/SVC 是理解操作系统底层工作原理和 ARM 异常处理机制的关键一步。

SWI/SVC 异常处理函数绝对编译和运行在操作系统内核中

应用程序只是触发这个机制,而处理这个机制的所有"幕后工作"都由内核全权负责。

为什么?------ 权限与保护

这完全是由 ARM 处理器的特权级别(Privilege Levels)和内存保护机制决定的:

执行模式的切换

应用程序运行在 用户模式(User Mode),这是一种受限模式。

当执行 SWI 指令时,硬件会自动将处理器切换到 监管模式(Supervisor Mode),这是一种特权模式。

内存访问权限

用户模式下的程序无法访问内核的内存空间(代码和数据)。这是通过内存管理单元(MMU)和页表(Page Tables)来强制实现的。

异常向量表(Exception Vector Table)的地址、内核的代码和数据都位于受保护的内核地址空间。用户程序试图读写这些地址会引发一个硬件错误(如 Segmentation Fault)。

因此,即使用户程序自己写了一个名为 SWI_Handler 的函数,它也根本无法被硬件访问到,更无法在特权模式下执行。

硬件设计流程

硬件行为:CPU 一遇到 SWI 指令,其硬件逻辑就已经固定死了下一步要做什么:改变模式、保存状态、跳转到预先由内核设置好的异常向量表地址(如 0x00000008 或 0xFFFF0008)。

内核的职责:操作系统内核在启动初期,其最重要的任务之一就是初始化异常向量表,即将所有异常处理函数(包括 SWI 处理函数、IRQ 处理函数等)的地址填写到这张表的对应位置。