“Linux内核学习”系列博文致力为每一位有兴趣学习Linux内核的同志排除学习道路上的障碍,给大家提供最好的帮助,补足市面上各种材料书籍解释不周、报错多等问题。
本系列博文使用的系统版本和编译器版本等均在《Linux内核学习 篇-00》中有详细介绍。
-
注意事项
- 本系列博文默认您有一定的:Linux、VIM、C、操作系统基础。
- 为了方便大家的阅读学习,可能会引用其他博文资讯中的内容,所有引用都会将来源附在文末。
- 如果参考本文学习过程中遇到任何报错或问题,欢迎您在评论区发起提问,我会将所有问题的原因以及解决方法完善在对应博文的“后话”中,感谢您的贡献。
持续更新中,欢迎点赞收藏关注,三连不迷路。
正文
笔者平时学习生活使用的是Ubuntu,抱着更了解自己的系统,甚至定制属于自己的内核功能等愿望,开始了Linux内核的学习。最初,我直接在工作机上进行各种操作。但是频繁遇到各种问题,诸如:内核编译器版本与模块编译器版本不一致等,这些都会导致内核模块无法加载。
因此笔者建议:即使您平时就是使用Linux的各种发行版进行工作学习,为了保证不破坏自己的环境,请专门搭建一个全新的Linux虚拟机,用来学习接下来的内容。我认为这是完全值得的。
[一] 环境搭建
[1] 下载安装虚拟机/Ubuntu
本系列博文使用的镜像/软件版本如下,可以点击直接下载:
虚拟机:VMware Workstation Pro 16.2.2
Ubuntu:ubuntu-22.04.1-live-server-amd64
Ubuntu建议用种子,会比HTTP快一点。
安装过程很简单,在网络设置环节需要手动DHCP一下,在DISK设置环节需要取消第二个X,不再赘述,不熟悉的同志可以搜索:虚拟机安装Ubuntu live server
[2] 安装GCC、VIM等必要工具
为了尽可能避免奇怪的问题,我决定不进行换源操作,直接进行安装
sudo apt update
sudo apt upgrade
sudo apt install gcc vim make libncurses-dev flex bison libssl-dev libelf-dev dwarves
[二] 重新编译内核
进行内核学习和开发的第一步是配置和重新编译内核,这么做为了以后各种工具的版本问题,也可以让我们先熟悉一下内核的编译启用过程。
首先运行uname -r
查看当前内核版本
然后运行sudo apt install linux-source-5.15.0
,下载Linux内核代码。请将版本号替换为您自己的内核版本。
下载好后,进入以下目录进行解压和配置:
cd /usr/src/linux-source-5.15.0
sudo tar -xjvf linux-source-5.15.0.tar.bz2
cd linux-source-5.15.0
sudo vim .config
# 如果没有.config文件,就使用sudo make menuconfig生成一个,配置页面出来后直接退出就可以
输入/
查找字符串:CONFIG_SYSTEM_TRUSTED_KEYS
和下面的CONFIG_SYSTEM_REVOCATION_KEYS
,将此两行字符串注释掉,然后执行:
sudo make bzImage -j8
sudo make modules -j8
# 这里的`-j8`是编译使用的CPU核心数,这个要根据同志们创建虚拟机时分配的核心数来决定。
sudo make modules_install
sudo make install
reboot
要等比较久,只需要等就好了,需要选择的话直接回车。
重启后再次执行uname -r
,可以看到此时的查询结果已经与原来的不同了,说明我们确实成功编译并更换了系统的内核:
[三] 编写并加载一个helloworld模块
可以先把home目录清理一下,然后在home目录:
mkdir helloworld
cd helloworld
touch helloworld.c
touch Makefile
然后在helloworld.c中输入以下代码:
#include <linux/init.h>
#include <linux/module.h>
static int __init helloworld_init(void){
printk("Hausa_ said hello to Linux kernel");
return 0;
}
static void __exit helloworld_exit(void){
printk("Hausa_ said bye to Linux kernel");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hausa_ hausahan@gmail.com");
MODULE_DESCRIPTION("Hausa_'s helloworld module");
MODULE_ALIAS("helloworld");
麻雀虽小,五脏俱全。尽管这个模块只有两个函数,但是却完全可以加载运行。
helloworld_init()
函数是该模块初始化/入口函数,helloworld_exit()
是销毁/出口函数;
module_init()
和module_exit()
是linux/init.h
中声明的函数,这两个函数告诉内核:某某函数是这个模块的入口/出口函数。
对于最下面一部分大写的函数,是在linux/modules,h
中声明的,分别表明了该模块接受的软件许可协议、作者信息、描述以及别名。
printk()
函数类似C语言常见的printf,只是前者会将字符串输出到内核模块日志中而不是当前终端上。
接下来我们来编译这个模块,在Makefile中输入以下代码:
KERNELDIR := /lib/modules/$(shell uname -r)/build
CONFIG_MODULE_SIG=n
obj-m := helloworld.o
all:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules;
clean:
rm -rf *.o *.ko *.mod *.symvers *.order *.mod
KERNELDIR
为正在运行的Linux内核编译目录,obj-m
表示要生成的模块
在终端输入make
来进行编译。
编译后查看当前目录文件,多了很多文件,其中一个helloworld.ko
即为我们需要的文件,我们可以用file
和modinfo
命令来检查一下编译结果是否正确:
可以看到文件格式为x86-64的ELF文件,并且内核各种信息都正常表示了。
在终端输入sudo insmod helloworld.ko
来加载我们的模块,然后可以输入以下命令查看模块打印信息或检查其是否被加载:
sudo dmesg
sudo lsmod | grep helloworld
最后,可以用sudo rmmod helloworld
来将模块卸载。
此外,加载模块后,系统会在/sys/module/
目录下为模块新建一个目录,对于此例会建helloworld目录,卸载模块后该目录会删除。
本篇结束,有任何报错都可以在评论区反映,我会帮助解决。
后话
如果本文的点赞阅读收藏量还看得过去,我会持续输出同系列文章,感谢大家的支持。
报错
在最后make的一步可能会报错,原因可能是缩进处使用了空格而非tab。请将缩进更改为tab