“Linux内核学习”系列博文致力为每一位有兴趣学习Linux内核的同志排除学习道路上的障碍,给大家提供最好的帮助,补足市面上各种材料书籍解释不周、报错多等问题。
本系列博文使用的系统版本和编译器版本等均在《Linux内核学习 篇-00》中有详细介绍。
目录
注意事项正文
[一] 环境搭建
[1] 下载安装虚拟机/Ubuntu[2] 安装GCC、VIM等必要工具 [二] 重新编译内核[三] 编写并加载一个helloworld模块 后话
报错
注意事项 本系列博文默认您有一定的:Linux、VIM、C、操作系统基础。为了方便大家的阅读学习,可能会引用其他博文资讯中的内容,所有引用都会将来源附在文末。如果参考本文学习过程中遇到任何报错或问题,欢迎您在评论区发起提问,我会将所有问题的原因以及解决方法完善在对应博文的“后话”中,感谢您的贡献。持续更新中,欢迎点赞收藏关注,三连不迷路。
正文
笔者平时学习生活使用的是Ubuntu,抱着更了解自己的系统,甚至定制属于自己的内核功能等愿望,开始了Linux内核的学习。最初,我直接在工作机上进行各种操作。但是频繁遇到各种问题,诸如:内核编译器版本与模块编译器版本不一致等,这些都会导致内核模块无法加载。
因此笔者建议:即使您平时就是使用Linux的各种发行版进行工作学习,为了保证不破坏自己的环境,请专门搭建一个全新的Linux虚拟机,用来学习接下来的内容。我认为这是完全值得的。
本系列博文使用的镜像/软件版本如下,可以点击直接下载:
虚拟机:VMware Workstation Pro 16.2.2
Ubuntu:ubuntu-21.10-desktop-amd64
Ubuntu建议用种子,会比HTTP快一点。
安装过程文章众多,不再赘述,不熟悉的同志可以复制搜索:虚拟机安装Ubuntu
为了尽可能避免奇怪的问题,我决定不进行换源操作,直接进行安装
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-get install linux-source-5.13.0,下载Linux内核代码。请将版本号替换为您自己的内核版本。
下载好后,进入以下目录进行解压和配置:
cd /usr/src/linux-source-5.13.0 sudo tar -xjvf linux-source-5.13.0.tar.bz2 cd linux-source-5.13.0 sudo vim .config
输入/查找字符串:CONFIG_SYSTEM_TRUSTED_KEYS,将此字符串注释掉,然后执行:
sudo make bzImage -j8 sudo make modules -j8 #这里的`-j8`是编译使用的CPU核心数,这个要根据同志们创建虚拟机时分配的核心数来决定。 sudo make modules_install sudo make install reboot
要等比较久,只需要等就好了,需要选择的话直接回车。
重启后再次执行uname -r,可以看到此时的查询结果已经与原来的不同了,说明我们确实成功编译并更换了系统的内核:
可以先把home目录清理一下,然后在home目录:
mkdir helloworld cd helloworld touch helloworld.c touch Makefile
然后在helloworld.c中输入以下代码:
#include#include 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目录,卸载模块后该目录会删除。
本篇结束,有任何报错都可以在评论区反映,我会帮助解决。
后话
如果本文的点赞阅读收藏量还看得过去,我会持续输出同系列文章,感谢大家的支持。
报错


