如何实现一个系统调用

在 Linux 4.19 内核中实现一个系统调用,主要分为以下四个步骤。以 x86_64 架构为例,假设我们要实现一个名为 sys_mysyscall 的简单系统调用:

  1. 实现系统调用函数
    在内核源码中编写你的功能代码。通常可以放在 kernel 目录下,或者在特定模块中。 使用 SYSCALL_DEFINE 宏来定义函数,这有助于处理安全检查和参数传递。
    文件:sys.c (或新建一个文件)
    /* 定义一个接收 2 个参数的系统调用 */
SYSCALL_DEFINE2(mysyscall, int, a, int, b)
{
    int result = a + b;
    /* printk 会将信息输出到内核日志缓存区,可通过 dmesg 查看 */
    printk("============= [MySyscall] Kernel Hello! a=%d, b=%d =============\n", a, b);
    return result;
}

注:SYSCALL_DEFINEx 中的 x 代表参数个数(0-6)。

  1. 注册系统调用号
    你需要为新的系统调用分配一个唯一的编号。在 x86_64 架构中,这在系统调用表中完成。
    文件:syscall_64.tbl 找到文件末尾,添加一行:
# [系统调用号]   [ABI]    [名称]        [内核函数入口]
548	common mysyscall	__x64_sys_mysyscall

注:548 是顺延的下一个可用编号。

  1. 添加函数原型声明
    在内核头文件中声明该系统调用,以便其他内核组件可以识别。
    文件:syscalls.h 在文件末尾(#endif 之前)添加:
asmlinkage long sys_mysyscall(int a, int b);
  1. 编译并测试
    重新编译内核:执行 make -j$(nproc) 并更新启动镜像。
    编写用户态测试程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
 
#define SYS_MYSYS 548 // 必须与 syscall_64.tbl 中的编号一致
 
int main() {
    long ret = syscall(SYS_MYSYS);
    if (ret == 0) {
        printf("System call executed successfully! Check 'dmesg' for output.\n");
    } else {
        perror("System call failed");
    }
    return 0;
}

关键点总结
架构差异:不同架构(如 ARM, x86)的系统调用表路径不同。
参数传递:内核态与用户态通信时,指针必须使用 __user 修饰,并使用 copy_from_user 或 copy_to_user 进行安全拷贝。
返回值:系统调用通常返回 long 类型,成功返回 0 或正数,失败返回负的错误码(如 -EFAULT)。
你可以通过 dmesg 命令查看内核输出的 Hello from kernel space! 日志来验证是否成功。

posted @ 2026-09-22 15:57  GULIU  阅读(3)  评论(0)    收藏  举报