由于本学期开设了操作系统课程, 需要我们编译linux内核, 以及添加系统调用. 但是, 老师给的教程是基于2.x版本的内核, 在GCC版本较高时, 编译内核基本是没法进行的.

同时, 由于使用的宿主系统如果直接更换内核, 可能会对用户造成困扰, 因此, 本教程中的所有操作均不需要对原系统有所修改, 系统调用的测试均放在qemu这个虚拟机中.

如果大家对于Linux不熟悉, 新建和编辑文件可以使用gedit编辑器

下面列出了主机的几个比较重要的环境

  • 将要编译的内核版本 4.10.3(2017-03-15稳定版)
  • 宿主Linux版本 4.10.2-1-ARCH
  • 编译器版本 gcc 6.3.1
  • qemu版本 2.8.0
  • busybox版本 1.26

本教程的使用时间是有限制的, 如果未来Linux内核结构与此不同, 请勿进行使用, 或者联系笔者将此教程撤下, 以免误人子弟

Linux内核编译

内核下载

download

  • 下载完成后解压
1
2
3
4
xz -d linux-4.10.3.tar.xz
tar -xf linux-4.10.3.tar

cd linux-4.10.3 # 文章后面的内核目录就是指该目录

show

  • 编译及启动

若你使用的是Ubuntu, 那么编译过程中出现的所有问题可以直接百度解决

1
2
make menuconfig         # 默认就好, 精简可以加快编译速度
make -j4 # 直接make会很慢的, 默认只使用1个核

笔者大概用了15分钟 (CPU: i5-4200M)

使用qemu启动我们编译好的内核

我们刚刚编译完成的只是一个linux内核, 它具有很强大的功能, 但是只有内核是无法称之为系统的, 这里, 我们使用busybox制作根文件系统, 为了方便大家, 我在这里直接提供一份我已经制作好的系统

从这里下载world.img

  • qemu简单使用(此时我们在内核目录中)
1
2
3
4
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage \
-m 256 \
-hda ../world.img \
-append "root=/dev/sda rdinit=sbin/init noapic"
  • 在此解释下使用的参数
1
2
3
4
-kernel arch/x86_64/boot/bzImage                # 指定内核
-m 256 # 指定内存256M
-hda ../world.img # 使用该文件作为hda硬盘
-append "root=/dev/sda rdinit=sbin/init noapic" # 内核启动参数, 定义根文件系统

启动成功之后如图所示, 简易的linux系统就这样搭建完成了.

qemu

添加系统调用

定义新的系统调用

  • 确保你在内核目录
1
2
mkdir $KERNEL/hello
cd $KERNEL/hello
  • 自行新建文件hello.c, hello.c内容如下图所示(cat查看文件内容)

version_changed: 2019-08-30 感谢一位学弟指正, 在hello.c中定义sys_hello是有问题的, 而应该使用SYSCALL_DEFINE的形式, 这个问题与CVE-2009-0029相关, 我并没有深入了解. 不过, 自己定义的系统调用最好也和其他内核的系统调用保持一致.

请把下面代码中的asmlinkage long sys_hello(int arg0)修改为SYSCALL_DEFINE1(hello, arg0)

hello

添加到系统调用表中

  • 修改syscall_64.tbl

在4.10.3中, 该文件在arch/x86/entry/syscalls/syscall_64.tbl

此时我们在文件末尾添加我们自己的系统调用

syscall.tbl

  • 修改sysclass.h

在4.10.3中, 该文件在include/linux/syscalls.h

也是在末尾添加我们的系统调用

syscall.h

添加到内核编译的Makefile

  • hello目录下新建Makefile, Makefile内容如下

makefile

  • 修改全局主Makefile

全局Makefile

全局主Makefile就是内核目录下的Makefile, 大概是在900多行, 有这样一句话

1
core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/

现在我们将其改为如下, 也就是添加了hello文件夹

1
core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ hello/

重新编译内核

做完以上的工作就可以重新编译内核了, 可以直接make -j4, 不需要make menuconfig. 本来由于上一次的编译有中间文件, 此次会很快. 可惜添加了系统调用之后基本整个内核都要重新编译了, 希望大家有耐心

检验我们新添加了系统调用

使用系统调用的程序, 首先我们要将程序加入到根文件夹中, 才可以通过qemu虚拟机进行调用执行. 因此, 我们首先将根文件系统挂载在本地目录, 然后将编译好的测试程序添加到根文件系统中, 也就是拷贝到该目录中

在宿主机上挂载world.img文件

1
2
mkdir world_mount                 # 新建文件夹, 稍后会将程序拷贝到此文件夹
sudo mount world.img world_mount # 挂载文件到文件夹中

如图所示

kernel_build

编写程序实现系统调用

  • 系统调用程序

刚才我们添加的系统调用号为551, 并且接受一个int参数, 所以, 此处函数syscall(551, 24), 若有疑问可以查阅之前的hello.c文件 相对比之下更容易理解, call.c文件如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"
#include "unistd.h"
#include "linux/kernel.h"
#include "sys/syscall.h"
#include "errno.h"

int main(int argc, char *argv[])
{
int ret = syscall(551, 24);
if (ret != -1) {
printf("The systemcall %d has been used\n", ret);
} else {
printf("Error occured %d\n", errno);
}

return 0;
}
  • 保存后可以直接编译
1
gcc -o call call.c -static
  • 拷贝程序到根文件系统中
1
sudo cp call world_mount/
  • 使用qemu启动系统
1
2
3
4
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage \
-m 256 \
-hda ../world.img \
-append "root=/dev/sda rdinit=sbin/init noapic"

这时, 启动后的系统如下图所示, 同时, 使用./call运行程序, 由于我们的系统调用中使用了printk函数, 会由内核输出信息, 可以通过dmesg进行查看, 这时, 可以看到系统打印出来了内容

qemu2

  • 最终的目录结构大概是这样的
1
2
3
4
5
6
7
8
9
.
├── call
├── call.c
├── linux-4.10.3
├── linux-4.10.3.tar
├── world.img
└── world_mount

2 directories, 4 files

参考资料

LICENSE:

Copyright © 2016 corvo. 未经许可不得用于商业用途. 转载请注明出处.