(三)简单的内核模块驱动程序(下)
一、概述
-
上篇中我们讲述了,简单的内核模块驱动程序。但是那并不是内核模块的一般形式。
不知你是否尝试过将我们编写的内核模块加载到到内核中。如果你加载了,应该会看到以下输出:
# hello:module license 'unspecified' taints kernel # Disabling lock debugging due to kernel taint
这两句话的大意是:因为加载了hello模块而导致内核被污染,并且因此禁止了锁的调试功能。
你肯定很奇怪,这是为什么呢?其实呢,这是因为GNU的规定,因为Linux是开源项目,所以其要求任何组织或者个人在免费得到源码并对其进行修改和再发布的同时,必须将修饰后的源码发布。这就是多为的GPL许可证协议。
我们只需要在代码中加上如下一段代码,就可以了。
MODULE_LICENSE("GPL");
MODULE_LICENSE是一个宏,里面的参数是一个字符串,代表相应的许可证协议。可以是:GPL、GPL v2 、Dual MPL/GPL等其他不同的协议。我们可以在源码树目录:include/linux/module.h里面查看到。
那么这个东西的具体原理是什么呢?原来,这些宏是会生成一些模块信息,放在ELF文件中的一个特殊字段中,模块在加载时会将该信息复制到内存中。内核运行时回去检查该信息,如果没有该信息,那么内核中的很多功能我们是不能调用,比如说一些内核的API函数。
好啦,现在你明白为什么要加这句话了吧。其实呢,内核汇总还有很多其宏,在我们编写驱动时会经常用到。
MODULE_AUTHOR("..."); //描述模块的作者信息 MODULE_DESCRIPTION(); //用于描绘模块的详细信息 MODULE_ALIAS(); //可以给用户空间使用的一个别名
模块的初始化以及模块清除函数的名字都是固定的。名字都叫main,如果这样那么我们在调用模块清除和卸载的函数时,如果不看函数中的具体内容,就无法分辨函数具体的功能,有没有什么更好的办法呢?当然是有的,GNU为我们提供了一些函数别名机制。
module_init(hello_init); module_exit(hello_exit);
module_init()和module_exit()是两个宏,分别用于指定init_module函数的别名是hello_int,cleanup_module函数的别名是hello_exit。
好啦,这里我们函数可以任意指定函数的别名啦,但是这样又引入了一个新的问题。就是如果我们任意指定的一个函数与内核中的某个函数重名了该怎么办呢?因为C原因没有类似C++的函数命名空间以及重载的概念。而该模块最终有时要加载进入内核成为内核的一部分。别忘啦,我们C语言是有static关键字的,用它来修饰每一个函数不就是把函数的作用域限定到本文件内了吗?所以,我们在编写内核模块的时候,每个函数都最好加上static修饰。
-
我们都知道,Linux在内存的利用上面可以说是教科书级别的了。众多Linux大神在设计Linux时,是绝对不会浪费一丁点内存的。这当然值得我们在以后的编程中去学习。所以我们看看我们前面写的程序是否有什么问题呢?
我们很容易发现,在原来的代码中,模块的初始化函数只会被调用一次,后面就不会再被调用。所以它占用的内存应给被及时给释放掉。这里我们选择在该函数前面加__init可以达到我们的目的。那么__init由什么功能呢?原来,该标记会会把标记的函数放到ELF文件的特定代码段,在模块加载这些段时会单独分配内存,这些函数调用成功后,模块的加载程序就会释放这部分内存空间。那么对于模块清除函数,我们也有同样的标记__exit用于修饰清除函数,和__init作用很类似,但是是用于模块的卸载的。如果模块是不允许卸载,那么这段代码完全就不用加载。
-
好啦,有了以上的补充。我们来完善一下上次的程序:
/*******************hello********************/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init hello_init(void)
{
printk("init module:hello world!\n ");
return 0;
}
static void __exit hello_exit(void)
{
printk("exit module:hello_exit!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fan <123456@163.com>");
MODULE_DESCRIPTION("A hello module");
MODULE_ALIAS("printf_hello");
- 好啦,这就是内核驱动模块最小的一个模型啦。可以看看,是不是麻雀虽小,五脏俱全呐。