MODULEPARAM.docx
《MODULEPARAM.docx》由会员分享,可在线阅读,更多相关《MODULEPARAM.docx(11页珍藏版)》请在冰豆网上搜索。
MODULEPARAM
.1.Hello,World(part1):
最简单的内核模块
当第一个洞穴程序员在第一台洞穴计算机的墙上上凿写第一个程序时,这是一个在羚羊皮上输出`Hello,world'的字符串。
罗马的编程书籍上是以`Salut,Mundi'这样的程序开始的。
我不明白人们为什么要破坏这个传统,但我认为还是不明白为好。
我们将从编写一系列的`Hello,world'模块开始,一步步展示编写内核模块的基础的方方面面。
这可能是一个最简单的模块了。
先别急着编译它。
我们将在下章模块编译的章节介绍相关内容。
----------------------------
Example2-1.hello-1.c
PLAINTEXT
1./*
2.*hello-1.c-Thesimplestkernelmodule.
3.*/
4.#include/*Neededbyallmodules*/
5.#include/*NeededforKERN_ALERT*/
6.
7.intinit_module(void)
8.{
9.printk("<1>Helloworld1.\n");
10.
11./*
12.*Anon0returnmeansinit_modulefailed;modulecan'tbeloaded.
13.*/
14.return0;
15.}
16.
17.voidcleanup_module(void)
18.{
19.printk(KERN_ALERT"Goodbyeworld1.\n");
20.}
----------------------------
一个内核模块应该至少包含两个函数。
一个“开始”(初始化)的函数被称为init_module()还有一个“结束”(干一些收尾清理的工作)的函数被称为cleanup_module(),当内核模块被rmmod卸载时被执行。
实际上,从内核版本2.3.13开始这种情况有些改变。
你可以为你的开始和结束函数起任意的名字。
你将在以后学习如何实现这一点Section2.3。
实际上,这个新方法时推荐的实现方法。
但是,许多人仍然使init_module()和cleanup_module()作为他们的开始和结束函数。
一般,init_module()要么向内核注册它可以处理的事物,要么用自己的代码替代某个内核函数(代码通常这样做然后再去调用原先的函数代码)。
函数cleanup_module()应该撤消任何init_module()做的事,从而内核模块可以被安全的卸载。
最后,任一个内核模块需要包含linux/module.h。
我们仅仅需要包含linux/kernel.h当需要使用printk()记录级别的宏扩展时KERN_ALERT,相关内容将在Section2.1.1中介绍。
--------------------------------------------------------------------------------
2.1.1.介绍printk()
不管你可能怎么想,printk()并不是设计用来同用户交互的,虽然我们在hello-1就是出于这样的目的使用它!
它实际上是为内核提供日志功能,记录内核信息或用来给出警告。
因此,每个printk()声明都会带一个优先级,就像你看到的<1>和KERN_ALERT那样。
内核总共定义了八个优先级的宏,所以你不必使用晦涩的数字代码,并且你可以从文件linux/kernel.h查看这些宏和它们的意义。
如果你不指明优先级,默认的优先级DEFAULT_MESSAGE_LOGLEVEL将被采用。
阅读一下这些优先级的宏。
头文件同时也描述了每个优先级的意义。
在实际中,使用宏而不要使用数字,就像<4>。
总是使用宏,就像KERN_WARNING。
当优先级低于intconsole_loglevel,信息将直接打印在你的终端上。
如果同时syslogd和klogd都在运行,信息也同时添加在文件/var/log/messages,而不管是否显示在控制台上与否。
我们使用像KERN_ALERT这样的高优先级,来确保printk()将信息输出到控制台而不是只是添加到日志文件中。
当你编写真正的实用的模块时,你应该针对可能遇到的情况使用合适的优先级。
--------------------------------------------------------------------------------
2.2.编译内核模块
内核模块在用gcc编译时需要使用特定的参数。
另外,一些宏同样需要定义。
这是因为在编译成可执行文件和内核模块时,内核头文件起的作用是不同的。
以往的内核版本需要我们去在Makefile中手动设置这些设定。
尽管这些Makefile是按目录分层次安排的,但是这其中有许多多余的重复并导致代码树大而难以维护。
幸运的是,一种称为kbuild的新方法被引入,现在外部的可加载内核模块的编译的方法已经同内核编译统一起来。
想了解更多的编译非内核代码树中的模块(就像我们将要编写的)请参考帮助文件linux/Documentation/kbuild/modules.txt。
现在让我们看一个编译名字叫做hello-1.c的模块的简单的Makefile文件:
Example2-2.一个基本的Makefile
----------------------------
obj-m+=hello-1.o
----------------------------
现在你可以通过执行命令make-C/usr/src/linux-`uname-r`SUBDIRS=$PWDmodules编译模块。
你应该得到同下面类似的屏幕输出:
----------------------------
[root@pcsenonsrvtest_module]#make-C/usr/src/linux-`uname-r`SUBDIRS=$PWDmodules
make:
Enteringdirectory`/usr/src/linux-2.6.x
CC[M]/root/test_module/hello-1.o
Buildingmodules,stage2.
MODPOST
CC/root/test_module/hello-1.mod.o
LD[M]/root/test_module/hello-1.ko
make:
Leavingdirectory`/usr/src/linux-2.6.x
----------------------------
请注意2.6的内核现在引入一种新的内核模块命名规范:
内核模块现在使用.ko的文件后缀(代替以往的.o后缀),这样内核模块就可以同普通的目标文件区别开。
更详细的文档请参考linux/Documentation/kbuild/makefiles.txt。
在研究Makefile之前请确认你已经参考了这些文档。
现在是使用insmod./hello-1.ko命令加载该模块的时候了(忽略任何你看到的关于内核污染的输出显示,我们将在以后介绍相关内容)。
所有已经被加载的内核模块都罗列在文件/proc/modules中。
cat一下这个文件看一下你的模块是否真的成为内核的一部分了。
如果是,祝贺你!
你现在已经是内核模块的作者了。
当你的新鲜劲过去后,使用命令rmmodhello-1.卸载模块。
再看一下/var/log/messages文件的内容是否有相关的日志内容。
这儿是另一个练习。
看到了在声明init_module()上的注释吗?
改变返回值非零,重新编译再加载,发生了什么?
--------------------------------------------------------------------------------
2.3.HelloWorld(part2)
在内核Linux2.4中,你可以为你的模块的“开始”和“结束”函数起任意的名字。
它们不再必须使用init_module()和cleanup_module()的名字。
这可以通过宏module_init()和module_exit()实现。
这些宏在头文件linux/init.h定义。
唯一需要注意的地方是函数必须在宏的使用前定义,否则会有编译错误。
下面就是一个例子。
----------------------------
Example2-3.hello-2.c
PLAINTEXT
1./*
2.*hello-2.c-Demonstratingthemodule_init()andmodule_exit()macros.
3.*Thisispreferredoverusinginit_module()andcleanup_module().
4.*/
5.#include/*Neededbyallmodules*/
6.#include/*NeededforKERN_ALERT*/
7.#include/*Neededforthemacros*/
8.
9.staticint__inithello_2_init(void)
10.{
11.printk(KERN_ALERT"Hello,world2\n");
12.return0;
13.}
14.
15.staticvoid__exithello_2_exit(void)
16.{
17.printk(KERN_ALERT"Goodbye,world2\n");
18.}
19.
20.module_init(hello_2_init);
21.module_exit(hello_2_exit);
----------------------------
现在我们已经写过两个真正的模块了。
添加编译另一个模块的选项十分简单,如下:
Example2-4.两个内核模块使用的Makefile
----------------------------
obj-m+=hello-1.o
obj-m+=hello-2.o
----------------------------
现在让我们来研究一下linux/drivers/char/Makefile这个实际中的例子。
就如同你看到的,一些被编译进内核(obj-y),但是这些obj-m哪里去了呢?
对于熟悉shell脚本的人这不难理解。
这些在Makefile中随处可见的obj-$(CONFIG_FOO)的指令将会在CONFIG_FOO被设置后扩展为你熟悉的obj-y或obj-m。
这其实就是你在使用makemenuconfig编译内核时生成的linux/.config中设置的东西。
--------------------------------------------------------------------------------
2.4.HelloWorld(part3):
关于__init和__exit宏
这里展示了内核2.2以后引入的一个新特性。
注意在负责“初始化”和“清理收尾”的函数定义处的变化。
宏__init的使用会在初始化完成后丢弃该函数并收回所占内存,如果该模块被编译进内核,而不是动态加载。
宏__initdata同__init类似,只不过对变量有效。
宏__exit将忽略“清理收尾”的函数如果该模块被编译进内核。
同宏__exit一样,对动态加载模块是无效的。
这很容易理解。
编译进内核的模块是没有清理收尾工作的,而动态加载的却需要自己完成这些工作。
这些宏在头文件linux/init.h定义,用来释放内核占用的内存。
当你在启动时看到这样的Freeingunusedkernelmemory:
236kfreed内核输出,上面的那些正是内核所释放的。
----------------------------
Example2-5.hello-3.c
PLAINTEXT
1./*
2.*hello-3.c-Illustratingthe__init,__initdataand__exitmacros.
3.*/
4.#include/*Neededbyallmodules*/
5.#include/*NeededforKERN_ALERT*/
6.#include/*Neededforthemacros*/
7.
8.staticinthello3_data__initdata=3;
9.
10.staticint__inithello_3_init(void)
11.{
12.printk(KERN_ALERT"Hello,world%d\n",hello3_data);
13.return0;
14.}
15.
16.staticvoid__exithello_3_exit(void)
17.{
18.printk(KERN_ALERT"Goodbye,world3\n");
19.}
20.
21.module_init(hello_3_init);
22.module_exit(hello_3_exit);
--------------------------------------------------------------------------------
2.5.HelloWorld(part4):
内核模块证书和内核模块文档说明
如果你在使用2.4或更新的内核,当你加载你的模块时,你也许注意到了这些输出信息:
#insmodhello-3.o
Warning:
loadinghello-3.owilltaintthekernel:
nolicense
Seehttp:
//www.tux.org/lkml/#export-taintedforinformationabouttaintedmodules
Hello,world3
Modulehello-3loaded,withwarnings
在2.4或更新的内核中,一种识别代码是否在GPL许可下发布的机制被引入,因此人们可以在使用非公开的源代码产品时得到警告。
这通过在下一章展示的宏MODULE_LICENSE()当你设置在GPL证书下发布你的代码时,你可以取消这些警告。
这种证书机制在头文件linux/module.h实现,同时还有一些相关文档信息。
/*
*Thefollowinglicenseidentsarecurrentlyacceptedasindicatingfree
*softwaremodules
*
*"GPL"[GNUPublicLicensev2orlater]
*"GPLv2"[GNUPublicLicensev2]
*"GPLandadditionalrights"[GNUPublicLicensev2rightsandmore]
*"DualBSD/GPL"[GNUPublicLicensev2
*orBSDlicensechoice]
*"DualMPL/GPL"[GNUPublicLicensev2
*orMozillalicensechoice]
*
*Thefollowingotheridentsareavailable
*
*"Proprietary"[Nonfreeproducts]
*
*Thereareduallicensedcomponents,butwhenrunningwithLinuxitisthe
*GPLthatisrelevantsothisisanonissue.SimilarlyLGPLlinkedwithGPL
*isaGPLcombinedwork.
*
*Thisexistsforseveralreasons
*1.Somodinfocanshowlicenseinfoforuserswantingtovettheirsetup
*isfree
*2.Sothecommunitycanignorebugreportsincludingproprietarymodules
*3.Sovendorscandolikewisebasedontheirownpolicies
*/
类似的,宏MODULE_DESCRIPTION()用来描述模块的用途。
宏MODULE_AUTHOR()用来声明模块的作者。
宏MODULE_SUPPORTED_DEVICE()声明模块支持的设备。
这些宏都在头文件linux/module.h定义,并且内核本身并不使用这些宏。
它们只是用来提供识别信息,可用工具程序像objdump查看。
作为一个练习,使用grep从目录linux/drivers看一看这些模块的作者是如何为他们的模块提供识别信息和档案的。
----------------------------
Example2-6.hello-4.c
PLAINTEXT
1./*
2.*hello-4.c-Demonstratesmoduledocumentation.
3.*/
4.#include
5.#include
6.#include
7.#defineDRIVER_AUTHOR"PeterJaySalzman
"
8.#defineDRIVER_DESC"Asampledriver"
9.
10.staticint__initinit_hello_4(void)
11.{
12.printk(KERN_ALERT"Hello,world4\n");
13.return0;
14.}
15.
16.staticvoid__exitcleanup_hello_4(void)
17.{
18.printk(KERN_ALERT"Goodbye,world4\n");
19.}
20.
21.module_init(init_hello_4);
22.module_exit(cleanup_hello_4);
23.
24./*
25.*Youcanusestrings,likethis:
26.*/
27.
28./*
29.*GetridoftaintmessagebydeclaringcodeasGPL.
30.*/
31.MODULE_LICENSE("GPL");
32.
33./*
34.*Orwithdefines,likethis:
35.*/
36.MODULE_AUTHOR(DRIVER_AUTHOR);/*Whowrotethismodule?
*/
37.MODULE_DESCRIPTION(DRIVER_DESC);/*Whatdoesthismoduledo*/
38.
39./*
40.*Thismoduleuses/dev/testdevice.TheMODULE_SUPPORTED_DEVICEmacromight
41.*beusedinthefuturetohelpautomaticconfigurationofmodules,butis
42.*currentlyunusedotherthanfordocumentationpurposes.
43.*/
44.MODULE_SUPPORTED_DEVICE("testdevice");
--------------------------------------------------------------------------------
2.6.从命令行传递参数给内核模块
模块也可以从命令行获取参数。
但不是通过以前你习惯的argc/argv。
要传递参数给模块,首先将获取参数值的变量声明为全局变量。
然后使用宏MODULE_PARM()(在头文件linux/module.h)。
运行时,insmod将给变量赋予命令行的参数,如同./insmodmymodule.omyvariable=5。
为使代码清晰,变量的声明和宏都应该放在模块代码的开始部分。
以下的代码范例也许将比我公认差劲的解说更好。
宏MODULE_PARM()需要两个参数,变量的名字和其类型。
支持的类型有"b":
比特型,"h":
短整型,"i":
整数型,"l:
长整型和"s":
字符串型,其中正数型既可为signed也可为unsigned。
字符串类型应该声明为"char*"这样insmod就可以为它们分配内存空间。
你应该总是为你的变量赋初值。
这是内核编程,代码要编写的十分谨慎。
举个例子:
intmyint=3;
char*mystr;
MODULE_PARM(myint,"i");
MODULE_PARM(mystr,"s");
数组同样被支持。
在宏MODULE_PARM中在类型符号前面的整型值意味着一个指定了最大长度的数组。
用'-'隔开的两个数字则分别意味着最小和最大长度。
下面的例子中,就声明了一个最小长度为2,最大长度为4的整形数组。
intmyshortArray[4];
MODULE_PARM(myintArray,"3-9i");
将初始值设为缺省使用的IO端口或IO内存是一个不错的作法。
如果这些变量有缺省值,则可以进行自动设备检测,否则保持当前设置的值。
我们将在后续章节解释清楚相关内容。
在这里我只是演示如何向一个模块传递参数。
最后,还有这样一个宏,MODULE_PARM_DESC()被用来注解该模块可以接收的参数。
该宏两个参数:
变量名和一个格式自由的对该变量的描述。
----------------------------
Example2-7.hello-5.c
PLAINTEXT
1./*
2.*hello-5.c-Demonstratescommandlineargumentpassingtoamodule.
3.*/
4.#include
5.#include
6.#include
7.#include
8.#include
9.
10.MODULE_LIC