Linux-A01-内核入门

编译内核代码

下载内核代码

从国内镜像网站下载最新的内核源码linux-5.9.1,并进行解压

1
2
xz -d linux-5.9.1.tar.xz
tar xvf linux-5.9.1.tar

配置内核

对内核进行配置是为了得到内核配置文件.config。通过对内核进行配置,可以使未来编译成功的内核增加或减少对一些内核特性的支持。对内核进行配置有多种方法:基于文本/图形等,这里采用make menuconfig方式:

由于该配置方式基于ncurses库,所以在启动配置界面前要先安装ncurses库

1
2
yum install -y ncurses-devel  # make menuconfig requires the ncurses libraries
make menuconfig

配置界面成功如下

image-20201022001432829

对内核按照默认的配置方式进行编译,因此当配置菜单启动后直接退出并保存即可。此时就在内核源码根目录下生成了.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

安装内核的过程主要完成了以下的工作:

  1. 将编译内核时生成的内核镜像bzImage拷贝到/boot目录下,并将这个镜像命名为vmlinuz-5.9.1。如果使用x86的cpu,则该镜像位于arch/x86/boot/目录下(处于正在编译的内核源码下)。

  2. 将~/linux-5.9.1/目录下的System.map拷贝到/boot/目录下,重新命名为System.map-5.9.1。该文件中存放了内核的符号表。

  3. 将~/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
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
27
28
29
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

//必选
//模块许可声明
MODULE_LICENSE("GPL");

//模块加载函数
static int hello_init(void)
{
printk(KERN_ALERT "hello,I am edsionte\n");
return 0;
}

//模块卸载函数
static void hello_exit(void)
{
printk(KERN_ALERT "goodbye,kernel\n");
}

//模块注册
module_init(hello_init);
module_exit(hello_exit);

//可选
MODULE_AUTHOR("xboom dove");
MODULE_DESCRIPTION("This is a simple example!\n");
MODULE_ALIAS("A simplest example");

一个模块程序的中,模块加载函数模块卸载函数以及模块许可声明是必须有的

编写了模块加载函数后,还必须用module_init(mode_name);的形式注册这个函数。当接下来用insmod加载模块时,内核会自动去寻找并执行内核加载函数,完成一些初始化工作。类似的当使用rmmod命令时,内核会自动去执行内核卸载函数。

注意这里的printk函数,可以简单的理解为它是内核中的printf函数,初次使用很容易将其打成printf

编写Makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
obj-m += hello.o

#generate the path
CURRENT_PATH:=$(shell pwd)

#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)

#the absolute path
LINUX_KERNEL_PATH:=/lib/modules/$(LINUX_KERNEL)/build

#complie object

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

#clean
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

在当前目录下运行make命令,就会生成hello.ko文件,即模块目标文件。接下来:

  1. 使用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
    2
    apt install libelf-dev
    apt install libssl-dev
  2. 查看已经加载的模块 lsmod

    1
    2
    [root@localhost linuxDemo]# lsmod |grep hello
    hello 16384 0
  3. 使用rmmod hello.ko 卸载该模块

  4. 使用 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还有另外一种办法:

  1. 将hello.ko文件拷贝到/lib/module/#uname -r#/目录下,

    #uname -r#意思是,在终端中输入
    uname -r后显示的内核版本及名称,例如mini2440中#uname -r#就是2.6.32.2-FriendlyARM。

  2. 然后depmod(会在/lib/modules/#uname -r#/目录下生成modules.dep和modules.dep.bb文件,表明模块的依赖关系)

  3. 最后modprobe hello(注意这里无需输入.ko后缀)即可

两种方法的区别:

modprobe和insmod类似,都是用来动态加载驱动模块的,区别在于modprobe可以解决load module时的依赖关系,它是通过/lib/modules/#uname -r/modules.dep(.bb)文件来查找依赖关系的;而insmod不能解决依赖问题。

参考文档

  1. Linux内核新手区
  2. 《Linux内核设计与实现》