模块
来源:互联网 发布:陈洁仪左右手 知乎 编辑:程序博客网 时间:2024/05/02 19:33
Linux是单块内核(monolithic)的操作系统,整个系统内核都运行与一个单独的包含域中。Linux内核是模块化组成的,它允许内核在运行时动态地向其中插入或从中删除代码。这些代码包括相关的子例程,数据,函数入口和函数出口,被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,或简称为模块。
支持模块的好处就是基本内核镜像可以尽可能的小,因为可选的功能和驱动程序可以利用模块形式再提供。模块允许我们方便的删除和重新载入内核代码,也方便了调试工作。而且当热插拔新设备时,可通过命令载入新的驱动程序。
- Hello world!
- /*
- *hello.c--hello world 简单内核模块程序
- */
- #include<linux/init.h>
- #include<linux/module.h>
- #include<linux/kernel.h>
- static int hello_init()
- {
- printk(KERN_ALERT"I bear a charmed life./n");
- return 0;
- }
- static void hello_exit(void)
- {
- printk(KERN_ALERT"out,out,brief candle!/n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Shakespeare");
与大多数内核子系统不同,模块开发更接近编写新的应用系统,因为至少要在模块文件中具有入口点和出口点。
模块的初始化函数(入口点)必须符合下面的形式:
- int my_init(void); //如果初始化顺利完成,返回0,否则返回非零
因为init函数通常不会被外部函数直接调用,所以不必导出该函数,它可以被标记为static类型。
模块的初始化函数通过module_init()例程注册到系统中,在模块装载时被调用。调用module_init()实际上是一个宏调用,它惟一的参数是模块的初始化函数。
模块的出口函数偶module_exit()例程注册到系统。在模块从内存卸载时,内核便会调用模块的出口函数。该函数可能
在返回前负责清理资源,以保证硬件处于一致状态,或者其他别的操作。在退出函数返回后,模块就被卸载了。退出函数的形式如下:
- void my_exit(void);
如果上述文件被静态地编译到内核映像中,那么退出函数将不被包含,而且用于都不会被调用(如果不是编译成模块的话,代码就永远不需要从内核中卸载)。
MODULE_LICENSE()宏用于指定模块的版权。如果载入非GPL模块到系统内存,则会在内核中设置被污染标志,这个标志起到记录信息的作用。不过如果开发者提交的bug报告中包含有被污染的标志,那么报告的信用会被减低。非GPL模块不能调用GPL-only符号。
MODULE_AUTHOR()宏指定了代码作者,它完全是用于信息记录。
- 构建模块
在2.6内核中,采用了新的“kbuild”构建系统,构建的第一步是决定在哪里管理模块源码。可以把模块源码加入到内核源代码树中,或者作为一个补丁,或者最终把代码把代码最终合并到正式的内核代码树中;另一种可行的方式是在内核源代码树之外维护和构建模块源码。
1. 放在内核源码树中
最理想的情况是你的模块正式称为Linux内核的一部分,这样就会被存放在内核源代码树中。
设备驱动程序存放在内核源码树根目录deriver/的子目录下,在其内部设备驱动文件被进一步按照类别、类型和特殊驱动程序得呢个更有序的组织起来。字符设备存在与drivers/char/目录下,块设备存放在drivers/block/目录下,USB设备则存放在drivers/usb/目录下。
假设我们的驱动程序名为Foo XL,如果在目录drivers/char/下建立了自己的代码子目录foo,要做的是向drivers/char/目录中的Makefile文件添加代码,不同的代码产生不同的编译效果:
- obj-m += foo/ //这行编译指令告诉模块构建系统在编译模块时需要进入foo/目录中
- obj-$(CONFIG_FOO) += foo/ //如果驱动程序的编译取决于一个特殊的配置选项,比如CONFIG_FISHING_POLE,这时要用这条编译指令
- /*驱动程序智能化,可以自动检测其他文件,如foo-xxx.c,这样foo-main.c和foo-xxx.c就一起被编译和连接到foo.ko模块内*/
- obj-$(CONFIG_FOO) += foo.o
- foo-objs :=foo-main.o foo-xxx.o
- //注意,如果需要在构建文件时需要额外的编译标记,添加如下指令
- EXTRA_CFLAGS += -DTITANIUM_POLE
如果把源文件置于drivers/char/目录下,不建立新目录,就将原来处于drivers/char/foo/下你写的makefile中的代码都加入到drivers/char/下的Makefile中。
编译,运行内核构建过程。如果模块编译取决于配置选项,比如有CONFIG_FOO约束,那么在编译前首先要确保选项被允许。
2. 放在内核代码外
如要要脱离内核源代码以此来维护和构建自己的模块,要做的就是在自己的源代码目录中建立一个Makefile文件,它只需要一行指令:
- obj-m := foo.o
就可以把foo.c编译成foo.ko。如果有多个源文件,那么就用下列两行 :
- obj-m := foo.o
- foo-objs := foo-main.o foo-xxx.o
这样foo-main.c和foo-xxx.c就一起被编译和连接到foo.ko模块内。
模块在内核内或内核外构建的最大区别在于构建过程,当模块在内核源码外围时,要告诉make如何找到内核源码文件和基础Makefile文件:
- /* /kernel source location是已配置的内核源代码树。
- *不要把要处理的内核源代码树放在/usr/src/linux下,而要移到你的home目录下的某个方便访问的地方
- */
- make -c /kernel source location SUBDIRS-$PWD modules
- 安装模块
编译后的模块将被装入到目录/lib/modules/version/kernel/下。比如,使用2.6.10版本内核,模块的源代码直接放在drivers/char/下,编译后的驱动程序的存放路径是:
/lib/modules/2.6.10/kernel/drivers/char/foo.ko
- //下面的构建命令用来安装编译的模块到合适的目录下,要以root权限运行
- make modules_install
- 产生模块依赖性
Linux模块之间存在依赖性。A模块依赖于B模块,那么当载入A模块时,B模块也会被自动载入。这里需要的依赖信息必须事先生成。若想产生内核依赖关系的信息,root用户可以运行命令depmod
为了执行更快的更新操作,可以只为新模块生成依赖信息,而不是生成所有的依赖关系,这时root用户可以运行命令:
depmod -A
模块依赖关系信息存放在/lib/modules/version/modules.dep文件中。
- 载入模块
载入模块最简单的方法是通过insmod命令。这是个功能很有限的命令,它的作用是请求内核载入指定的模块。insmod程序不执行任何依赖性分析或进一步的错误检查。以root身份运行命令:
insmod foo
卸载一个模块:
rmmod foo
系统提供了更先进的工具modprobe,它提供了模块依赖性分析,错误智能检查,错误报告以及其他功能和选项:
modprobe modulename [module parameter]
参数将在模块加载时传入内核。
modprobe命令不但会加载指定的模块,还会自动加载任何它所依赖的有关模块。它还可以用来从内核中卸载模块,以root身份运行:
modprobe -r modulename
与rmmod命令不同,modprobe也会卸载给定模块所依赖的相关模块。其前提是这些相关模块没有被使用。
- 管理配置选项
前面设置了CONFIG_FOO配置选项,foo模块将被自动编译,若要添加一个新的配置选项还可以怎么做呢?
向Kconfig文件中添加一项,用以对应内核源码树。
对驱动程序而言,Kconfig一般与源代码处于同一目录,比如,foo程序在目录drivers/char/下,那么就存在drivers/char/Kconfig。如果新建了子目录存放foo,而且系统Kconfit文件存在于该目录,那么必须在一个已经存在的Kconfig文件(即drivers/char/Kconfig)中将它引入:
source "drivers/char/foo/Kconfig"
我们以Foo XL模块添加选项为例,在Kconfig文件中加入一个配置选项如下:
- config FOO
- tristate "Foo XL support"
- default n
- help
- If you say Y here, support for the Foo XL with computer interface will
- be complied into the kernel and accessible via device node. You can also
- say N here and the driver will be built as a module named foo.ko.
- If unsure, say N.
配置选项第1行定义了该选项所代表的配置目标。注意CONFIG_前缀不需要写上。
第2行声明选项类型为tristate,也就是说可以被编译进内核(Y),也可作为模块编译(M),或者干脆不编译它(N)。如果编译选项代表的是一个系统功能,而不是一个模块,那么编译选项将用bool指令代替tristate,这说明它不允许被编译成模块。处于指令之后的引号内的文字为该选项指定的名称。
第3行指定了该选项的默认选择,这里默认为不编译它。
第4行help指令的目的是为该选项提供帮助文档。
除了上述选项,还有其他选项。
depends指令规定了在该选项被设置其,首先要设置的选项。假如依赖性不满足,那么该选项被禁止。比如:
- depends on FOO_DEPEND
加入到配置选项中,那么就意味着CONFIG_FOO_DEPEND被选择前,Foo XL模块是不能被使用的。
select指令与depends指令类似,不同之处在于,select指定了谁,它就会强行将被指定的选项打开。不能滥用该指令,因为它会自动的激活其他配置选项。比如:
- select BIT
意味着当CONFIG_FOO被激活时,配置选项CONFIG_BIT必然一起被激活。
如果select和depends同时指定多个选项,需要通过&&指令来进行多选。使用depends还可以利用叹号!前缀来指明禁止某个选项。比如:
- depends on DUMB_DREVERS && !NO_FOO_ALLOWED
bool选项和tristate往往会结合if指令一起使用,表示某个选项取决于另一个配置选项。如果条件不满足,配置选项就会被禁止,甚至不会显示在配置工具中。比如:
- bool “Deep sea mode”if OCEAN
意味着"Deep sea mode"只有在CONFIG_OCEAN选项打开时才可见且有效。
If指令也可以与defualt指令结合使用,强制只有在条件满足时default选项才有效。
配置系统导出了一些元选项(meta-option)以简化生成的配置文件。
CONFIG_EMBEDDED是用于关闭那些用户想要禁止的关键功能。
CONFIG_BROKEN_ON_SMP用来表示驱动程序并非多处理器安全的。
CONFIG_EXPERIMENTAL用于说明某些功能尚在试验或处于beta版本阶段。
- 模块参数
Linux语序驱动程序声明参数,从而用户可以在系统启动或者模块装载时再指定参数值,这些参数对驱动程序来说属于全局变量。模块参数同时也将出现在sysfs文件系统中,这样一来,无论是生成模块参数,还是管理模块参数的方式都变得灵活多样了。
定义一个模块参数可以通过宏module_param()完成:
- 在include/linux/moduleparam.h中
- /*第一个参数是用户可见参数名,也是模块中存放模块参数的变量名。
- *第二个参数存放了参数的类型
- *第三个参数制定了模块在sysfs文件系统下对应文件的权限,该值可以是八进制格式或是S_Ifoo的定义形式,比如S_IRUGO|S_IWUSR,如果是0则表示禁止所有的sysfs
- */
- #define module_param(name, type, perm) /
- module_param_named(name, name, type, perm)
- /* Helper functions: type is byte, short, ushort, int, uint, long,
- ulong, charp, bool or invbool, or XXX if you define param_get_XXX,
- param_set_XXX and param_check_XXX. */ name是外部可见参数名,value是参数对应的内部全局变量名称
- #define module_param_named(name, value, type, perm) /
- param_check_##type(name, &(value)); /
- module_param_call(name, param_set_##type, param_get_##type, &value, perm); /
- __MODULE_PARM_TYPE(name, #type)
宏module_param()并没有定义变量,必须在使用该宏前进行变量定义。例如:
- static int allow_live_bait = 1;
- module_param(allow_live_bait,bool,0644);
通常需要用一个charp类型来定义模块参数(一个字符串),内核将用户提供的这个字符串拷贝到内存,而且将你的变量指向该字符串。比如:
- static char *name;
- module_param(name,charp,0);
有可能模块的外部参数名称与对应的内部参数名称,这时使用宏module_param_named()。比如:
- static unsigned int max_test = DEFAULT_MAX_LINE_TEST;
- module_para_named(maximum_line_test,max_test,int,0);
其他相关的宏:
- /* Actually copy string: maxlen param is usually sizeof(string). */
- #define module_param_string(name, string, len, perm) /
- static struct kparam_string __param_string_##name /
- = { len, string }; /
- module_param_call(name, param_set_copystring, param_get_string, /
- &__param_string_##name, perm); /
- __MODULE_PARM_TYPE(name, "string")
- /* Comma-separated array: *nump is set to number they actually specified. */
- #define module_param_array_named(name, array, type, nump, perm) /
- static struct kparam_array __param_arr_##name /
- = { ARRAY_SIZE(array), nump, param_set_##type, param_get_##type,/
- sizeof(array[0]), array }; /
- module_param_call(name, param_array_set, param_array_get, /
- &__param_arr_##name, perm); /
- __MODULE_PARM_TYPE(name, "array of " #type)
- #define module_param_array(name, type, nump, perm) /
- module_param_array_named(name, name, type, nump, perm)
- 导出符号表
模块被载入后,就会动态连接到内核。注意,它与用户空间中的动态连接库类型,只有当被显示导出后的外部函数,才可以被动态库调用。在内核中,导出内核函数需要使用特殊的指令:EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。
导出的内核函数可以被模块调用,而为导出的函数模块则无法被调用。模块代码的链接和调用规则相比核心内核镜像中的代码而言,更加严格。核心代码在内核中可以调用任意非静态接口,因为所有的核心源码文件都被链接成同一个镜像。当然,被导出的符号表所包含的函数必须是非静态的。
导出的内核符号表被看做是导出的内核接口,甚至称为API。
导出符号相对简单,在声明函数后,紧接着EXPORT_SYMBOL()指令就可以了。该函数如果定义在一个可访问的头文件中,那么任何模块现在都可以访问它。
如果系统自己的接口仅仅对GPL兼容的模块科技,那么内核连接器使用MODULE_LICENCE()宏满足这个要求,如果系统函数仅仅对标记为GPL协议的模块可见,那么就要用EXPORT_SYMBOL_GPL()指令。
如果你的代码被配置为模块,那么就必须确保当它被编译为模块时锁用的全部接口都已经被导出,否则会产生链接错误,模块不能成功编译。
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- 模块
- ASP.NET中遍历所有TextBox服务器控件
- Java SE 下使用Timer和TimerTask进行定时服务,时间的格式化
- C#日记6-- 制作安装包中添加数据库
- Spring事务原理
- DevExpress学习之ASPxGridView
- 模块
- Hibernate和JPA使用连接表处理多对一映射
- wxWidget链接库列表
- 指针,引用,数组
- Ant全攻略
- 在netbeans6.5中安装配置appfuse项目
- 开源测试工具的完整解决方案
- 一个简单的字符编码格式过滤器
- C#在WinForm中关于openfiledialog选定的数据插入到DataGridView中的代码