helloworld程序几乎成了我们学习任何一门软件编程语言首先要学会的程序,它教会了数以万计的程序员编写各种各样的应用程序,这里我们仍然用这个程序来踏入我们学习linux内核的第一步。
1、在这里我们要准备两个文件hello.c和Makefile
hello.c文件
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
static int myinit(void)
{
printk("hello, kernel!\n");
return 0;
}
static void myexit(void)
{
printk("goodbye, kernel!\n");
}
module_init(myinit);
module_exit(myexit);
module.h头文件中包含了对模块的结构定义以及模块的版本控制,任何内核模块程序的编写都要包含这个头文件;kernel.h包含了常用的内核函数。
就像我们的C语言需要一个入口函数和一个出口函数一样(通常是main()函数和exit()函数),我们的内核态程序也有一个入口函数和一个出口函数,分别是module_init()和module_exit()函数,他们各自接收一个函数作为参数,类似我们常说的注册回调函数,也叫钩子函数。钩子函数的一个作用就是代码注入,就好比已经写好的一个函数模板,你只要把自己想要的功能加进去就可以了。这里我们联想linux内核的动态加载过程:所谓动态加载过程就是说,为了能方便的支持新设备、新功能,又不会无限的扩大内核规模,用户在需要的时候可以现场动态加载自己的模块到内核中,使用完毕可以动态卸载,甚至用户可以自己选择使用内核的某些功能,而将某些功能剔除,很显然我们可以使用这种方式来扩展自己的需求,即就是说:linux内核有极好的可扩展性。现在我们回到linux内核的入口函数和出口函数中:我们通过将我们的代码放在两个钩子函数中,然后将这两个钩子函数注入内核中(通过入口函数和出口函数),就将我们要完成的某个功能加进了我们的内核,这就完成了所谓的动态加载和卸载。这里我们简单的加载了一个打印hello,world!的模块,通过module_init(myinit)函数加载进内核,然后通过module_exit(myexit)函数卸载。
MOUDLE_LICENSE("GPL");这句是模块许可声明。GPL是GNU通用公共许可证。
以上就构成了我们编写内核模块的基本模型,以后我们要完成更复杂的功能的话,也是在此基础上做各种扩展,比如设备驱动什么的。
Makefile文件:
obj-m := hello.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
Makefile的功能就是控制我们程序的编译过程,使我们的编译过程不用敲太多命令,因为这些东西Makefile文件已经帮我们弄好了,这就是脚本语言强大的功能。
在我们的程序中,我们首先得知道我们用目标文件来干嘛,第1行告诉我们用hello.o目标文件来生成一个模块。接下来我们需要知道两个路径:即当前文件夹的路径(里边是我们的源文件),这里我们用shell pwd获取这个路径,并将值赋给变量CURRENT_PATH;第二个路径是我们的linux内核的路径LINUX_KERNEL_PATH,我们最终要把模块加载进去,这里都是用shell命令获取到这些路径的。获取到路径之后,我们就开始用一系列编译命令编译我们的源代码并生成目标文件。接下来的两个make命令的使用就是编译我们的源代码的过程。
2、我们所需要准备的文件准备好了之后就可以运行make命令运行Makefile来产生我们的模块了,这里产生的模块名为hello.ko。
3、加载模块
insmod hello.ko 该命令把我们生成的hello.ko加载进内核。
dmesg 用来查看我们的程序的打印结果
4、卸载模块
rmmod hello 该命令卸载掉这个模块
同样我们可以使用dmesg来观察我们的打印结果。
注:加载模块和卸载模块的命令都得使用超级用户权限。
至此,我们完成了内核态的helloworld程序,其中遇到过各种问题,这里也贴出来跟大家讨教一下:
1、在我尝试编译模块的时候有时候会遇到这样的错误: 错误: expected declaration specifiers or ‘...’ before string constant, 而当我去掉MODULE_LICENSE("GPL")这句的时候就没有问题了,不知到什么原因。
2、当我多次加载模块再卸载模块再加载模块的时候,用dmesg观察输出结果会发现打印出来的结果不仅有本次输出结果还有上次加载模块和卸载模块时的结果,没搞清原因。