一、系统调用的初始化
系统调用通过软件中断(SWI)实现,内核通过初始化中断向量表(IVT)建立用户态SWI指令与内核态处理函数的映射关系。
1. 中断向量初始化
ARM架构的中断向量表通常位于arch/arm/kernel/entry-armv.S,系统调用通过SWI(Software Interrupt)指令触发,其向量号固定为0号中断。内核在启动时通过trap_init()注册SWI处理函数:
// 伪代码示意,实际在汇编中实现
set_swi_handler(&asm_do_syscall); // 设置SWI中断处理入口
2. SWI指令机制
- 用户态触发:通过
swi #<imm>指令触发中断,其中imm为可选立即数(ARM32中通常不使用,系统调用号通过寄存器传递)。 - 特权级切换:SWI触发时,CPU从用户态(USR模式)切换到特权模式(SVC模式),并自动保存当前状态到SVC模式堆栈。
二、系统调用回调函数
1. 中断处理入口(asm_do_syscall)
ARM的系统调用入口为汇编实现的asm_do_syscall(位于arch/arm/kernel/entry-armv.S),主要流程如下:
asm_do_syscall:
swi_svc_entry:
save_user_registers ; 保存用户态寄存器(r0-r12, lr等)
get_thread_info r7 ; 获取当前进程thread_info
ldr r2, [r7, #TI_FLAGS]
tst r2, #_TIF_SYSCALL_WORK
bne syscall_work_entry ; 处理调试/审计等场景
mov r8, r7 ; 保存thread_info到r8
swi_check_syscall:
mov r7, r0 ; 系统调用号存于r7(用户态通过r7传递)
cmp r7, #NR_syscalls
blo syscall_ok
mov r0, #-ENOSYS ; 越界返回错误
b syscall_exit
syscall_ok:
ldr pc, [pc, r7, lsl #2] ; 查表调用sys_call_table[r7]
2. sys_call_table
系统调用表定义于arch/arm/kernel/sys_call_table.S,为函数指针数组,按系统调用号索引:
/* 示例片段,对应ARM32系统调用表 */
SYSCALL(0, sys_restart_syscall)
SYSCALL(1, sys_exit)
SYSCALL(2, sys_fork)
SYSCALL(4, sys_read)
SYSCALL(5, sys_write)
- 索引方式:直接通过系统调用号(存于r7)作为数组下标,无需字节偏移(每个函数指针占4字节,与ARM指令长度一致)。
3. 示例:sys_write()
asmlinkage ssize_t sys_write(unsigned int fd, const char __user *buf, size_t count) {
// 参数通过r0/r1/r2传递(ARM前三个参数用r0-r2)
// 需通过copy_from_user()从用户空间读取数据
char kbuf[PAGE_SIZE];
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
// 实际写入逻辑...
return count;
}
三、系统调用过程(ARM32)
-
用户态调用:
- 系统调用号存入r7,参数存入r0-r6(最多6个参数)。
- 执行
swi #0触发中断(#0为SWI指令的保留立即数,无实际意义)。
// 用户态C代码示例(sys_write调用) int fd = open("test.txt", O_WRONLY | O_CREAT, 0644); const char *msg = "hello ARM"; asm volatile ( "mov r7, %0\n" // r7 = __NR_write (系统调用号,如64) "mov r0, %1\n" // r0 = fd "mov r1, %2\n" // r1 = msg地址 "mov r2, %3\n" // r2 = strlen(msg) "swi #0\n" // 触发SWI中断 : "=r"(result) // 输出:r0保存返回值 : "r"(fd), "r"(msg), "r"(len) ); -
内核态处理:
- SWI中断触发后,CPU跳转到向量表0号位置,执行
asm_do_syscall。 - 内核校验系统调用号,从
sys_call_table[r7]获取函数地址并调用。 - 返回值存入r0,通过
restore_user_registers恢复用户态上下文,执行subs pc, lr, #4返回用户态。
- SWI中断触发后,CPU跳转到向量表0号位置,执行
四、自定义系统调用示例(ARM32)
以下以新增加法系统调用sys_my_add为例,演示ARM架构下的实现流程。
1. 内核态实现
(1)定义系统调用号
在/usr/include/arm-linux-gnueabihf/unistd.h中添加:
#define __NR_my_add 384 // 假设当前最大系统调用号为383,新增号384
#define NR_syscalls 385 // 更新系统调用总数
(2)修改系统调用表
在arch/arm/kernel/sys_call_table.S中追加:
SYSCALL(384, sys_my_add) // 对应系统调用号384
(3)实现系统调用函数
在kernel/sys.c中编写:
asmlinkage long sys_my_add(int a, int b) {
// 参数通过r0(a)和r1(b)传递
return a + b;
}
2. 用户态调用方法
(1)通过汇编直接调用
.global _start
.section .text
_start:
mov r0, #10 ; 参数a存入r0
mov r1, #20 ; 参数b存入r1
mov r7, #__NR_my_add ; 系统调用号存入r7
swi #0 ; 触发SWI中断(r0保存结果)
// 输出结果
ldr r1, =format
mov r2, r0
bl printf
// 退出程序
mov r7, #__NR_exit
mov r0, #0
swi #0
.section .data
format: .asciz "Result: %d\n"
(2)通过C库函数包装
#include <unistd.h>
#include <stdio.h>
// 定义系统调用号(若未包含在头文件中)
#define __NR_my_add 384
// 使用syscall函数调用(需包含<sys/syscall.h>)
long my_add(int a, int b) {
return syscall(__NR_my_add, a, b);
}
int main() {
printf("Result: %ld\n", my_add(10, 20)); // 输出:30
return 0;
}
3. 内核编译与验证
- 配置内核:
在arch/arm/configs/中选择合适的内核配置,确保启用CONFIG_ARCH_ARM和CONFIG_SYSVIPC等基础功能。 - 编译安装:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install - 验证:
交叉编译用户态程序并在ARM设备上运行,输出Result: 30即表示成功。
参考资料
- 《庖丁解牛Linux操作系统分析》:https://gitee.com/mengning997/linuxkernel
668

被折叠的 条评论
为什么被折叠?



