对于一个简单的驱动模块,以下为Makefile的精典构成:
//------------Makefile----------------------obj-m:=hello.o
KERNELDIR:=/lib/modules/$(shelluname-r)/build
PWD:=$(shellpwd)modules:$(MAKE)-C$(KERNELDIR)M=$(PWD)modules#注意后面必须为tabmodules_install:$(MAKE)-C$(KERNELDIR)M=$(PWD)modules_install#注意后面必须为tab
下边逐一剖析一下各个句子:
obj-m:=hello.o
这句意为有一个模块须要从目标文件hello.o中构造,构造的模块名称为hello.ko.
KERNELDIR:=/lib/modules/$(shelluname-r)/build
这儿是定义一个变量KERNELDIR,但是形参为"/lib/modules/$(shelluname-r)/build"。
这个值中要解释的只有一点,即$(shelluname-r):
你们可以尝试在terminal中输入$:uname-r是哪些结果,没错,这个命令会获取当前内核的版本号,如“2.6.38.2”。
之后我们再查看"/lib/modules"目录下有什么文件:
$:ls/lib/modules/
结果为:
2.6.38.22.6.38-8-generic
所以"/lib/modules/$(shelluname-r)/build"的意思已然很明晰了,就是当前内核的源代码目录
PWD:=$(shellpwd)
有了KERNELDIR的解释,相信这个也不多说了linux 驱动 makefile,就是获取当前目录了。其实在shell里,$(shellxxx)就是相当于在terminal中执行xxx命令。
$(MAKE)-C$(KERNELDIR)M=$(PWD)modules
这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶楼makefile;M=选项让该makefile在构造modules目标之前返回到模块源代码目录;之后,modueles目标指向obj-m变量中设定的模块;在前面的事例中,我们将该变量设置成了hello.o。(—引自ldd3P29)
ps
在ldd3P29页,提到Makefile时,有一个这样的示例:
//Makefile很简单
obj-m:=hello.o
但编译模块时linux 驱动 makefile,则使用以下命令:
$:make-C~/kernel-2.6M='pwd'modules
其中"~/kernel-2.6"为内核源代码树目录,要视自己放置位置而修改,故对应本机环境的命令是:
$:make-C/usr/src/linux-source-2.6.38M=$PWDmodules
最后疗效和第一种方式完全一样!
如今,我们对比一下这两种方式可以晓得,虽然它们之间的惟一区别就是源码目录不一样,分别为"/lib/modules/$(shelluname-r)/build"和"/usr/src/linux-source-2.6.38/",但若果编译过内核都会晓得,usr目录下那种源代码通常是我们自己下载后解压的,而lib目录下的则是在编译时手动copy过去的,二者的文件结构完全一样,故make疗效完全一致就不足为怪了。
Hello,kernel模块实例
在学习C语言的时侯我们一开篇会学习hello,world的程序,我相信你们都认为及其简单,以至于我重复写下边的程序,你们都认为是多余的:
C++代码
#include
intmain()
printf(“hello,world/n”);
return0;
请朋友们思索两个问题:
为何我们必须写一个main()函数?内核的C程序须要main吗?
在这儿#include是为了让我们使用printf(),实际上她们都是C语言库的函数,她们能否在内核程序中使用吗?
我们先回答这两个问题,C语言的应用程序必需要有一个main()函数,由于它是应用程序的入口,至于为何非要是这样个入口,我们只有一个答案:规定的,强制性的。C应用程序有应用程序的规定,作为内核模块有内核模块的规定,所以我们在写内核模块框架的时侯,记住这是规定就可以了。
至于第二个问题比较重要:应用程序可以调用C语言标准库的函数,而内核程序将是绝对不可以的,假如你们还记得我们说fopen,是依赖于open的系统调用,而系统调用是有内核导入的话,这么假如我们能否在内核程序中使用标准函数库,这么就转到了”到底是鸡生蛋,还是蛋生鸡”的困局。
下边的程序就是Linux内核模块的标准的框架(请你们在初次学习的时侯看老师是怎么写这段代码的)。
C++代码
#include
#include
#include//使用printk,须要包含此文件
MODULE_LICENSE(“DualBSD/GPL”);
MODULE_AUTHOR(“stephanxu@eetek”);
MODULE_DESCRIPTION(“thefirstkernelmodule”)
staticint__inithello_init(void)
return0;
staticvoid__exithello_exit(void)
module_init(hello_init);
module_exit(hello_exit);
这就是一个hello内核模块的框架,假如我们要实现复印出hello,kernel,我们只须要在更改hello_init为:
staticint__inithello_init(void)
printk(“hello,kernel/n”);
return0;
模块的框架包含下边四个部份:
(1)模块在加载的时侯须要执行的module_init(function),以及在module_init()食指定的function,模块在卸载的时侯执行的module_exit(function)以及在module_exit()中定义的function.假如申明使用module_exit(),这么此模块将不具备动态卸载功能。
(2)须要定义module_init()调用的初始化函数,以及在module_exit()中使用的清除函数。只有当初始化函数返回非负值(由于在内核中,负值表示操作失败),内核模块能够被正确的加载,否则模块加载失败。而清除函数返回void类型。通常情况下linux开源软件,初始化函数是在模块加载的时侯拿来申请资源,而清除函数是在模块卸载的时侯拿来释放资源,有点类似于C++中的constructor与deconstructor.
(3)头文件,对于内核模块来讲,必需要使用和。须要非常注意的是,这儿面使用了来包含头文件,但很显著这两个头文件都不会是标准函数的头文件,由于,正如上面所说,内核模块不能引用标准函数库的函数。这儿的头文件实际上来自于Linux的内核源代码路径下的$(KERNELSRC)/include目录。
(4)由MODULE_XXX表示的相关内容,那些都是对当前内核模块的描述,尽管不是必须的,并且通常情况下,还是请大家填上几项,非常是模块的许可问题。其实也让你有名扬立万的机会linux apache 虚拟主机,同时你也该负有责任。你对模块有更详细的描述将对你之后调试错误是有帮助的。Modinfo可以让你更快的辨识模块,假如有须要,请参考LDD(,之后均简称为LDD)中有关更多的MODULE_XXX的宏描述。
文章评论