先从最简单的开始,Linux内核中想要打印一个hello world,如何实现?
区别于传统的C++编程,Linux内核中没有常用的库函数,因此需要做出修改
内核的Hello,World模块
内核没有显示终端的,因此输出会输出到日志文件中
创建一个example_example.c的源文件
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
// #include<linux/inotify.h>
/*
模块的初始化函数lkp_init()
__init用于初始化的修饰符, 告诉编译程序, 执行完之后需要回收内存
*/
static int __init lkp_init(void){
printk("<1> Hello, World: from the kernel space...\n");
return 0;
}
/*
模块的退出和清理函数lkp_exit()
*/
static void __exit lkp_exit(void){
printk("<1> Goodbye, World: leaving kernel space...\n");
}
module_init(lkp_init);
module_exit(lkp_exit);
/*
模块的许可证声明GPL
*/
MODULE_LICENSE("GPL");
- printf修改为printk,在kernel.h中
- module在module.h中
之后通过Makefile进行编译,同目录下创建一个Makefile文件,之后使用make
命令编译即可
obj-m:=module_example.o # 这里是指明使用module_example.o建立一个模块,生成一个可加载模块module_example.ko的模块
CURRENT_PATH := $(shell pwd) # 当前路径
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/kernels/$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
# 编译模块 -C表示在指定内核源码位置编译,-M表示编译的模块源文件地址
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
为什么要用make编译,而不是gcc,因为内核编程和用户态编程不一样,内核使用的是kbuild编译系统,需要用内核可加载模块的makefile。
- 通过
insmod module_example.ko
命令加载模块 - 通过
rmmod module_example
命令卸载模块 - 通过
lsmod
查看加载的模块 - 通过
cat /var/log/messages
命令查看日志记录,输出结果会保存在日志中
注意事项
- 关于代码提示
- 我是在windows通过vscode的remote-ssh链接远程centos7的,因此通过vscode进行代码提示。
- 可能识别不到一些头文件,可以手动找到所在库目录添加进去即可,参考之前我写的vscode配置教程:https://blog.csdn.net/qq_40482358/article/details/130745861,
- 简单来说,就是在.vscode中的c_cpp_properties.json中的includePath中补充进去
- 关于编译问题
- 如果编译的时候,gcc找不到库(例如linux/module.h),可以通过
find -name module.h
命令找文件的位置。再通过
命令查看gcc的编译路径,如果没有包含,则在环境变量中将gcc的编译路径加进去`gcc -print-prog-name=cc1plus` -v
vim ~/.bashrc export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/src/kernels/3.10.0-1160.88.1.el7.x86_64/include source ~/.bashrc
- 如果出现了好几个目录,比如我之前安装GPU驱动的时候升级过内核(3.10.0-1062升级到了3.10.0.1160以匹配kernel-devel和kernel-headers)所以我的/usr/src/kernels/目录下就有两个文件夹,这两个文件夹中都有module.h,我们只需要使用当前内核版本的就行了,可以通过
uname -r
命令查看当前内核版本。
- 如果编译的时候,gcc找不到库(例如linux/module.h),可以通过
- 关于内核版本问题
- 内核版本不一致可能在一些地方不太一样, centos7默认是3.10.0, 可以看到我上面的内核版本是3.10.0-1160.88.1.el7.x86_64,可以通过
yum distro-sync
命令升级内核版本.然后安装与内核版本一样的devel和headeryum install "kernel-devel-uname-r == $(uname-r)
.
- 内核版本不一致可能在一些地方不太一样, centos7默认是3.10.0, 可以看到我上面的内核版本是3.10.0-1160.88.1.el7.x86_64,可以通过
Linux内核模块和C应用的对比
Linux内核编程和普通的C语言编程不太一样:
c语言应用程序 | 内核模块程序 | |
---|---|---|
使用函数 | LibC库 | 内核函数 |
运行空间 | 用户空间 | 内核空间 |
运行权限 | 普通用户 | 超级用户 |
入口函数 | main() | module_init() |
出口函数 | exit() | module_cleanup() |
编译 | gcc -c | make |
连接 | gcc | insmod |
运行 | 直接运行 | insmod |
调试 | gdb | kgdb |
参考资料
- 学堂在线-Linux内核分析与应用:https://next.xuetangx.com/course/XIYOU08091001441/14767915