先前一直在外地有事情,耽搁了一些时间。希望能够赶上。一个简单的LDDHELLO#include<linuxmodule.h>#include<linuxinit.
先前一直在外地有事情,耽搁了一些时间。希望能够赶上。
一个简单的LDD HELLO
以上是一个简单的LDD例子,其中
Hello_init()函数是当驱动加载的时候调用的函数
Hello_out()函数是当驱动卸载的时候调用的函数
__init __exit 表明该函数分别用于初始化和退出模块的函数。
linux有内核空间和用户空间之分,printf()用于用户空间,而printk则用于kernel空间
用printk,内核会根据日志级别,可能把消息打印到当前控制台上,这个控制台通常是一个字符模式的终端、一个串口打印机或是一个并口打印机。这些消息正常输出的前提是──日志输出级别小于console_loglevel(在内核中数字越小优先级越高)。
没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到
日志级别一共有8个级别,printk的日志级别定义如下(在include/linux/kernel.h中):
#define KERN_EMERG 0/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
#define KERN_ALERT 1/*报告消息,表示必须立即采取措施*/
#define KERN_CRIT 2/*临界条件,通常涉及严重的硬件或软件操作失败*/
#define KERN_ERR 3/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
#define KERN_WARNING 4/*警告条件,对可能出现问题的情况进行警告*/
#define KERN_NOTICE 5/*正常但又重要的条件,用于提醒*/
#define KERN_INFO 6/*提示信息,如驱动程序启动时,打印硬件信息*/
#define KERN_DEBUG 7/*调试级别的消息*/
MODULE_LICENSE("GPL")用于给驱动模块加入许可证
GPL GNU通用许可证
GPL V2 vertion2
GPL and additional rights GPL和附加权力
Dual BSD/GPL 兼具BSD/GPL
Dual MPL/GPL 兼具MPL/GPL
Proprietary 专有
MODULE_AUTHOR () 描述模块作者
MODULE_DESCRIPTION() 模块描述
MODULE_VERSION()模块版本号
MODULE_DEVICE_TABLE() 支持设备列表
MODULE_ALIAS 别名
makefile编写,写makefile让我有点云里雾里的感觉
通过make后生成hello.ko文件,用insmod hello.ko加载驱动模块,也可以使用modeprobe加载模块
modprobe命令比insmod命令要强大,它在加载某模块时会同时加载该模块所依赖的其他模块
使用modprobe -r filename的方式卸载将同时其依赖的模块
在虚拟终端是无法看到相关信息的.键入dmesg命令后,可以观察到相关的printk打印的信息如下所示:
利用命令rmmod hello.ko可以卸载该模块。
带模块参数的写法:
参数不能像用户空间那样写在init函数中,需要通过module_param()来注册参数,如上述代码所示。关于模块参数,在别人的blog中找了些资料,摘录如下:
http://www.360doc.com/content/09/0428/11/36491_3298911.shtml
、模块中的参数
(内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变[color=#000000]。 example : insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1[/color])
[color=#000000]对于如何向模块传递参数,Linux kernel 提供了一个简单的框架。其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。这些模块参数也能够在sysfs中显示出来。结果,有许许多多的方法用来创建和管理模块参数。
通过宏module_param()定义一个模块参数:
module_param(name, type, perm);
这里,name既是用户看到的参数名,又是模块内接受参数的变量; type表示参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool。这些类型分别是:a byte, a short integer, an unsigned short integer, an integer, an unsigned integer, a long integer, an unsigned long integer, a pointer to a char, a Boolean, a Boolean whose value is inverted from what the user specifies. The byte type is stored in a single char and the Boolean types are stored in variables of type int. The rest are stored in the corresponding primitive C types. 最后,perm指定了在sysfs中相应文件的访问权限。访问权限用通常的八进制格式来表示,例如,用0644(表示ower具有读写权限,group和everyone只读权限), 或者用通常的S_Ifoo定义,例如,S_IRUGO | S_IWUSR (表示everyone具有读权限,用户具有写权限)。用0表示完全关闭在sysfs中相对应的项。
其实宏不会声明变量,因此在使用宏之前,必须声明变量。所以,典型地用法如下:
static unsigned int use_acm = 0;
module_param(use_acm, uint, S_IRUGO);
这些必须写在模块源文件的开头部分。即use_acm是全局的。
我们也可以使模块源文件内部的变量名与外部的参数名有不同的名字。这通过宏module_param_named()定义。
module_param_named(name, variable, type, perm);
这里name是外部可见的参数名,variable是源文件内部的全局变量名。例如:
static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
如果模块参数是一个字符串时,通常使用charp类型定义这个模块参数。内核复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。例如:
static char *name;
module_param(name, charp, 0);
另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。
module_param_string(name, string, len, perm);
这里,name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小(可以小于buffer的大小,但是没有意义),perm表示sysfs的访问权限(或者perm是零,表示完全关闭相对应的sysfs项)。例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
上面说得只是给模块传入一个参数的情况,如果给模块传入多个参数,那该怎么办呢?可以通过宏module_param_array()给模块传入多个参数。 用法如下:
module_param_array(name, type, nump, perm);
这里,name既是外部模块的参数名又是程序内部的变量名,type是数据类型,perm是sysfs的访问权限。指针nump指向一个整数,其值表示有多少个参数存放在数组name中。值得注意是name数组必须静态分配。例如:
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444);
通过宏module_param_array_named()使得内部的数组名与外部的参数名有不同的名字。例如:
module_param_array_named(name, array, type, nump, perm);
这里的参数意义与其它宏一样。
最后,通过宏MODULE_PARM_DESC()对参数进行说明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole” /
“connected to this computer.” );
7、导出符号
当装载模块的时候,模块动态地链接入内核之中。动态链接的二进制代码只能调用外部函数,然而,外部函数必须明确地输出,在内核中,通过EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL来达到这个目的。
输出的函数可以被其它模块调用。没有输出过的函数不能被其它模块调用。模块比核心内核映像代码具有更严格的链接和调用规则。因为所有核心源文件链接成一个单一的作为基础的映像,因此在内核中核心代码可以调用任何非静态的接口。当然,输出符号也必须是非静态属性。
一套输出的内核符号称之为输出的内核接口,甚至称之为kernel API。
输出一个内核符号是举手之劳之事。当函数声明之时,在其后用EXPORT_SYMBOL()把函数输出。
例如:
/* it will receive control requests including set_configuration(), which enables non-control requests.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
…
}
EXPORT_SYMBOL(usb_gadget_register_driver) ;
从此以后,任何模块都可以调用函数usb_gadget_register_driver(),只要在源文件中包含声明这个函数的头文件,或者extern这个函数的声明。
有些开发者希望他们的接口只让遵从GPL的模块调用。通过MODULE_LICENSE()的使用,内核链接器能够强制保证做到这点。如果你希望前面的函数仅被标有GPL许可证的模块访问,那么你可以用如下方式输出符号:
EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
如果你的代码配置为模块方式,那么必须确保:源文件中使用的所有接口必须是已经输出的符号,否则导致在装载时链接错误。