ARM 的 SWI
(Software Interrupt,软件中断)指令是用于实现系统调用 (System Calls)的传统机制。它允许运行在用户模式 (User mode)下的应用程序请求运行在特权模式(如 Supervisor mode)下的操作系统内核提供服务。
现在,SWI
指令在较新的 ARM 架构(ARMv7 及以后)中已被 SVC
(Supervisor Call,监管调用)指令取代,但两者的概念和用法几乎完全相同。SWI
在 ARMv6 及之前的架构中更常见。
概念
- 触发异常 :当 CPU 执行
SWI
/SVC
指令时,它会产生一个异常(Exception),导致处理器模式切换到监管模式(Supervisor Mode)。 - 跳转到异常向量表 :处理器自动跳转到异常向量表 (Exception Vector Table)中预定义的地址(对于
SWI
,通常是0x00000008
或0xFFFF0008
)。 - 执行异常处理程序 :该地址处存放着软件中断处理程序(Software Interrupt Handler)的代码,也就是操作系统内核中处理系统调用的部分。
- 识别和分发 :处理程序会检查
SWI
指令中自带的立即数 (Number),这个数被称为SWI number
或SVC number
。操作系统根据这个号码来查询系统调用表(Syscall Table),确定用户程序请求的是哪个服务(如打开文件、写入数据、创建进程等)。 - 返回用户模式 :服务执行完毕后,处理程序会执行一条特殊的返回指令(如
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
的编码
- 条件字段
Cond
为1110
(AL,表示始终执行)。 - 操作码
1111
。 - 立即数
0x123456
。 - 组合起来就是:
1110 1111 0001 0010 0011 0100 0101 0110b
->0xEF123456
。
在swi_handler中如何获取 SWI 编号
当 SWI
处理程序开始执行时,它需要从触发异常的指令本身提取出这个编号。步骤如下:
- 获取返回地址 :处理器在进入异常时,会将
SWI
指令下一条指令 的地址保存到监管模式的链接寄存器LR_svc
中。 - 计算 SWI 指令地址 :
SWI
指令的地址就是LR_svc - 4
。 - 读取指令 :从内存中
LR_svc - 4
这个地址读出完整的 32 位机器码。 - 提取立即数 :屏蔽 掉高 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
-
SVC 指令 :在 ARMv7 和 AArch32 中,
SWI
被重新命名为SVC
,但其功能和编码方式基本保持不变。你现在更常看到的是SVC #0
。 -
AAPCS :现代系统调用通常遵循更严格的 EABI 或 AAPCS 标准,它规定了哪个系统调用号放在哪个寄存器(通常是
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 ; 触发监管调用
-
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 处理函数等)的地址填写到这张表的对应位置。