Featured image of post Linux 内核编译及添加系统调用

Linux 内核编译及添加系统调用

改进一下老古董课程实验

前言

这学期上了一门挺感兴趣的操作系统课,课程实践内容就是标题。但问题来了,教程里面给的是华为云和openEuler发行版的教程,这让我很不理解。毋庸置疑这项任务是非常有学习价值的,但这种冷门发行版很多操作都和主流发行版不同,与其说是学习,不如说是照本宣科完成一项死板的任务。而之后真正需要用到的时候,又要重新学习一遍其他发行版的流程,无形中增加了学习成本。不过毕竟大学的教育就是这样,不合理的教学已经司空见惯了

好在这次实验的核心其实和发行版关系不大,重点在于内核编译流程和系统调用的原理,只要保证方法正确,换环境不会影响结果。为了不让自己失去动力,从而随便糊弄过去,错失了宝贵的课堂学习机会,我就直接在 Ubuntu 上做了一遍

环境

我使用的是Ubuntu 24.04.4 LTS 64位 的虚拟机

  • CPU:4 核
  • 内存:8GB
  • 硬盘:50GB
  • 网络:NAT模式

img

实验开始前首先创建一个快照,这样哪怕系统崩了也可以随时恢复

安装工具,构建开发环境

1
2
sudo apt update
sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev bc dwarves zstd

为了防止后续步骤更新内核失败导致无法引导,备份/boot目录并保存内核版本信息

1
2
sudo tar czvf /root/boot.origin.tgz /boot/
uname -r > ~/uname_r.log

内核编译

获取源码

从Linux内核官方归档网站下载源码。我下载的是 5.15.148版本

1
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.148.tar.xz

解压源码

1
tar -xvf linux-5.15.148.tar.xz

配置内核

为了确保新内核的配置与现在的系统兼容,最好的方法是拷贝现在内核的配置

拷贝当前系统的配置文件

1
2
cd linux-5.15.148
cp /boot/config-$(uname -r) .config

生成新内核的配置文件,保留当前内核的配置,新增配置全部默认设置

1
make olddefconfig

修改证书

在Ubuntu中,上一步拷贝来的配置文件默认开启了内核签名校验,配置中引用了 Ubuntu 的证书文件路径,但源码里没有这些 .pem 文件。如果不修改,编译很可能报错

为了避免编译失败,需要使用 scripts/config 脚本修改 .config

1
2
3
4
5
6
7
# 关闭验证系统受信任的密钥列表
scripts/config --disable SYSTEM_TRUSTED_KEYS
# 禁用吊销密钥列表检查
scripts/config --disable SYSTEM_REVOCATION_KEYS
# 将密钥路径强制设置为空
scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS ""

确认修改,这里如果看到如图的输出,说明修改成功

1
grep "CONFIG_SYSTEM_TRUSTED_KEYS" .config

编译

查看可编译项

1
make help | grep bzImage

可以看到bzImage - Compressed kernel image (arch/x86/boot/bzImage),这就是编译生成的内核镜像

执行编译,这里 $(nproc) 是自动获取的 CPU 核数,这样可以同时运行多个编译任务,加快编译速度

执行命令进行编译前务必确认剩余存储大于20G,否则很可能由于存储不足导致编译失败,可以执行 df -h 查看 / 分区

1
make -j$(nproc) bzImage modules
  • bzImage:内核名,计算机将大量.c源文件编译成二进制.o文件,最后输出一个压缩镜像文件 bzImage,位于 arch/x86/boot/bzImage
  • modules:驱动插件,如果将所有驱动都编译进内核会很臃肿,所以需要这个独立的插件。它编译为 .ko 文件(Kernel Object),平时存储在硬盘,只有插入对应硬件时,内核才会加载对应的驱动到内存

内核编译时间比较长,一般需要几十分钟甚至几小时

一段时间后可以看到编译完成

安装

编译完成后执行下面的命令,安装模块和内核镜像

Ubuntu的 make install 会自动调用 update-grub,执行完之后会自动把内核复制到 /boot,并更新 grub 引导,不需要手动配置

1
2
sudo make modules_install
sudo make install

完成后重启系统

1
sudo reboot

在GRUB引导菜单选择新内核启动,登录系统后验证版本

1
uname -r

遇到问题:重启后没有进入GRUB菜单,无法选择启动项,还是进入了原来的内核

检查GRUB配置文件

1
sudo nano /etc/default/grub

可以看到GRUB菜单是隐藏的

设置开机时显示10秒GRUB菜单:

  • GRUB_TIMEOUT_STYLE=menu
  • GRUB_TIMEOUT=10

更新配置使其生效

1
sudo update-grub

输出中有这一行Found linux image: /boot/vmlinuz-5.15.148,说明内核确实安装成功了,只是刚刚默认进入了原来的内核

再次重启,在 GRUB 界面选择Advanced options for Ubuntu

进入后,可以看到刚刚编译的内核Ubuntu, with Linux 5.15.148,选中并回车,进入系统后重新输入命令进行验证

这里的输出是源码的版本号5.15.148,说明内核编译成功

添加 Linux 的系统调用

用户程序的权限不足,无法直接访问内核,系统调用就是用户程序以syscall作为接口,通过调用表的方式来间接调用内核执行功能

之后的操作均在linux源码所在目录中执行,进入源码目录

1
cd linux-5.15.148

分配系统调用号

这一步是为系统调用在调用表中分配一个唯一编号,相当于一个指针

编辑 x86_64 的系统调用表文件

1
nano arch/x86/entry/syscalls/syscall_64.tbl

文件中有这一行don't use numbers 387 through 423, add new calls after the last'common' entry。在文件中找到common调用的末尾,可以看到截止到448号。我使用未被占用的450作为新的系统调用号

1
450     common  my_syscall              sys_my_syscall

声明系统调用

这一步相当于在头文件启用函数的调用

编辑系统调用头文件

1
nano include/linux/syscalls.h

在文件末尾 #endif 之前,添加系统调用函数声明

1
asmlinkage long sys_my_syscall(void);

实现系统调用

sys.c 文件末尾添加系统调用的代码

sys.c 是内核存放系统调用的文件,在末尾添加可以避免影响现有代码

1
nano kernel/sys.c

代码如下

1
2
3
4
5
SYSCALL_DEFINE0(my_syscall)
{
    printk(KERN_INFO "Student ID: XXXXXXX\n");
    return 0;
}
  • SYSCALL_DEFINE0 表示无参数
  • printk 相当于内核里的 printf
  • 输出在内核日志中,并不会在终端直接显示

重新编译内核

只是修改源码并不能在当前内核直接生效,重新编译安装才是真正将前面的修改添加进内核

1
2
3
4
make -j$(nproc) bzImage modules
sudo make modules_install
sudo make install
sudo reboot

重启后进入刚刚编译好的内核,GRUB中可以看到有两个相同版本号的内核,其中一个有.old后缀,这个就是修改源码前第一次编译的内核,选择没有后缀的内核进入

进行测试

创建一个测试程序 syscall_t.c

1
nano syscall_t.c

代码如下:

1
2
3
4
5
6
7
#include <unistd.h>
#include <sys/syscall.h>

int main() {
    syscall(450);
    return 0;
}

编译运行:

1
2
gcc syscall_t.c -o syscall_t
./syscall_t

查看内核日志

1
sudo dmesg | grep Student

grep后面应该写想要搜索的日志或输出中的关键词,而不是程序名。这一步需要加sudo,是因为普通用户默认不可访问内核日志

看到刚才 printk 的内容,就说明调用成功

现象:进入这个内核以后,图形界面一直特别卡,操作延迟在一秒左右,查看系统监视器,发现CPU的四个核心轮流占用百分百,某一个核心运行的时候其他的几乎不工作

可能原因:内核没有加载显卡驱动,桌面环境的所有图形计算都使用 CPU。而图形渲染是单线程,从而导致 CPU 满载。Linux 调度器会在不同 CPU 核之间迁移这个线程,也就是多个核心轮流100%占用

这是缺少图形驱动导致的性能问题,不是内核编译错误

实验成功后,在源码目录执行 make clean 清除编译文件

附:Ubuntu 和 openEuler 的差异

对比项 openEuler (ARM64) Ubuntu (x86_64)
包管理器 yum / dnf (RPM系) apt (Debian系)
架构特定目录 arch/arm64/ arch/x86/
内核镜像目标 Image bzImage
设备树 (DTB) 需要编译 dtbs x86平台不需要编译 dtbs
系统调用表位置 include/uapi/asm-generic/unistd.h arch/x86/entry/syscalls/syscall_64.tbl
调用约定 依赖ARM64特定的调用约定和寄存器 依赖 x86_64 调用约定与 SYSCALL_DEFINE
Like 0
本站已不稳定运行 小时 分钟
共发表文章 29 篇 ,总计 126.96 k 字
本站总访问量:
使用 Hugo 构建
主题 StackJimmy 设计