什么是module以及如何写一个module

发布时间:   来源:文档文库   
字号:
modulemodule[1]
不知道在什幺时候,Linux出现了module这种东西,的确,它是Linux的一大革新。有了module之后,写devicedriver不再是一项恶梦,修改kernel不再是一件痛苦的事了。因为你不需要每次要测试driver就重新compilekernel一次。那简直是会累死人。Module可以允许我们动态的改变kernel加载devicedriver,而且它也能缩短我们driverdevelopment的时间。在这篇文章里,我将要跟各位介绍一下module的原理,以及如何写一个module
module翻译成中文就是模块,不过,事实上去翻译这个字一点都没意义。在讲模块之前,我先举一个例子。相信很多人都用过RedHat。在RedHat里,我们可以执行sndconfig它可以帮我们config声卡。config完之后如果捉得到你的声卡,那你的声卡马上就可以动了,而且还不用重新激活计算机。这是怎幺做的呢?就是靠modulemodule其实是一般的程序。但是它可以被动态载到kernel里成为kernel的一部分。载到kernel里的module它具有跟kernel样的权力。可以access任何kerneldatastructure。你听过kdebug?是用来debugkernel的。它就是先将它本身的一个module载到kernel里,而userspacegdb就可以经由跟这个module沟通,得知kernel里的datastructure的值,除此之外,还可以经由载到kernelmodule去更改kerneldatastructure
我们知道,在写C程序的时候,一个程序只能有一个mainKernel本身其实也是一个程序它本身也有个mainstart_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后再使用其提供的functionvariable。每一个module都可以export一些function或变量来让别人使用。除此之外,module也可以使用已经载到kernel里的module提供的function这种情形叫做modulestack比方说,moduleAmoduleB的东西,那在加载moduleA之前必须要先加载moduleB。否则moduleA会无法加载。除了moduleexport东西之外,kernel本身也会export一些functionvariable同样的,module也可以使用kernelexport出来的东西。由于大家平时都是撰写userspace程序,所以,当突然去写module的时候,会把平时写程序用的function拿到module里使用。像是printf之类的东西。我要告诉各位的是,module所使用的functionvariable,要嘛就是自己写在module里,要嘛就是别的module提供的,再不就是kernel提供的。你不能使用一般libcglibc所提供的function。像printf之类的东西。这一点可能是各位要多小心的地方。(也许你可以先link好,再载到kernel我好象试过,但是忘了
刚才我们说到kernel本身会export出一些functionvariable来让module使用,但是,我们不是万能的,我们怎幺知道kernel有开放那里东西让我们使用呢?Linux提供一个command,叫ksyms,你只要执行ksyms-a可以知道kernel或目前载到kernel里的module提供了那些function

variable。底下是我的系统的情形:c0216ba0drive_info_R744aa133c01e4a44boot_cpu_data_R660bd466c01e4ac0EISA_bus_R7413793ac01e4ac4MCA_bus_Rf48a2c4cc010cc34__verify_write_R203afbeb.....
kernel里,有一个symboltable是用来记录export出去的functionvariable除此之外,也会记录着那个moduleexport那些function上面几行中,表示kernel提供了drive_info这个function/variable所以,我们可以在kernel里直接使用它,等载到kernel里时,会自动做好link的动作。由此,我们可以知道,module本身其实是还没做link的一些objectcode。一切都要等到module被加载kernel之后,link才会完成。各位应该可以看到drive_info面还接着一些奇怪的字符串。_R744aa133,这个字符串是根据目前kernel的版本再做些encode得出来的结果。为什幺额外需要这一个字符串呢?Linux不知道从那个版本以来,就多了一个config的选项,叫做Setversionnumberinsymbolsofmodule这是为了避免对系统造成不稳定。我们知道Linuxkernel更新的很快。kernel更新的过程,有时为了效率起见,会对某些旧有的datastructurefunction做些改变,而且一变可能有的variable被拿掉,有的functionprototype跟原来的都不太一样。如果这种情形发生的时候,那可能以前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到各个exportedfunctionvariable里。
所以,刚才也许我们不应该讲kernel提供了drive_info,而应该说kernel提供driver_info_R744aa133来让我们使用。这样也许各位会比较明白。也就是说,kernel认为它提供的driver_info_R744aa133这个东西,而不是driver_info。所以,我们可以发现有的人在加载module时,系统都一直告诉你某个functionresolved。这就是因为kernel里没有你要的function,要不然就是你的module里使用的functionkernelencode的结果不一样。所以无法resolve解决方式,要嘛就是将kernel里的setversion选项关掉,要嘛就是将modulecompilekernel有办法接受的型式。
kernelfunctiondriver_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(printkset_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个档在moduleinclude进来,如果真的要,那也要加上以下数行:#ifdefMODVERSIONS#include#endif
加入这三行的原因是,避免这个module在没有设定kernelversion系统上,将modversions.h这个档案include进来。各位可以去试试看,当你把setversion的选项关掉时,modversions.hmodules这个目录都会不见。如果没有上面三行,那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我刚才忘了讲。刚才我们说要解决fucntionvariable名字encode的方式就是要includemodversions.h,其实除此之外,你还必须定义MODVERSIONS这个constant。再来就是MODULE这个constant。其实,只要是你要写module就一定要定义这个变量。而且你还要includemodule.h这个档案,因为_set_ver就是定义在这里的。
讲到这里,相信各位应该对module有一些认识了,以后遇到moduleunresolved应该不会感到困惑了,应该也有办法解决了。
刚才讲的都是使用别人的function上遇到的名字encode问题。但是,如果我们自己的module想要export一些东西让别的module使用呢。很简单。default上,在你的module里所有的globalvariablefunction都会被认定为你要export出去的。所以,如果你的module里有10globalvariableksyms,你可以发现这十个variable都会被export出去。这当然是个很方便的事啦,但是,你知道,有时候我们根本不想把所有的variableexport出去,万一有个module没事乱改我们的variable怎幺办呢?所以,在很多时候,我们都只会限定几个必要的东西export出去。在2.2.1之前的kernel(不是很确可以利用register_symtab来帮我们。但是,现在更新的版本早就出来了。

所以,在此,我会介绍kernel2.2.1里所提供的。kernel2.2.1里提供了一个macro叫做EXPORT_SYMBOL这是用来帮我们选择要exportvariablefunction比方说,我要export一个叫fullvariable那我只要在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_SYMTABmain.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把一些functionvariableexport出来之后,我们使用ksyma-a去看一些结果。我们发现EXPORT_SYMBOL(full确是把fullexport出来了:c8822200full[my_module]
c01b8e08pci_find_slot_R454463b5...但是,结果怎幺跟我们想象中的不太一样,照理说,应该是full_Rxxxxxx类的东西才对啊,怎幺才出现full而已呢?奇怪,问题在那里呢?其实,问题就在于我们没有对本身的moduleexport出来的functionvariable的名字做encode。想想,如果在module的开头。我们加入一行#definefullfull_Rxxxxxx
之后,我们再重新compilemodule一次,载到kernel之后,就可以发现ksyms-a显示的是
c8822200full_Rxxxxxx[my_module]c01b8e08pci_find_slot_R454463b5.....了。那是不是说,我们要去对每一个export出来的variablefunctiondefine的动作呢?当然不是啰。记得吗,前头我们讲去使用kernelexportfunction时,由于include了一些.ver的档案,以致于我们不用再做define动作。现在,我们也要利用.ver的档案来帮我们,使我们moduleexport出来functionkernelversioninformationfull_Rxxxxxx之类的东西。
Linux里提供了一个commandgenksyms就是用来帮我们产生这种.ver档案的。它会从stdin里读取sourcecode,然后检查sourcecode里是否有exportvariablefunction。如果有,它就会自动为每个export出来的东西产生一些define这些define就是我们之前说的。等我们有了这些define之后,

只要在我们的module里加入这些define,那export出来的functionvariable就会变成上面那个样子。
假设我们的程序都放在一个叫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.verdefine改头换面。所以如果你要让别人使用,那你必须将main.ver公开,不然,别人就没办法使用你export出来的东西了。
讲了这幺多,相信各位应该都已经比较清楚modulekernel中是怎幺样一回事,也应该知道为什幺有时候module会无法加载了。除此之外,各位应该使moduleexport西kernelversioninformation接下来,要跟各位讲的就是,如何写一个module了。其实,写一个module很简单的。如果你了解我上面所说的东西。那我再讲一次,再用个例子,相信大家就都会了。要写一个module,必须要提供两个function。这两个functioninsmodrmmod使init_module(cleanup_module(intinit_module(;
voidcleanup_module(;
相信大家都知道在Linux里可以使用insmod这个command来将某个module加载。比方说,我有一个modulehello.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。用来记录目前有多少个processmodule正在使用这个module。当moduleusecount不等于0时,module是不会被移除掉的。也就是说,当moduleusecount不等于0时,cleanup_module(是不会被呼叫的。
在此,我要介绍三个macro,是跟moduleusecount有关的。

MOD_INC_USE_COUNTMOD_DEC_USE_COUNTMOD_IN_USE
MOD_INC_USE_COUNTmoduleusecountMOD_DEC_USE_COUNTmoduleusecountMOD_IN_USE则是用来检查目前这个module是不是被使用中。也就是检查usecount是否为0moduleusecount必须由写module的人自己来maintain系统并不会自动为你把usecount加一或减一。一切都得由自己控制。下面有一个例子,但是,并不会介绍这三个macro的使用方法。将来如果有机会,我再来介绍这三个macro的用法。
使init_module(cleanup_module(来写一个module当然,这两个function只是构成module基本条件罢了。至于module里要提供的功能则是看各人的需要。main.c
#defineMODULE#include#includeintfull;
EXPORT_SYMBOL(full;/*fullexport出去*/intinit_module(void{
printk("<5>Moduleisloaded\n";return0;}
voidcleanup_module(void{
printk("<5>Moduleisunloaded\n";}
关于printk是这样子的,它是kernel所提供的一个打印讯息的functionkernelexport这个function所以你可以自由的使用它。它的用法跟printf乎一模一样。唯独讯息的开头是<5>,其实,不见得这三个字符啦。也可以是<4><3><7>等等的东西。这是代表这个讯息的prioirtylevel<5>表示的是跟KERNEL有关的讯息。main.ver:
利用genksyms产生出来的。
gcc-E-D__GENKSYMS__main.c|genksyms-k2.2.1>main.ver接下来,就是要把main.ccompilemain.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出来了,现在下一个commandinsmodmain.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表示,我们已经成功的将full的名字加上kernelversioninformation了。当我们不需要这个module时,我们就可以下一个command
rmmodmain
这样main就会被移除掉了。再检查看看/proc/modules就可以发现main那一行不见了。各位现在可以看一下/var/log/message这个档案,应该可以发现以两行
Apr1214:19:05hostkernel:ModuleisloadedApr1214:39:29hostkernel:Moduleisunloaded这两行就是printk印出来的。
关于module的介绍已经到此告一段落了。其实,使用module实在是很简单的一件事。对于要发展driver或是增加kernel某些新功能的人来讲,用module不啻为一个方便的方式。希望这篇文章对各位能有所帮助。

本文来源:https://www.2haoxitong.net/k/doc/8192da3f0912a216147929f2.html

《什么是module以及如何写一个module.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式