编译内核代码
下载内核代码
从国内镜像网站下载最新的内核源码linux-5.9.1,并进行解压
1 | xz -d linux-5.9.1.tar.xz |
配置内核
对内核进行配置是为了得到内核配置文件.config。通过对内核进行配置,可以使未来编译成功的内核增加或减少对一些内核特性的支持。对内核进行配置有多种方法:基于文本/图形等,这里采用make menuconfig方式:
由于该配置方式基于ncurses库,所以在启动配置界面前要先安装ncurses库
1 | yum install -y ncurses-devel # make menuconfig requires the ncurses libraries |
配置界面成功如下
对内核按照默认的配置方式进行编译,因此当配置菜单启动后直接退出并保存即可。此时就在内核源码根目录下生成了.config文件
编译内核
编译内核包含两部分的工作,
- 其一是编译内核,即编译配置选项中标记为Y的那部分,这部分内核最终形成bzIamge镜像文件;
- 其二是编译内核模块,即编译配置选项中标记为M的那部分内核,这部分形成以.ko结尾的内核模块目标文件。
上述两部分编译工作可以依次通过make bzImage和make modules完成,也可以通过一条make命令直接完成。
编译内核的整个过程比较漫长,可以对make加-j参数来提高编译的效率。在make时使用该选项会为编译过程分配n个并发任务,这样可以缩短编译时间。n的取值为cpu个数的二倍。
1 | make -j4 |
如果不想看到垃圾信息又不想错过告警和错误信息,可以使用重定向文件写入,编译信息会写到文件中,同时错误和告警信息都会在屏幕上显示
1 | make > ../detritus |
安装内核
安装过程分为两部分,首先对内核模块进行安装,这个过程会将刚刚编译内核模块时生成的内核模块复制到/lib/modules/5.9.1/目录下,其中5.9.1为对应的内核版本。使用的命令如下:
1 | sudo make modules_install |
接着使用下述命令安装编译好的内核:
1 | sudo make install |
安装内核的过程主要完成了以下的工作:
-
将编译内核时生成的内核镜像bzImage拷贝到/boot目录下,并将这个镜像命名为vmlinuz-5.9.1。如果使用x86的cpu,则该镜像位于arch/x86/boot/目录下(处于正在编译的内核源码下)。
-
将~/linux-5.9.1/目录下的System.map拷贝到/boot/目录下,重新命名为System.map-5.9.1。该文件中存放了内核的符号表。
-
将~/linux-5.9.1/目录下的.config拷贝到/boot/目录下,重新命名为config-5.9.1。
创建initrd.img文件
initrd.img即为初始化的ramdisk文件,它是一个镜像文件,将一些最基本的驱动程序和命令工具打包到镜像文件里。该镜像文件的作用是在系统还没有挂载根分区前,系统需要执行一些操作,比如挂载scsi驱动,此时将initrd文件释放到内存中,作为一个虚拟的根分区,然后执行相关脚本,运行insmod命令加载需要的模块。
1 | sudo mkinitramfs 5.9.1 -o /boot/initrd.img-5.9.1 |
更新grub
最后一步则是更新grub启动菜单,使用下面的命令则可以自动更新启动菜单:
1 | sudo update-grub2 |
这样会将刚才编译好的内核放在启动菜单的首位,如果需要修改启动菜单中默认系统的启动顺序,则修改/boot/grub/grub.cfg文件中的set default=的值即可
内核就这样编译完毕了
Hello Kernel
编写一个Hello.c文件
1 |
|
一个模块程序的中,模块加载函数,模块卸载函数以及模块许可声明是必须有的
编写了模块加载函数后,还必须用module_init(mode_name);
的形式注册这个函数。当接下来用insmod
加载模块时,内核会自动去寻找并执行内核加载函数,完成一些初始化工作。类似的当使用rmmod
命令时,内核会自动去执行内核卸载函数。
注意这里的printk函数,可以简单的理解为它是内核中的printf函数,初次使用很容易将其打成printf
编写Makefile文件
1 | obj-m += hello.o |
在当前目录下运行make
命令,就会生成hello.ko文件,即模块目标文件。接下来:
-
使用
insmod hello.ko
添加该模块如果报错 “Cannot generate ORC metadata for CONFIG_UNWINDER_ORC=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel”
Centos:
yum install elfutils-libelf-devel
Ubuntu:
1
2apt install libelf-dev
apt install libssl-dev -
查看已经加载的模块
lsmod
1
2[root@localhost linuxDemo]# lsmod |grep hello
hello 16384 0 -
使用rmmod hello.ko 卸载该模块
-
使用
dmesg
命令查看printk
中显示的语句1
2
3
4
5[339722.818996] hello: loading out-of-tree module taints kernel.
[339722.819088] hello: module verification failed: signature and/or required key missing - tainting kernel
[339722.819609] hello,I am edsionte
[339764.633084] goodbye,kernel
[339786.267993] hello,I am edsionte
linux内核从3.7 开始加入模块签名检查机制,如果内核选项CONFIG_MODULE_SIG和CONFIG_MODULE_SIG_FORCE打开的话,
当加载模块时内核会检查模块的签名,如果签名不存在或者签名内容不一致,会强制退出模块的加载
提示"module verification failed: signature and/or required key missing - tainting kernel" 关闭方式:
在Makefile中添加
CONFIG_MODULE_SIG=n
然后重新重新编译内核
其实第一步insmod
还有另外一种办法:
-
将hello.ko文件拷贝到/lib/module/#uname -r#/目录下,
#uname -r#意思是,在终端中输入
uname -r
后显示的内核版本及名称,例如mini2440中#uname -r#就是2.6.32.2-FriendlyARM。 -
然后
depmod
(会在/lib/modules/#uname -r#/目录下生成modules.dep和modules.dep.bb文件,表明模块的依赖关系) -
最后
modprobe hello
(注意这里无需输入.ko后缀)即可
两种方法的区别:
modprobe
和insmod类似,都是用来动态加载驱动模块的,区别在于modprobe
可以解决load module
时的依赖关系,它是通过/lib/modules/#uname -r/modules.dep(.bb)文件来查找依赖关系的;而insmod
不能解决依赖问题。
参考文档
- Linux内核新手区
- 《Linux内核设计与实现》