Linux内核Module.docx
《Linux内核Module.docx》由会员分享,可在线阅读,更多相关《Linux内核Module.docx(18页珍藏版)》请在冰豆网上搜索。
Linux内核Module
Linux–Module
Module可以允许我们动态的改变kernel,加载devicedriver,而且它也能缩短我们driverdevelopment的时间。
在RedHat里,我们可以执行sndconfig,它可以帮我们config声卡。
config完之后如果捉得到你的声卡,那你的声卡马上就可以动了,而且还不用重新激活计算机。
这是怎么做到的呢?
就是靠module。
module其实是一般的程序,但是它可以被动态载到kernel里成为kernel的一部分。
载到kernel里的module它具有跟kernel一样的权力,可以access任何kernel的datastructure。
你听过kdebug吗?
它是用来debugkernel的。
它就是先将它本身的一个module载到kernel里,而在userspace的gdb就可以经由跟这个module沟通,得知kernel里的datastructure的值,除此之外,还可以经由载到kernel的module去更改kernel里datastructure。
我们知道,在写C程序的时候,一个程序只能有一个main。
Kernel本身其实也是一个程序,它本身也有个main,叫start_kernel()。
当我们把一个module载到kernel里的时候,它会跟kernel整合在一起,成为kernel的一部分。
请各位想想,那module可以有main吗?
答案很明显的,是No。
理由很简单。
一个程序只能有一个main。
在使用module时,有一点要记住的是module是处于被动的角色,它是提供某些功能让别人去使用的。
Kernel里有一个变量叫module_list,每当user将一个module载到kernel里的时候,这个module就会被记录在module_list里面。
当kernel要使用到这个module提供的function时,它就会去search这个list,找到module,然后再使用其提供的function或variable。
每一个module都可以export一些function或变量来让别人使用。
除此之外module也可以使用已经载到kernel里的module提供的function。
这种情形叫做modulestack。
比方说,moduleA用到moduleB的东西,那在加载moduleA之前必须要先加载moduleB。
否则moduleA会无法加载。
除了module会export东西之外,kernel本身也会export一些function或variable。
同样的,module也可以使用kernel所export出来的东西。
由于大家平时都是撰写userspace的程序,所以,当突然去写module的时候,会把平时写程序用的function拿到module里使用。
像是printf之类的东西。
我要告诉各位的是,module所使用的function或variable,要嘛就是自己写在module里,要嘛就是别的module提供的,再不就是kernel所提供的。
你不能使用一般libc或glibc所提供的function。
像printf之类的东西。
这一点可能是各位要多小心的地方。
kernel本身会export出一些function或variable来让module使用,但是,我们不是万能的,我们怎么知道kernel又开放哪些东西让我们使用呢?
Linux提供一个command,叫ksyms,你只要执行ksyms-a就可以知道kernel或目前载到kernel里的module提供了那些function或variable。
底下是我的系统的情形:
c0216ba0drive_info_R744aa133
c01e4a44boot_cpu_data_R660bd466
c01e4ac0EISA_bus_R7413793a
c01e4ac4MCA_bus_Rf48a2c4c
c010cc34__verify_write_R203afbeb
.....
在kernel里,有一个symboltable是用来记录export出去的function或variable。
除此之外,也会记录着哪个moduleexport有哪些function。
上面几行中,表示kernel提供了drive_info这个function/variable。
所以,我们可以在kernel里直接使用它,等载到kernel里时,会自动做好link的动作。
由此,我们可以知道,module本身其实是还没做link的一些objectcode。
一切都要等到module被加载kernel之后,link才会完成。
各位应该可以看到drive_info后面还接着一些奇怪的字符串。
_R744aa133,这个字符串是根据目前kernel的版本再做些encode得出来的结果。
为什么额外需要这一个字符串呢?
Linux不知道从那个版本以来,就多了一个config的选项,叫做Setversionnumberinsymbolsofmodule。
这是为了避免对系统造成不稳定。
我们知道Linux的kernel更新得很快。
在kernel更新的过程,有时为了效率起见,会对某些旧有的datastructure或function做些改变,而且一变可能有的variable被拿掉,有的function的prototype跟原来的都不太一样。
如果这种情形发生的时候,那可能以前2.0.33版本的module拿到2.2.1版本的kernel使用,假设原来module使用了2.0.33kernel提供的变量叫A,但是到了2.2.1由于某些原因必须把A都设成NULL。
那当此module用在2.2.1kernel上时,如果它没去检查A的值就直接使用的话,就会造成系统的错误。
也许不会整个系统都死掉,但是这个module肯定是很难发挥它的功能。
为了这个原因,Linux就在compilemodule时,把kernel版本的号码encode到各个exportedfunction和variable里。
所以,刚才也许我们不应该讲kernel提供了drive_info,而应该说kernel提供了driver_info_R744aa133来让我们使用。
这样也许各位会比较明白。
也就是说,kernel认为它提供的driver_info_R744aa133这个东西,而不是driver_info。
所以,我们可以发现有的人在加载module时,系统都一直告诉你某个function无法resolved。
这就是因为kernel里没有你要的function,要不然就是你的module里使用的function跟kernelencode的结果不一样。
所以无法resolve。
解决方式,要嘛就是将kernel里的setversion选项关掉,要嘛就是将modulecompile成kernel有办法接受的型式。
那有人就会想说,如果kernel认定它提供的function名字叫做driver_info_R744aa133的话,那我们写程序时,是不是用到这个funnction的地方都改成driver_info_R744aa133就可以了。
答案是Yes。
但是,如果每个function都要你这样写,你不会觉得很烦吗?
比方说,我们在写driver时,很多人都会用到printk这个function。
这是kernel所提供的function。
它的功能跟printf很像。
用法也几乎都一样,是debug时很好用的东西。
如果我们module里用了一百次printk,那是不是我们也要打一百次的printk_Rdd132261呢?
当然不是,聪明的人马上会想到用#defineprintkprintk_Rdd132261就好了嘛。
所以啰,Linux很体贴的帮我们做了这件事。
如果各位的系统有将setversion的选项打开的话,那大家可以到/usr/src/linux/include/linux/modules这个目录底下。
这个目录底下有所多的*.ver档案。
这些档案其实就是用来做#define用的。
我们来看看ksyms.ver这个档案里,里面有一行是这样子的:
#defineprintk_set_ver(printk)
set_ver是一个macro,就是用来在printk后面加上versionnumber的。
有兴趣的朋友可以自行去观看这个macro的写法。
用了这些ver檔,我们就可以在module里直接使用printk这样的名字了。
而这些ver档会自动帮我们做好#define的动作。
可是,我们可以发现这个目录有很多很多的ver檔。
有时候,我们怎么知道我们要呼叫的function是在哪个ver档里有定义呢?
Linux又帮我们做了一件事。
/usr/src/linux/include/linux/modversions.h这个档案已经将全部的ver档都加进来了。
所以在我们的module里只要include这个档,那名字的问题都解决了。
但是,在此,我们奉劝各位一件事,不要将modversions.h这个档在module里include进来,如果真的要,那也要加上以下数行:
#ifdefMODVERSIONS
#include
#endif
加入这三行的原因是,避免这个module在没有设定kernelversion的系统上,将modversions.h这个档案include进来。
各位可以去试试看,当你把setversion的选项关掉时,modversions.h和modules这个目录都会不见。
如果没有上面三行,那compile就不会过关。
所以一般来讲,modversions.h我们会选择在compile时传给gcc使用。
就像下面这个样子。
gcc-c-D__KERNEL__-DMODULE-DMODVERSIONSmain.c\
-includeusr/src/linux/include/linux/modversions.h
在这个commandline里,我们看到了-D__KERNEL__,这是说要定义__KERNEL__这个constant。
很多跟kernel有关的headerfile,都必须要定义这个constant才能include的。
所以建议你最好将它定义起来。
另外还有一个-DMODVERSIONS。
这个constant我刚才忘了讲。
刚才我们说要解决fucntion或variable名字encode的方式就是要includemodversions.h,其实除此之外,你还必须定义MODVERSIONS这个constant。
再来就是MODULE这个constant。
其实,只要是你要写module就一定要定义这个变量。
而且你还要includemodule.h这个档案,因为_set_ver就是定义在这里的。
刚才讲的都是使用别人的function上遇到的名字encode问题。
但是,如果我们自己的module想要export一些东西让别的module使用呢。
很简单。
在default上,在你的module里所有的globalvariable和function都会被认定为你要export出去的。
所以,如果你的module里有10个globalvariable,经由ksyms,你可以发现这十个variable都会被export出去。
这当然是个很方便的事啦,但是,你知道,有时候我们根本不想把所有的variable都export出去,万一有个module没事乱改我们的variable怎幺办呢?
所以,在很多时候,我们都只会限定几个必要的东西export出去。
在2.2.1之前的kernel(不是很确定)可以利用register_symtab来帮我们。
但是,现在更新的版本早就出来了。
所以,在此,我会介绍kernel2.2.1里所提供的。
kernel2.2.1里提供了一个macro,叫做EXPORT_SYMBOL,这是用来帮我们选择要export的variable或function。
比方说,我要export一个叫full的variable,那我只要在module里写:
EXPORT_SYMBOL(full);
就会自动将fullexport出去,你马上就可以从ksyms里发现有full这个变量被export出去。
在使用EXPORT_SYMBOL之前,要小心一件事,就是必须在gcc里定义EXPORT_SYMTAB这个constant,否则在compile时会发生parsererror。
所以,要使用EXPORT_SYMBOL的话,那gcc应该要下:
gcc-c-D__KERNEL__-DMODULE-DMODVERSIONS-DEXPORT_SYMTAB\
main.c-include/usr/src/linux/include/linux/modversions.h
如果我们不想export任何的东西,那我们只要在module里下
EXPORT_NO_SYMBOLS;
就可以了。
使用EXPORT_NO_SYMBOLS用不着定义任何的constant。
其实,如果各位使用过旧版的register_symbol的话,一定会觉得新版的方式比较好用。
至少我是这样觉得啦。
因为使用register_symbol还要先定义出自己的symbol_table,感觉有点麻烦。
当我们使用EXPORT_SYMBOL把一些function或variableexport出来之后,我们使用ksyma-a去看一些结果。
我们发现EXPORT_SYMBOL(full)的确是把fullexport出来了:
c8822200full[my_module]
c01b8e08pci_find_slot_R454463b5
...
但是,结果怎么跟我们想象中的不太一样,照理说,应该是full_Rxxxxxx之类的东西才对啊,怎幺才出现full而已呢?
奇怪,问题在那里呢?
其实,问题就在于我们没有对本身的module所export出来的function或variable的名字做encode。
想想,如果在module的开头。
我们加入一行
#definefullfull_Rxxxxxx
之后,我们再重新compilemodule一次,载到kernel之后,就可以发现ksyms-a显示的是
c8822200full_Rxxxxxx[my_module]
c01b8e08pci_find_slot_R454463b5
.....
了。
那是不是说,我们要去对每一个export出来的variable和function做define的动作呢?
当然不是啰。
记得吗,前头我们讲去使用kernelexport的function时,由于include了一些.ver的档案,以致于我们不用再做define的动作。
现在,我们也要利用.ver的档案来帮我们,使我们moduleexport出来的function也可以自动加入kernelversion的information。
也就是变成full_Rxxxxxx之类的东西。
Linux里提供了一个command,叫genksyms,就是用来帮我们产生这种.ver的档案的。
它会从stdin里读取sourcecode,然后检查sourcecode里是否有export的variable或function。
如果有,它就会自动为每个export出来的东西产生一些define。
这些define就是我们之前说的。
等我们有了这些define之后,只要在我们的module里加入这些define,那export出来的function或variable就会变成上面那个样子。
假设我们的程序都放在一个叫main.c的档案里,我们可以使用下列的方式产生这些define。
gcc-E-D__GENKSYMS__main.c|genksyms-k2.2.1>main.ver
gcc的-E参数是指将preprocessing的结果show出来。
也就是说将它include的档案,一些define的结果都展开。
-D__GENKSYMS__是一定要的。
如果没有定义这个constant,你将不会看到任何的结果。
用一个管线是因为genksyms是从stdin读资料的,所以,经由管线将gcc的结果传给genksyms。
-k2.2.1是指目前使用的kernel版本是2.2.1,如果你的kernel版本不一样,必须指定你的kernel的版本。
产生的define将会被放到main.ver里。
产生完main.ver档之后,在main.c里将它include进来,那一切就OK了。
有件事要告诉各位的是,使用这个方式产生的module,其export出来的东西会经由main.ver的define改头换面。
所以如果你要让别人使用,那你必须将main.ver公开,不然,别人就没办法使用你export出来的东西了。
讲了这幺多,相信各位应该都已经比较清楚module在kernel中是怎幺样一回事,也应该知道为什幺有时候module会无法加载了。
除此之外,各位应该还知道如何使自己moduleexport出来的东西也具有kernelversion的information。
接下来,要跟各位讲的就是,如何写一个module了。
其实,写一个module很简单的。
如果你了解我上面所说的东西。
那我再讲一次,再用个例子,相信大家就都会了。
要写一个module,必须要提供两个function。
这两个function是给insmod和rmmod使用的。
它们分别是init_module(),以及cleanup_module()。
intinit_module();
voidcleanup_module();
相信大家都知道在Linux里可以使用insmod这个command来将某个module加载。
比方说,我有一个module叫hello.o,那使用insmodhello.o就可以将hello这个module载到kernel里。
观察/etc/modules应该就可以看到hello这个module的名字。
如果要将hello这个module移除,则只要使用rmmodhello就可以了。
insmod在加载module之后,就会去呼叫module所提供的init_module()。
如果传回0表示成功,那module就会被加载。
如果失败,那加载的动作就会失败。
一般来讲,我们在init_module()做的事都是一些初始化的工作。
比方说,你的module需要一块内存,那你就可以在init_module()做kmalloc的动作。
想当然尔。
cleanup_module()就是在module要移除的时候做的事。
做的事一般来讲就是一些善后的工作,比方像把之前kmalloc的内存free掉。
由于module是载到kernel使用的,所以,可能别的module会使用你的module,甚至某些process也会使用到你的module,为了避免module还有人使用时就被移除,每个module都有一个usecount。
用来记录目前有多少个process或module正在使用这个module。
当module的usecount不等于0时,module是不会被移除掉的。
也就是说,当module的usecount不等于0时,cleanup_module()是不会被呼叫的。
在此,我要介绍三个macro,是跟module的usecount有关的。
MOD_INC_USE_COUNT
MOD_DEC_USE_COUNT
MOD_IN_USE
MOD_INC_USE_COUNT是用来增加module的usecount,而MOD_DEC_USE_COUNT是用来减少module的usecount。
至于MOD_IN_USE则是用来检查目前这个module是不是被使用中,也就是检查usecount是否为0。
module的usecount必须由写module的人自己来maintain。
系统并不会自动为你把usecount加一或减一,一切都得由自己控制。
下面有一个例子,但是,并不会介绍这三个macro的使用方法。
将来如果有机会,我再来介绍这三个macro的用法。
这个例子很简单。
其实只是示范如何使用init_module()以及cleanup_module()来写一个module。
当然,这两个function只是构成module的基本条件罢了。
至于module里要提供的功能则是看各人的需要。
main.c
#defineMODULE
#include
#include
intfull;
EXPORT_SYMBOL(full);
intinit_module(void)
{
printk("<5>Moduleisloaded\n");
return0;
}
voidcleanup_module(void)
{
printk("<5>Moduleisunloaded\n");
}
关于printk是这样子的,它是kernel所提供的一个打印讯息的function。
kernel有export这个function,所以你可以自由的使用它。
它的用法跟printf几乎一模一样。
唯独讯息的开头是<5>,其实,不见得这三个字符啦。
也可以是<4>,<3>,<7>等等的东西。
这是代表这个讯息的prioirty或level。
<5>表示的是跟KERNEL有关的讯息。
main.ver:
利用genksyms产生出来的。
gcc-E-D__GENKSYMS__main.c|genksyms-k2.2.1>main.ver
接下来,就是要把main.ccompile成main.o
gcc-D__KERNEL__-DMODVERSIONS-DEXPORT_SYMTAB-c\
-I/usr/src/linux/include/linux-include\
/usr/src/linux/include/linux/modversions.h\
-include./main.vermain.c
好了。
main.o已经成功的compile出来了,现在下一个command,
insmodmain.o
检查看/proc/modules里是否有main这个module。
如果有,表示main这个module已经载到kernel了。
再下一个指令,看看fullexport出去的结果。
ksyms
结果显示
AddressSymbolDefinedby
c40220e0full_R355b84b2[main]
c401d04cne_probe[ne]
c401a04cei_open[8390]
c401a094ei_close[8390]
c401a504ei_interrupt[8390]
c401af1cethdev_init[8390]
c401af80NS8390_init[8390]
可以看到full_R355b84b2,表示我们已经成功的将f