设计与实现自定义Linux系统调用及内核编译

(在做操作系统课程的这个实验时,由于老师发的教程涉及的Linux版本较早,教程中有些许纰漏,在经历了一天的踩坑填坑最后完成实验后,决定重写一波教程,由原教程+避坑指南构成)

P.S. 此教程相对于原教程增加、改动的部分源于本人网上查找和自己的试验,因此并不百分百正确,很多地方才疏学浅不知所以然,大家发现了错误或者有了更好的解释,可以评论。

实验环境

1、macOS上的Parallels Desktop虚拟机
2、Ubuntu版本:16.04 64位 使用的Ubuntu内核版本:4.15.0
3、用于修改的内核版本:4.10.14
4、CPU 8259u 4核8线程(分配给虚拟机4个核心) 内存:16G(分配给虚拟机8G)

推荐大家在进行此实验前,将虚拟机设置中,分配的内核数提到最大,同时分配给虚拟机的硬盘空间设置在50G以上(比较保险,最好不要低于40G,底线是30G)

实验步骤

更换Ubuntu软件下载源

由于部分Ubuntu装好后默认的软件下载地址是国外的地址,会出现下载软件速度极慢甚至无法下载的情况
因此我们需要先将软件的下载源更换为国内镜像

选择System Settings
设计与实现自定义Linux系统调用及内核编译_第1张图片

选择System-Software&Updates,在Download from处下拉菜单,选择other-China-任意国内源即可,图中显示的是阿里的源,选好后会需要输入管理员密码

之后在terminal中输入

sudo apt-get update

即可
设计与实现自定义Linux系统调用及内核编译_第2张图片

下载内核源码

官方地址:https://www.kernel.org/pub/linux/kernel/v4.x/
由于官方地址下载较慢,我们采用国内镜像
国内镜像地址:https://mirror.bjtu.edu.cn/kernel/linux/kernel/v4.x/
至于下载何种版本的内核源码,如果是16.04的话下载4.10.14(也是我做这个实验使用的版本),其它版本的Ubuntu的话…百度吧亲
设计与实现自定义Linux系统调用及内核编译_第3张图片

解压内核源码并移动到指定位置

下载后得到如下压缩包:
设计与实现自定义Linux系统调用及内核编译_第4张图片

双击此压缩包,打开,右键单击文件夹,选择Extract进行解压
设计与实现自定义Linux系统调用及内核编译_第5张图片

之后需要选择解压目录,我们暂时解压到桌面
设计与实现自定义Linux系统调用及内核编译_第6张图片

之后我们再将源码文件移动至指定目录下面
命令(在Desktop下运行此命令):

sudo mv linux-4.10.14 /usr/src/

在这里插入图片描述

修改源码

首先进入源码文件中的kernel目录

cd /usr/src/linux-4.10.14/kernel

在这里插入图片描述

用vim打开文件sys.c,在sys.c中添加一个函数,为了避免这段函数实现被放入到某个预编译命令之中,我们在vim中使用shift+g跳至文件末尾,使用i切换到编辑模式,添加完后使用:wq保存并退出(每一处做完修改都要保存哦,之后不再赘述)。如果打开文件出现权限不够的情况,请在命令的最前面加上sudo(之后碰到这种问题都按此法解决)

包含头文件的地方
设计与实现自定义Linux系统调用及内核编译_第7张图片

#include 

函数实现的地方

函数实现代码:

asmlinkage int sys_helloworld(void){
			printk(KERN_EMERG “hello JYQ!”) // hello后面是我自己的名字,大家可以随便改(hello改了也行)
			return 1;
}

添加函数声明

刚刚只是在sys.c文件里面进行了函数实现,我们还需要在一个名为syscalls.h的头文件里对该函数进行声明

进入如下目录

cd /usr/src/linux-4.10-14/arch/x86/include/asm/

在这里插入图片描述

用vim打开syscalls.h进行编辑,加入函数声明
命令如下

vim syscalls.h

函数声明位置
设计与实现自定义Linux系统调用及内核编译_第8张图片

asmlinkage int sys_helloworld(void);

添加系统调用

进入如下目录

cd /usr/src/linux-4.10.14/arch/x86/entry/syscalls/

在这里插入图片描述
这里要注意的是,这个路径是针对64位Ubuntu的,如果使用32位Ubuntu进行实验的话,是如下路径(这也是原教程没说明白的地方…两个路径有区别的):

cd /usr/src/linux-3.10.4/arch/x86/syscalls/
//这里的3.10.4瞎写的,根据实际情况改就行

用vim打开syscall_64.tbl(32位的童鞋请打开syscall_32.tbl)
64位:

vim syscall_64.tbl

如下位置添加系统调用
设计与实现自定义Linux系统调用及内核编译_第9张图片

332	64		helloworld		sys_helloworld

这里的332是系统调用的id,可以不一样,但自己要记住,之后有用
后面的64是Linux应用二进制接口的一种类型,剩下的还有common和x32,网上说三个都可以,本人未进行实验(改一下重新编译一下时间成本太高了,有兴趣的童鞋可以试一下)
32位:

vim syscall_32.tbl

设计与实现自定义Linux系统调用及内核编译_第10张图片

编译

首先我们需要安装一些编译内核需要的依赖
请依次执行以下命令

sudo apt-get update
sudo apt-get install libncurses5-dev
sudo apt-get install libssl-dev
sudo apt-get install libelf-dev

以上的依赖不一定全,由各自的系统情况而定,如果在编译过程中发现缺失,比如我在make modules时,遇到报错
设计与实现自定义Linux系统调用及内核编译_第11张图片
报错中有明显的提示“please install libelf-dev…”,于是去安装这个包即可。大家根据自己的实际情况,遇到缺失再安装也是可以的。
之后执行以下命令

sudo make mrproper
sudo make clean
sudo make menuconfig

实现完全干净的第一次编译
mrproper为清除编译过程中产生的所有中间文件
clean为清除上一次产生的编译中间文件
(如果大家需要重新编译内核请务必执行以上三条指令)
执行完第三条指令后,会出现图形化配置窗口
方向键向下,选择General setup
再方向键向右,选择save
之后选择ok、exit、exit即可

之后就是正式编译啦
我们还需要再做一些预备工作
使用命令

lscpu

查看自己的cpu支持几个线程
设计与实现自定义Linux系统调用及内核编译_第12张图片
以上是我的,线程数为CPU(s) (4)乘上 Thread(s) per core(1),这里等于4,即支持4线程(实际上我的电脑CPU支持8线程,我也不明白这里怎么回事,而且实测以8线程进行编译的CPU占用率会比4线程进行编译的CPU占用率高百分之20左右)
得到线程数后,就可以开始编译内核啦

sudo make bzImage -j8

-j后面是线程数,大家按各自情况选择,如果一不小心没加-j,推荐还是ctrl+C停止编译,加上-j再编译(单线程速度感人)
如果编译成功(没有报错、warnings一般都可以无视),下一步进行编译模组

sudo make modules -j8

然后就是漫长的等待了(取决于线程数和大家电脑CPU的性能)

编译结束后,依次安装内核模块和内核

sudo make modules_install
sudo make install

(这里相比较原教材省去了很多步骤,经测试make install可以完成之后的拷贝和创建镜像过程,大家不放心的话可以使用原教材的步骤)
原教材步骤(把make install替换成下述命令):

cp arch/x86/boot/bzImage /boot/vmlinuz-4.10.14
cp .config /boot/config-4.10.14
mkinitramfs -o /boot/initrd.img-4.10.14 4.10.14

修改开机启动项

使用如下命令打开要修改的文件

vim /etc/default/grub

将图中选中的这句语句注释掉
设计与实现自定义Linux系统调用及内核编译_第13张图片
将GRUB_TIMEOUT改为一个大于0的数
设计与实现自定义Linux系统调用及内核编译_第14张图片
调用命令更新开机启动项

sudo update-grub

重新启动,选择修改过后的镜像进入系统

输入以下命令

reboot

系统会进行重启,应该会进入镜像选择的界面,如果未进入,原教材的说法是
按下ESC,考虑到教材年代久远,推荐百度
在我的机器上,reboot后进入以下界面
设计与实现自定义Linux系统调用及内核编译_第15张图片
方向键向下,选择Advanced options for Ubuntu,进入如下界面
设计与实现自定义Linux系统调用及内核编译_第16张图片
(大家应该不会和我这一样这么多镜像的,我踩坑踩多了重编译了好几次qwq)
由于之前我们用4.10.14的源码进行编译,所以我们选择Ubuntu,with Linux 4.10.14 进入即可

进入系统后,可以用以下命令查看当前运行的系统的内核版本

uname -r

利用代码对自定义系统调用进行测试

首先用vim新建一个文件

vim sys_helloworld.c

在这里插入图片描述

代码如下:

#include
#define _NR_helloworld 332
int main(void){
		syscall(_NR_helloworld);
		return 0;
}

这里的_NR_helloworld 332中的332即为之前添加的系统调用的id,各位可以按照之前添加的id填写
之后使用gcc进行编译,编译时将编译后的可执行文件命名为sys_helloworld(别的名字也行,这样好记一些)

执行以下命令,打印开机信息

sudo dmesg -c

设计与实现自定义Linux系统调用及内核编译_第17张图片
发现并没有我们自己写的系统调用函数的输出

接下来关闭当前的这个terminal,重新打开一个terminal(玄学,似乎在之前那个窗口里之后的步骤会失败)
执行该可执行文件

./sys_helloworld

再次执行

sudo dmesg -c

在这里插入图片描述
成功调用自定义系统调用!

教程到此为止就暂时告一段落啦,因为才疏学浅,所以有很多坑填得不明不白,教程中会有不少错误,我自己也会继续去研究,知道的童鞋们也请不吝赐教哦。之后可能还会对这篇教程的不足之处、错误之处进行改正。

你可能感兴趣的:(操作系统)