Linux内核编译及添加系统调用
Linux内核编译及添加系统调用
接下来的几篇博客会详细记录总结在Linux实践课程中的问题和感悟,这是第一篇。
内容要求
- 编译一个干净的Linux内核并加载成功,不需要对内核进行修改。
- 在1中新编译的内核中,添加一个系统调用,实现对指定进程的nice值的修改或读取功能。
开发平台
- Linux环境 gcc vim
具体步骤
-
编译内核并加载
首先下载内核kernel.org,我下载的是最新版。
将下载的linux-4.11.3.tar.xz文件移动到/usr/src/目录下使用命令
sudo mv ./linux-4.11.3.tar.xz /usr/src/
,并进入/usr/src/
目录,然后解压缩,使用命令sudo xz -d linux-4.11.3.tar.xz && tar -xvf linux-4.11.3.tar && rm linux-4.11.3.tar
- 进入linux-4.11.3子目录,执行
sudo make mrproper
清除.config和.o文件。 配置内核。
sudo make menuconfig
注意,执行此命令时,shell窗口不能太小,应为要使用配置对话框。在显示的对话框中,对于每个选项,你可以选择y
或m
或n
,其中,y
表示将相应特性的支持或驱动程序编译进内核;m
表示将相应特性的支持或设备驱动编译成可加载模块,在需要时,可由系统或用户自行加载到内核中;n
表示内核不提供相应特性或驱动的支持。
一般采用默认值,但设备驱动配置要检查,进入Device Drivers,如下图所示:
进入scsi设备配置页面,如下图,将下边几项全设置成*
返回上一个页面,设置”Fusion MPT device support”:设置为*
,如下图:
然后保存退出。编译内核,执行
sudo make bzImage
这一步需要很长时间。当时为了加速,可以使用多核编译sudo make -j 4 bzImage
注意,这里-j
后边的参数最好是物理核心的两倍,经测验,速度最快。编译完成后在/usr/src/linux4.11.3/arch/x86/boot/
目录下会生成名为bzImage的文件。编译模块,执行
sudo make modules
同理,为了更快速可以使用sudo make -j 4 modules
。但是依然需要很长时间。我总共用了41:29.15min,time
命令给出的三个时间分别是user、system、total,total是实际使用的时间,前两个是cpu执行时间,但user+system>tatal,是因为是4核cpu,所以(user+system)/4≈total.-
安装内核
- 安装模块:
sudo make modules_install
同样,可以加-j
参数。 - 建立要载入ramdisk的映像文件:
sudo mkinitramfs 4.11.3 -o /boot/initrd-4.11.3.img
其中4.11.3
是/lib/modules
下的目录名称,及内核版本号,initrd-4.11.3.img
是生成的内核镜像名。上边的命令也可以写成sudo mkinitramfs -o /boot/initrd-4.11.3.img -v 4.11.3
- 安装内核:
sudo make install
- 安装模块:
- 配置grub引导程序:
sudo update-grub2
该命令会修改grub。 - 重启系统 :
sudo reboot
,将使用新内核启动linux。启动完成后,使用uname -a
查看内核版本号。
-
添加系统调用
这里先一步一步介绍操作步骤,然后讲解概念- 分配系统调用号
- Linux的系统调用号保存在
/usr/include/asm-generic/unistd.h
文件中,通过查系统调用表/usr/src/linux-4.11.3/arch/x86/entyr/syscalls/syscall_64.tbl
来确定系统调用号,如图,当前使用到332,则新的系统调用号为333。 - 修改unistd.h文件,为新系统调用nicevalue设置系统调用号,并将__NR_syscalls的值加1,修改前后如图:
- Linux的系统调用号保存在
- 修改系统调用表
- 在系统调用表中关联调用号与新调用的服务例程的入口地址,即修改
/usr/src/linux-4.11.3/arch/x86/entyr/syscalls/syscall_64.tbl
文件,为新添的调用加一条记录,其格式为:<系统调用号><commom/64/x32><系统调用名><服务例程入口地址>
修改后如图:
- 在系统调用表中关联调用号与新调用的服务例程的入口地址,即修改
- 实现系统调用服务例程
- 在
/usr/src/linux-4.11.3/kernel/sys.c
中添加nicevalue的服务例程sys_nicevalue,如图:
- 在
-
重新编译内核
- 见实验内容1操作
-
编写用户态程序测试系统调用:
#include <linux/unistd.h>
#include <sys/syscall.h>
int main()
{
syscall(__NR_nicevalue,2440); // 修改4325进程的nice值为15
return 0;
}- 使用
top
命令查看要修改或查看进程的pid,比如我查看的是chrome进程,如图:
pid为2440,在用户态程序中传入参数2440,保存。用gcc -o nivevalue nivevalue.c
编译后,执行./nivevalue
,再用top -pid 2440
查看chrome的nice值,发现已被修改为15,同时,用dmesg
查看系统环缓冲,发现已输入提示信息。
- 分配系统调用号
系统调用补充知识
系统调用的实质是调用系统函数,于内核态中运行。Linux系统中用户(或封装例程)通过执行一条访管指令int &0x80
来实现系统调用,该指令会产生一个访管中断,从而让系统暂停当前进程的执行,转去执行系统调用处理程序。
- 系统调用号
Linux系统提供了多大几百种系统调用,为唯一标识每一个系统调用,Linux为每个系统调用都设置了唯一的编号,称为系统调用号。在4.11.3内核版本中,系统调用号定义在/usr/include/adm-generic/unistd.h
中。 - 系统调用表
系统调用表是linux内核中用于关联系统调用号以及其他相应服务例程入口地址的一张表。存放在/usr/src/linux-4.11.3/arch/x86/entyr/syscalls/syscall_64.tbl
中,每个表项记录了某个系统调用的服务例程入口地址,以系统调用号为索引可以快速找到其服务例程。 - 系统调用服务例程
每个系统调用都对应一个内核服务例程来实现该系统调用的具体功能,其命名格式为sys_
开头,如sys_read
通常存放在/usr/src/linux-4.11.3/kernel/sys.c
文件中。
DONE