在Linux内核中添加系统调用

本文在Linux内核6.8.0中加入新系统调用,编译、安装新的Linux内核,测试新的系统调用。
编译Linux内核会产生大量中间文件,建议为Ubuntu系统保留至少50G硬盘空间,否则会超出容量,不能完成编译。

操作系统:Ubuntu 24.04.1 LTS。
在终端输入uname -r查看当前系统版本号,为6.8.0-x-generic。

下载源代码

在系统设置->系统->软件更新->设置中,下载源代码项打勾,确定并更新软件源。

安装dpkg-dev。(下载Ubuntu源代码的命令依赖dpkg-dev)

1
sudo apt install dpkg-dev

在/usr/src目录下执行以下命令,获得当前内核源代码。linux内核源代码目录为linux-6.8.0。

1
sudo apt source linux-image-unsigned-$(uname -r)

安装编译Linux内核所需的工具。

1
2
sudo apt build-dep linux linux-image-unsigned-$(uname -r)
sudo apt install libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf llvm

注册新系统调用

查看arch/x86/entry/syscalls/syscall_64.tbl文件,可以看到注释要求在所有使用common二进制接口的系统调用后添加新系统调用,因此在461号后添加以下内容,从而注册系统调用处理函数sys_my_syscall。

1
462     common  my_syscall            sys_my_syscall

定义系统调用处理函数

在kernel/sys.c的第961行后加入下列代码,定义sys_my_call()为返回进程号的函数,相当于sys_getpid()。

1
2
3
4
SYSCALL_DEFINE0(my_syscall)
{
return task_tgid_vnr(current);
}

如果在新增的C语言文件定义新系统调用处理函数,则需要修改相应文件夹中Makefile的obj-y,并在include/linux/syscalls.h中声明新函数。

编译和安装内核

  • 在Linux内核源代码目录/usr/src/linux-6.8.0下,执行以下命令,从当前启动目录拷贝配置信息,意思是编译内核的配置与当前环境一致。

    1
    sudo cp -v /boot/config-$(uname -r) .config
  • 编辑.config,把CONFIG_SYSTEM_TRUSTED_KEYS和CONFIG_SYSTEM_REVOCATION_KEYS改为空字符串。或使用以下命令。

    1
    2
    sudo scripts/config --disable SYSTEM_TRUSTED_KEYS
    sudo scripts/config --disable SYSTEM_REVOCATION_KEYS
  • 执行以下命令启动配置界面

    1
    2
    sudo make menuconfig  # 使用当前配置
    # sudo make defconfig # 使用默认配置

出现蓝色背景的kernel configuration界面,说明配置成功。save后exit即可。

  • 运行make命令,编译Linux内核。需要较长时间。

    1
    sudo make -j$(nproc)  # 按照核心数量多核编译
  • 运行make modules_install命令安装内核模块。就是把编译好的模块拷贝到/lib/modules/$(uname -r)目录下。

  • 运行make install命令安装新内核。

    1
    2
    sudo make modules_install # 安装内核模块
    sudo make install # 安装新内核
  • 完成内核编译后,重启系统,自动进入新内核。
    uname -r或uname -a查看内核版本,显示为6.8.x(而不是上面看到的6.8.0-x-generic),说明成功安装新内核。

测试程序

下列C代码中,调用syscall函数传递系统调用号,获得新系统调用处理函数的返回值,即当前进程号。相当于调用<unistd.h>中的getpid()。
在主函数main()里打印syscall()和getpid()的返回值,如果两个值相等,打印“Successfully added my system call.”(成功加入我的系统调用。)

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
#define __NR_my_syscall 462

int main() {
int mycall = syscall(__NR_my_syscall);
int pid = getpid();
printf("sys_my_syscall() returns %d.\n", mycall);
printf("getpid() returns %d.\n", pid);
if (mycall == pid) printf("Successfully added my system call.\n");
return 0;
}

用gcc编译并运行。

1
gcc test.c && ./a.out

参考文献

  1. AimTao. Linux 内核|系统调用[EB/OL]. aimtao.net, (2022-04-12)[2024-12-15]. https://www.aimtao.net/system-call/
  2. Adding a system call to Linux CS 273 (OS), Spring 2022[EB/OL]. Stolaf.edu, (2022-04-29)[2024-12-15]. https://www.stolaf.edu/people/rab/os/newsyscall.html
  3. Arnold Lu. Ubuntu 22.04内核代码下载、编译、调试[EB/OL]. 博客园, (2024-03-10)[2024-12-15]. https://www.cnblogs.com/arnoldlu/p/18064348
  4. robotech_erx. 在Ubuntu上编译安装linux内核详细过程[EB/OL]. 博客园, (2024-04-16)[2024-12-15]. https://www.cnblogs.com/robotech/p/16152269.html