Linux内核编译与系统调用

今天尝试一下人生中第一次编译Linux内核,遇到了几个问题,也收获了很多技能,总的来说还是很有意义的。

Linux体系结构简介

Linux体系结构

Linux可以分为两部分,分别为用户空间和内核空间具体如下图:

1)用户空间包括:用户的应用程序、C库

2)内核空间包括:系统调用接口、内核(狭义的)、平台架构相关的代码

Linux内核结构(广义内核)

Linux内核由七个部分构成,具体如下图所示:

a) 系统调用接口(SCI):open、read、write等系统调用
b) 进程管理(PM):创建进程、删除进程、调度进程等
c) 内存管理(MM):内存分配、管理等
d) 虚拟文件系统(VFS):为多种文件系统提供统一的操作接口
e) 网络协议栈:提供各种网络协议
f) CPU架构相关代码(Arch):为的是提高至移植性
g) 设备驱动程序(DD):各种设备驱动,占到内核的70%左右代码

Linux内核源码简介

源码获取

可以从官网直接下载:https://www.kernel.org/

源码目录简介

其源码主要有以下目录(介绍重要目录):
a) Arch目录:存放处理器相关的代码。下设子目录,分别对应具体的CPU,每个子目录有boot,mm,以及kernel三个子目录,分别对应系统引导以及存储管理,和系统调用
b) Include目录:内核所需要的大部分头文件目录。与平台无关的在include/linux子目录下,与平台相关的则放在include相应的子目录中。
c) fs目录:存放各种文件系统的实现代码。
d) init目录:init子目录包含核心的初始化代码(不是系统的引导代码)。其包含两个文件main.c和version.c,可以用来研究核心如何工作。
e) ipc目录:包含核心进程间的通信代码。
f) kernel目录:包含内核管理的核心代码。与硬件相关代码放在arch/*/kernel目录下。
g) mm目录:包含了所有的内存管理代码。与硬件相关的内存管理代码位于arch/*/mm目录下。
h) scripts目录:包含用于配置核心的脚本文件。
i) lib目录:包含了核心的库代码,与硬件相关的库代码被放在arch/*/lib/目录下

Linux内核编译(CentOS 7)

环境准备

安装所需的软件包:

1
2
sudo yum update
sudo yum install -y ncurses-devel make gcc bc bison flex elfutils-libelf-devel openssl-devel grub2

下载内核源码:

1
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.17.11.tar.xz

这里我用的是内核的4.17.11版本。

解压缩到当前目录:

1
tar -xf linux-4.17.11.tar.xz

重用当前系统内核的配置:

1
cp -v /boot/config-3.10.0-693.el7.x86_64 .config

然后我们就可以使用make menuconfig开启菜单了,随后即可自由配置所需的内核模块:

配置完config之后,保存并退出。

虚拟机增加磁盘容量

如果你的虚拟机磁盘容量大于30GB请直接跳过这部分!

由于当时安装虚拟机时没考虑去用它编译内核,因此虚拟机磁盘容量只有20GB,然后内核编译过程中就会出错,因为磁盘容量不足,因此这里说一下如何给CentOS 7虚拟机磁盘扩容。

1. VMware扩展磁盘

首先将虚拟机关机,在虚拟机上点击右键,设置,点硬盘,扩展,我这里设置的是40G,如果主机硬盘够大可以多设置一些,然后点击扩展,随后启动虚拟机。

2. CentOS中扩展

首先查看系统的磁盘情况:

1
sudo fdisk -l
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[admin@localhost ~]$ sudo fdisk -l
[sudo] password for admin:

Disk /dev/sda: 42.9 GB, 42949672960 bytes, 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0009003d

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 41943039 19921920 8e Linux LVM
/dev/sda3 41943040 83886079 20971520 83 Linux

Disk /dev/mapper/centos-root: 39.6 GB, 39619395584 bytes, 77381632 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/centos-swap: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

分区情况如上所示。

然后创建新的分区sda3:

1
sudo fdisk /dev/sda

然后根据提示创建分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
Command (m for help): n   # 输入 n 表示创建新分区
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 3 # 输入 3 表示sda3,因为前面已经有两个了
First cylinder (1306-4568, default 1306): # 回车,按默认值
Using default value 1306
Last cylinder, +cylinders or +size{K,M,G} (1306-4568, default 4568): # 回车,按默认值
Using default value 4568

Command (m for help): w # 输入 w 保存退出
The partition table has been altered!

再次查看分区情况:

1
sudo fdisk -l
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Disk /dev/sda: 42.9 GB, 42949672960 bytes, 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0009003d

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 41943039 19921920 8e Linux LVM
/dev/sda3 41943040 83886079 20971520 83 Linux

Disk /dev/mapper/centos-root: 39.6 GB, 39619395584 bytes, 77381632 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/centos-swap: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

分区已经变成3个了,但是还没有挂载,还不能使用。

重启操作系统:

1
sudo reboot

格式化新的分区为ext3文件系统:

1
sudo mkfs -t ext3 /dev/sda3

将物理硬盘分区初始化为物理卷:

1
sudo pvcreate /dev/sda3

向卷组中添加物理卷来增加卷组的容量:

1
2
3
4
5
6
7
8
9
10
11
12
13
[admin@localhost ~]$ sudo df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 894M 0 894M 0% /dev
tmpfs 910M 0 910M 0% /dev/shm
tmpfs 910M 11M 900M 2% /run
tmpfs 910M 0 910M 0% /sys/fs/cgroup
/dev/mapper/centos-root 37G 21G 17G 55% /
/dev/sda1 1014M 313M 702M 31% /boot
tmpfs 182M 4.0K 182M 1% /run/user/42
tmpfs 182M 24K 182M 1% /run/user/1000
/dev/sr0 8.1G 8.1G 0 100% /run/media/admin/CentOS 7 x86_64
tmpfs 182M 0 182M 0% /run/user/0

从以上信息我们可以看出,centos就是root所在的组名,输入指令增加卷组容量:

1
sudo vgextend centos /dev/sda3

查看可扩展空间大小并扩充磁盘空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[admin@localhost ~]$ sudo vgdisplay 
[sudo] password for admin:
--- Volume group ---
VG Name centos
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 5
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 2
Open LV 2
Max PV 0
Cur PV 2
Act PV 2
VG Size 38.99 GiB
PE Size 4.00 MiB
Total PE 9982
Alloc PE / Size 9958 / <38.90 GiB
Free PE / Size 24 / 96.00 MiB
VG UUID IW5x2l-MRIe-AbnO-u110-wmFr-QCJj-DwN41a

正常未扩容之前Free PE / Size应该很大,由于我是先进行了扩容后写的博客,这里没法展示之前原本的数据。

扩充磁盘空间:

1
sudo lvextend -L+20G /dev/mapper/vg_test001-lv_root /dev/sda3

上面的20G就是我们扩容的容量,最大不应当超过Free PE / Size的值。

增大或者收缩未加载的文件系统的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[admin@localhost ~]$ sudo df -hT
[sudo] password for admin:
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 894M 0 894M 0% /dev
tmpfs tmpfs 910M 0 910M 0% /dev/shm
tmpfs tmpfs 910M 11M 900M 2% /run
tmpfs tmpfs 910M 0 910M 0% /sys/fs/cgroup
/dev/mapper/centos-root xfs 37G 21G 17G 55% /
/dev/sda1 xfs 1014M 313M 702M 31% /boot
tmpfs tmpfs 182M 4.0K 182M 1% /run/user/42
tmpfs tmpfs 182M 24K 182M 1% /run/user/1000
/dev/sr0 iso9660 8.1G 8.1G 0 100% /run/media/admin/CentOS 7 x86_64
tmpfs tmpfs 182M 0 182M 0% /run/user/0

可以看出/dev/mapper/centos-root是xfs类型的

文件扩充:

1
sudo xfs_growfs /dev/mapper/centos-root

至此所有的步骤完成,扩容成功,如果中间出了什么错误,自行百度谷歌即可。

开始编译

进入到源码解压的目录下,依次执行如下命令开始内核编译,这可能需要几个小时的时间,可以写一个简单的shell脚本自动执行这些编译指令即可。

1
2
3
4
5
make bzImage
make modules
make
make modules_install
make install

这里最后一步make install之后我这里报了一个错误:

dracut[38489]: Failed to install module libnvdimmvmxnet3

目测是一个模块安装错误,但是重启后发现内核已经编译安装成功了,可以正常使用:

图中4.17.11内核就是我们新编译安装的。

但是,刚才为什么会报错呢?百度毫无疑问无解,我们直接谷歌,发现谷歌上有很多提问,相关内容也不多,于是改变思路,直接搜索未安装成功的libnvdimmvmxnet3模块,然后找到了答案。原来这是vm虚拟机的一个模块,emmmmm突然发现好像自己并没有解决这个问题。。。等我找出来原因在写

增加一个自定义的系统调用

1.踩坑合集(4.17.11版本内核勿看)

俗话说尽信书则不如无书,照着实验手册来做,忽然发现做出来的有问题,先记录一下这些有问题的

1.1加系统调用号:

先进入源代码根目录,也就是你安装的的内核的目录。在系统调用入口表中增加一项:

1
2
cd arch/x86/entry/syscalls/
vi syscall_64.tbl

在文件末端增加自己的系统调用函数以及对应的系统调用编号,注意不要覆盖原有的系统调用(333号为本文增加的)

这个时候其实已经感觉有点不对了,看看前面的系统调用号,明显和自己加的不一样。

1.2 声明系统调用函数

1
2
3
cd include/linux/

vi syscalls.h

在末端加入系统调用函数的声明:

1.3实现系统调用函数

1
vi kernel/sys.c

在文件末端加入对应的实现函数:

1.4编译安装内核同上,然后重启系统选择编译好的内核。

首先编写相应代码测试:

1
vi mysys.c
1
2
3
4
5
6
7
8
9
10
#include<unistd.h>
#include<stdio.h>

int main(){
int i;
for(i = 0;i < 5;i++){
printf("ret = %d\n",syscall(333,i));
}
return 0;
}

编译运行后,查看函数是否加入我们的内核:

1
cat /proc/kallsyms | grep mysyscall

结果是:000000000000000 T sys_mysyscall

我们可以看到,自己编译的函数成功加入到内核,然后我们执行测试代码,然后用dmesg查看内核输出信息:

可以看出可以调用也正常加入了内核,但是参数明显不对,传参出现了错误!

2.正确做法(4.17.11版本及以上内核)

2.1加系统调用号:

先进入源代码根目录,在系统调用入口表中增加一项:

1
2
cd arch/x86/entry/syscalls/
vi syscall_64.tbl

在文件末端增加自己的系统调用函数以及对应的系统调用编号,注意不要覆盖原有的系统调用(333号为本文增加的)

2.2 声明系统调用函数

1
2
3
cd include/linux/

vi syscalls.h

在末端加入系统调用函数的声明:

2.3实现系统调用函数

1
vi kernel/sys.c

在文件末端加入对应的实现函数:

2.4编译安装内核同上,然后重启系统选择编译好的内核。

首先编写相应代码测试:

1
vi mysys.c
1
2
3
4
5
6
7
8
9
10
#include<unistd.h>
#include<stdio.h>

int main(){
int i;
for(i = 0;i < 5;i++){
printf("ret = %d\n",syscall(333,i));
}
return 0;
}

编译运行后,查看函数是否加入我们的内核:

1
cat /proc/kallsyms | grep mysyscall

结果是:000000000000000 T sys_mysyscall

我们可以看到,自己编译的函数成功加入到内核,然后我们执行测试代码,然后用dmesg查看内核输出信息:

至此问题解决,成功增加了一个自己的系统调用!

欢迎大家批评指正!


Linux内核编译与系统调用
https://chujian521.github.io/blog/2019/10/21/Linux内核编译与系统调用/
作者
Encounter
发布于
2019年10月21日
许可协议