驱动-模块(Modules)

这篇文章做为驱动的入门很不错:http://greenlinux.blogcn.com/diary,103232026.shtml,如下是引用的内容!html


     就整个kernel运行在单个保护域而言,Linux kernel 成为了“单内核”,可是Linux kernel是组件模式的,在运行时容许“代码”动态地插入到kernel和从kernel中移除。模块就是相关的一些子程序,数据,入口点和出口点共同组合成的一个单一的二进制映像,即一个可装载的kernel目标文件。模块的支持使得系统能够拥有一个最小的基本的内核映像,而且经过模块的方式支持一些可选的特征和驱动程序。模块对内核代码动态地插入到kernel中和从kernel中移除提供了一种简便方法;它有助于调试内核程序(例如,当咱们在板子上调试一个驱动程序时,能够采用模块的方式,也能够采用静态的编译到内核当中,若是采用后者,每次修改驱动程序的时候,都必须从新烧写内核;若是采用前者,只需把驱动程序下载到板子,而后动态插入到内核);当有新的设备“热插”到的系统时,能够经过模块方式按需装载新的驱动程序。在这章中,咱们将学习模块的基本知识和实现模块的基本原理以及如何写本身的模块。
1. Hello World
    模块的开发很像写一个应用程序,它有本身的入口点,出口点和本身的“生活空间”。下面咱们一块儿来写“Hello World”内核模块,借此学习写内核模块的通常步骤。
     /*
* hello.c   Hello, World! As a Kernel Module
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

/*
* hello_init   the init function, called when the module is loaded.
* Returns zero if successfully loaded, nonzero otherwise.
*/
static int hello_init(void)
{
         printk(KERN_ALERT "I bear a charmed life.\n" ) ;
         return 0;
}

/*
* hello_exit   the exit function, called when the module is removed.
*/
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" ) ;
     经过module_init()把hello_init()函数注册为这个模块的入口点。当装载模块的时候,内核调用hello_init()函数。module_init()是一个宏,它的任务是把它的唯一参数做为相应模块的初始化函数。全部初始化函数必须有下列形式:int my_init(void);
     因为初始化函数不会被外部的代码直接调用,所以你没必要export这个函数,若是把这个函数标志为static,将会更加合理。初始化函数返回一个int型整数,若是初始化成功,返回零,若是初始化失败,返回非零。
     这里的初始化函数仅仅打印了一句话,而后返回零。在实际开发的模块里,初始化函数通常是注册资源,为数据结构分配内存等等。若是咱们选择把这个文件静态地编译到内核的p_w_picpath内,那么也会保留初始化函数,而且在内核启动的时候运行。
     经过module_exit()把hello_exit()函数注册为这个模块的出口点。当模块从内存中移除的时候,内核调用hello_exit()。退出函数在返回以前,必须清除模块占用的资源,确保硬件处于一致状态等等。退出函数返回以后,模块从内核中移除。
     退出函数必须有下列形式:void my_exit(void); 似于初始化函数,你把这个函数标志为static,将会更加合理。
     若是这个文件编译到静态的内核p_w_picpath内,退出函数不会包含在内核p_w_picpath以内,它也不会被调用。由于,若是不是以模块方式插入内核,代码永远不会从内存中删除。
     宏MODULE_LICENSE()用于指定这个文件的copyright license。若是装载一个non-GPL模块到内存,从而致使在内核中设置了tainted flag。这个flag仅仅为开发者提供说明信息的目的。若是设置了tainted flag,许多内核开发者会给出bug报告。进一步说,non-GPL模块不能调用GPL-only 符号。
     最后,宏MDULE_AUTHOR()指定了这个文件的做者。宏的值彻底是为了提供说明信息的目的。
2. Building Modules
    在编译模块以前,咱们必须决定模块的源码放在哪里。你能够把模块的源码增长到内核源码的一个合适的地方,既能够把你的文件做为一个”patch”,最终也能够把你的代码合到官方的源码树内。另一种选择是在源码树以外维护和编译你本身的模块。
2.1 At Home in the Source Tree
     从理想的角度出发,你的模块是官方Linux的一部分,这样,模块的代码能够生存在内核的源码树内。若是你的工做完全地放入内核之中,那么首先你须要对模块代码要更多的维护观念,这也是一般首选的方案。
     第一步是决定你的模块将要生活在内核源码树的哪一个地方。假设咱们要写一个经过USB线把手机看成PC机的一台联机小电脑的驱动程序,那么这个驱动程序放在内核源码树的哪一个地方比较合适呢?咱们进入到内核源码树根目录下的drivers/目录下看一看,各个子目录的组织是按照驱动程序的class, type来实现的。咱们很容易发现有个usb/子目录,在usb/子目录以内,咱们又能够找到gadget/子目录,在gadget/子目录内能够发现许多驱动程序,有的用来实现经过USB线把手机看成u盘,有的用来实现经过USB线把手机看成modem,等等,物以类聚,把手机看成PC机的一台联机小电脑的驱动程序生活在gadget/子目录里看来很合适,可是因为实现这个驱动程序须要许多文件,所以,咱们想在gadget/子目录下再建立online/子目录,让咱们新建立的相关文件存放这个目录下。
     在gadget/子目录下建立online/子目录以后,咱们必须在gadget/的Makefile文件中添加一行:                 
obj-m += online/
     目的在于编译模块的时候,告诉build系统找到online/子目录。通常状况下,为了控制是否是须要编译驱动程序,咱们的使用一个特定的配置选项来达到这个目的,例如,使用CONFIG_USB_GADGET_ONLINE(在第五节会讲述如何添加一个新的配置选项)。所以咱们能够把上面添加的一行修改成:
obj-$(CONFIG_USB_GADGET_ONLINE) += online/
     最后,咱们在online/下建立新文件Makefile,而且添加下面一行到其内。
obj-m += netmeeting.o
     到这一步,build系统可以沿着源码树往下找到online/子目录,而且根据netmeeting.c文件创建netmeeting.ko模块。虽然在Makefile文件中netmeeting.o的扩展名是.o,可是编译后模块的扩展名是.ko。
     通常状况下,为了控制是否是须要编译该驱动程序,咱们的使用一个特定的配置选项来达到这个目的,例如:
obj-$(CONFIG_USB_GADGET_ONLINE) += netmeeting.o
     固然,实现手机看成PC机的一台联机小电脑的驱动程序须要许多文件,那么Makefile又该如何写呢?
obj-$(CONFIG_USB_GADGET_ONLINE) += netmeeting.o
netmeeting-objs := one.o two.o three.o four.o
     这样,netmeeting.ko 由one.c, two.c, three.c 和four.c四个文件编译链接而成。
     最后,若是你想为这些文件指定额外的gcc编译选项,在Makefile文件中添加相似于如下一行:
EXTRA_CFLAGS += -ONLINE_NETMEETING
     若是咱们不须要建立一个子目录来存放新添加的文件,而是放新添加的文件在gadget/目录下,那么咱们把在online/下的Makefile内的全部内容移到gadget/目录下的Makefile文件中。
     若是模块是否编译由配置选项来控制,然而你又想编译此模块,那么在编译以前,必须把配置选项打开。在源代码树kernel/目录下,运行make menuconfig,在drivers/usb/gadget下并无发现咱们添加的online目录,这究竟什么回事?咱们学了第五节如何添加一个新的配置选项后,问题就会迎刃而解。
2.2 Living Externally
    若是咱们在源码树以外维护和创建模块,那么在本身的源码目录下建立Makefile文件,而且添加:
obj-m := netmeeting.o
    这样会把netmeeting.c编译成netmeeting.ko。 若是有多个源文件,那么在Makefile文件中添加:
obj-m := netmeeting.o
netmeeting-objs := one.o two.o three.o four.o
    这样,one.c, two.c, three.c, four.c都编译到netmeeting.ko以内。
    与在内核源码树内添加新文件相比,主要的区别在于build过程。在内核源码树以外编译模块,须要使用命令make 去找到内核源码文件和基本的Makefile文件。按照下面的方法作就好了。
make –C /kernel/source/location SUBDIRS=$PWD modules
    这里/kernel/source/location是已经配置过的内核源码树的位置。你最好不要把内核源码树放在/usr/src/linux之下,这个目录下存放的是系统安装时的内核源码树,把你的内核源码树放到容易找的地方,好比home目录中。

2.2 Installing Modules
     编译后的模块要放在/lib/modules/version/kernel/。下面的命令把全部编译后的模块放到相应的目录之下 (须要root权限)。
make modules_install
2.3 Generating Module Dependencies
     Linux模块实用工具可以理解模块间的依赖性。这意味着,若是模块chum依赖于模块bait,那么当装载模块chum时,模块bait会自动装载。这种依赖性信息不是凭空存在的,而是事先要创建起来。大多数发行的Linux可以自动产生这种映射关系,而且在每次启动的时候,创建最新的依赖关系。为了创建模块间的依赖信息,在root权限下运行以下命令:
depmod
     为了快速更新模块间的依赖信息,即若是有新模块,从新创建依赖信息;若是没有新模块,会保留原来的依赖信息。在root权限下运行以下命令:
depmod –A
     模块间的依赖信息存放在文件/lib/modules/version/modules.dep之中。
2.4 Loading Modules
     用insmod装载模块是一种最简单的方法。它请求kernel装载指定的模块。insmod不会检查模块间的依赖关系,也不会执行是否有错误的检查。用法很简单。在root权限下,运行下列命令:
insmod module
     这里module是模块名。
     一样,移除模块,使用rmmod。在root权限下,运行下列命令:
rmmod module
     这两个实用工具,虽然用法简单,可是缺少智能性。实用工具modprobe提供了依赖关系的解决方案,智能的错误检查和汇报,等等。装载模块的时候,是咱们的首选。
     用modprobe装载模块,在root权限下,运行下列命令:
modprobe module { module parameters }
     这里module是模块名。模块的参数(请看第七节)是要传递到模块的参数。有点像DOS下执行程序的时候附带几个参数。
     modprobe命令不只试图装载写在其后的模块,还试图装载它依赖的全部模块。所以,咱们要首选它。
     modprobe也能够用于从kernel中移除模块,在root权限下,运行下列命令:
modprobe r modules
     这里modules指的是要移除的模块名,能够是多个模块名。不像rmmod仅移除指定的模块,modprobe还移除它依赖的而且不在使用中的其它模块。
2.5 Managing Configuration Options
     在2.6 kernel中,因为有了新的 “kbuild”系统,所以增添一项新的配置是至关容易的。就是在Kconfig文件中增添一项新的配置内容。Kconfig文件用于衔接整个kernel源码树。对于驱动程序而言,Kconfig文件在驱动程序源码的同级目录。若是你的驱动程序在drivers/usb/gadget/下,那么你用的是driversusb/gadget/Kconfig。
     若是你建立了一个新目录,那么要在其下建立新的Kconfig文件,而且从一个已存在的Kconfig中“source”它。即在已存在的Kconfig(例如,drivers/usb/gadget/Kconfig)中添加相似下面的一行:
source “drivers/usb/gadget/online/Kconfig”
     而后在新建的Kconfig中添加相似如下的内容:
config USB_GADGET_ONLINE
   tristate “ Gadget Netmeeting support”
   default n
   help
If you say Y here, netmeeting driver will be compiled into the kernel. You can also say M here and the driver will be built as a module named netmeeting.ko
If unsure, say N.
第一行定义了配置选项。事先已经假定存在有CONFIG_前缀,所以用不着咱们写。
第二行说明了这个配置选项是三态的,即有三种选择方式,第一种是选择Y,表示相对应的程序编译到kernel以内;第二种是选择M,表示相对应的程序编译成模块,第一种是选择N,表示不编译相对应的程序.若是没有编译成模块这个选项,能够用bool代替tristate。指令tristate后带引号的文本是配置选项名,用于各类配置实用程序的选项显示。
第三行为这个配置选项指定一个默认值,这里是表示选择了n。
第四行help指令表示其后面是帮助文本。有助于用户和开发者理解相应的程序和创建本身的内核。
还有其余另一些指令。好比depends指令用于指定要使这个配置选项有效,则必须先要设置其它配置选项有效。若是这种依赖关系不能知足,那么配置选项就会失效。例如,若是你添加了以下的指令:
depends on PXA27X
要使CONFIG_GADGET_NETMEETING这个配置选项有效,必须首先要使CONFIG_PXA27X这个配置选项有效。
指令select与depends有些相似,不一样点在于:若是选了当前配置选项,那么select后的配置选项也会选中。因为指令select会自动“打开”其余配置选项,所以它不如depends经常使用。用法以下:
select PXA27X
若是打开CONFIG_GADGET_NETMEETING配置选项,那么自动打开CONFIG_PXA27X配置选项。
对于select和depends指令,使用&&,||和!组合多个配置选项。例如:
depends on DUMB_DRIVERS && !ONE_DRIVER
意思是仅当CONFIG_DUMB_DRIVERS配置选项打开而CONFIG_ONE_DRIVER配置选项关闭时,CONFIG_GADGET_NETMEETING配置选项才打开。
指令if能够跟在指令tristate和bool以后,这样为配置选项设置了一个条件选项。若是条件不符合,不只关闭了配置选项甚至在配置实用工具内也不会出现这个配置选项。例如:
bool “Deep Sea Mode” if OCEAN
其表示只有配置选项CONFIG_OCEAN打开以后,配置实用工具才会显示配置选项名Deep Sea Mode,并且也会打开CONFIG_GADGET_NETMEETING配置选项。
指令if也能够跟在指令default以后,其意思是仅当条件成立时,才会赋默认值。
     为了更容易地创建配置,配置系统提供了几个meta-options。当且仅当用户但愿打开设计用来禁止关键特性(好比在嵌入式系统上保留精确的内存)的选项时,那么打开配置选项CONFIG_ EMBEDDED( This option allows certain base kernel options and settings to be disabled or tweaked. This is for specialized environments which can tolerate a “non-standard” kernel. Only use this if you really know what you are doing)。配置系统CONFIG_BROKEN_ON_SMP是用于指定一个驱动程序不是SMP-safe。一般这个选项是没有打开的,这样强制用户清晰地认识到一个驱动程序在SMP环境下具备“破坏性”。固然,新开发的驱动程序,不要使用这个选项。
    最后,CONFIG_EXPERIMENTAL配置选项用于标志一个驱动程序是处于试验阶段或者beta版本阶段,这个选项默认是关闭的,这使得用户在使用驱动程序以前清晰地认识到其中的风险性。
2.6 Module Parameters
     对于如何向模块传递参数,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.” );
     使用这些宏时须要包含头文件<linux/moduleparam.h>。
2.7 Exported Symbols
     当装载模块的时候,模块动态地连接入内核之中。动态连接的二进制代码只能调用外部函数,然而,外部函数必须明确地输出,在内核中,经过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);
     若是你的代码配置为模块方式,那么必须确保:源文件中使用的全部接口必须是已经输出的符号,不然致使在装载时连接错误。
2.8 Wrapping Up Modules
     这章咱们学习了如何写模块,创建模块,装载和卸载模块。咱们讨论了什么是模块,Linux如何动态装载模块代码,并且咱们还讨论了模块的参数和输出符号。linux

相关文章
相关标签/搜索