ARM架构下的Linux系统调用分析与自定义系统调用实现方法

一、系统调用的初始化

系统调用通过软件中断(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)

  1. 用户态调用

    • 系统调用号存入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)
    );
    
  2. 内核态处理

    • SWI中断触发后,CPU跳转到向量表0号位置,执行asm_do_syscall
    • 内核校验系统调用号,从sys_call_table[r7]获取函数地址并调用。
    • 返回值存入r0,通过restore_user_registers恢复用户态上下文,执行subs pc, lr, #4返回用户态。

四、自定义系统调用示例(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. 内核编译与验证

  1. 配置内核
    arch/arm/configs/中选择合适的内核配置,确保启用CONFIG_ARCH_ARMCONFIG_SYSVIPC等基础功能。
  2. 编译安装
    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
    
  3. 验证
    交叉编译用户态程序并在ARM设备上运行,输出Result: 30即表示成功。

参考资料

  • 《庖丁解牛Linux操作系统分析》:https://gitee.com/mengning997/linuxkernel
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值