Linux内核Module.docx

上传人:b****5 文档编号:4398605 上传时间:2022-12-01 格式:DOCX 页数:18 大小:30.50KB
下载 相关 举报
Linux内核Module.docx_第1页
第1页 / 共18页
Linux内核Module.docx_第2页
第2页 / 共18页
Linux内核Module.docx_第3页
第3页 / 共18页
Linux内核Module.docx_第4页
第4页 / 共18页
Linux内核Module.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

Linux内核Module.docx

《Linux内核Module.docx》由会员分享,可在线阅读,更多相关《Linux内核Module.docx(18页珍藏版)》请在冰豆网上搜索。

Linux内核Module.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 机械仪表

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1