第十七章 设备与模块
关于设备驱动和设备管理,讨论四种内核成分:
·设备类型:在所有Unix 系统中为了统一普通设备的操作所采用的分类
·模块: Linux 内核中用于按需加载和卸载目标码的机制
·内核对象:内核数据结构中支持面向对象的简单操作,还支持维护对象之间的父子关系。
·sysfs :表示系统中设备树的一个文件系统。
17.1 设备类型
①设备被分为以下三种类型:块设备、字符设备、网络设备
②块设备通常缩写为blkdev,它是可寻址的,寻址以块为单位,块大小随设备不同而不同;块设备通常支持重定位( seeking )操作,也就是对数据的随机访问。
③字符设备通常缩写为cdev,它是不可寻址的,仅提供数据的流式访问,就是一个个字符,或者一个个字节。字符设备的例子有键盘、鼠标、打印机,还有大部分伪设备。
④网络设备最常见的类型有时也以以太网设备(ethemet devices)来称呼,它提供了对网络〈例如Internet)的访问,这是通过一个物理适配器(如你的膝上型计算机的802.11 卡)和一种特的协议(如IP 协议)进行的。
17.2 模块
Linux 内核是模块化组成的,它允许内核在运行时动态地向其中插入或从
中删除代码。这些代码(包括相关的子例程、数据、函数人口和函数出口)被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,或简称为模块。
17.2.1 Hello, World
hello_ini的函数是模块的入口点,它通过module_ init()例程注册到系统中,在内核装载时被调用。调用module_initO 实际上不是真正的函
数调用,而是一个宏调用,它唯一的参数便是模块的韧始化函数。。模块的所有初始化函数必须符合下面的形式:
int my _ init (void) ;
因为init 函数通常不会被外部函数直接调用,所以你不必导出该函数,故它可标记为static类型.
hello_ exit()函数是模块的出口函数,它由module_exit()例程应册到系统。退出函数必须符合以下形式:
void my_exit (void);
与init 函数一样,你也可以标记其为static.MODULE_LICENSE()宏用于指定模块的版权。如果载入非GPL 模块到系统内存,则会在内核中设置被污染标识一一这个标识只起到记录信息的作用。
17.2.2 构建模块
①放在内核派代码树中:假设想建立自己代码的子目录,你的驱动程序
是一个钓鱼竿和计算机的接口,名为Fish Master XL 3000,那么你需要在drivers/char/目录下建立一个名为fishing 的子目录。接下来需要向企ivers/cbar/下的Makefile 文件中添加一行。编辑ivers/cbar/协kefile/ 并加入:
obj -m += fishing/
比如,可能的CONFIG_FISHING_POLE (请看17.2.6 节,它会告诉你如何加入一个新的编译选项)。如果这样,你需要用下面的指令代替刚才那条指令:
obj -$ (CONFIG_FI SHI NG_POLE) += fishing/
最后,在drivbar/fishing/ 下,需要添加一个新Makefile 文件,其中需要有下面这行指令:
obj-m +=fishing
构建系统运行将会进入fishing/ 目录下,并且将fishing.c 编译为fishing.ko模块.虽然你写的扩展名是.0,但是模块被编译后的扩展名却是.ko
最后一个注意事项是,在构建文件时你可能需要额外的编译标记,如果这样,你只需在Make 中添加如下指令:
EXTRA CFLAGS +~ - DTITANIUM POLE
②放在内核代码外:要傲的就是在你自己的源代码树目录中建立一个Makefile 文件,它只需事一行指令:
obj -m : = fishing.o
这条指令就可把fishing.c 编译成fishing.ko 。如果你有多个源文件, 那么用两行就足够:
obj -m := fishing .o
fishing-objs := fishing -main.o fishing - line.o
这样一来, fisbing-main.c 和fishing-line.c 就一起被编译和连接到fishing.ko 模块内了。模块在内核内和在内核外构建的最大区别在于构建过程。当模块在内核源代码树外国时,你必须告诉make 如何找到内核源代码文件和基础Makefile 文件。不过要完成这个工作同样不难:
make -c / kernel/source/ l ocati on SUBDI RS =$PWD modules
17.2.3 安装模块
下面的构建命令用来安装编译的模块到合适的目录下
make modules install
通常需要以root 权限运行。
17.2.4 产生模块依赖性
若想产生内核依赖关系的信息, root 用户可运行命令
depmod
为了执行更快的更新操作,那么可以只为新模块生成依赖信息,而不是生成所有的依赖关系,这时root 用户可运行命令
depmod -A
模块依赖关系信息存放在/lib/modules/version/modules.dep 文件中.
17.2.5 戴入模块
insmod 程序不执行任何依赖性分析或进一步的错误检查。它用法简单,以root 身份运行命令:
insmod module .ko
这里, module.ko 是要载入的模块名称。比如装载钓鱼竿模块,那你就执行命令
insmod fis hing.ko
类似的,卸载一个模块,你可使用rmmod 命令,它同样需要以root 身份运行
rmmod module
为了在内核via modprobe 中插入模块,需要以root 身份运行
modprobe module [ module parameters ]
其中,参数module 指定了需要载入的模块各称,后面的参数将在模块加载时传入内核。modprobe 命令也可用来从内核中卸载模块,当然这也需要以root 身份运行:
modprobe -r modules
参数modules 指定一个或多个需要卸载的模块.与rmmod 命令不同, modprobe 也会载给定模块所依赖的相关模块,但其前提是这些相关模块没有被使用.
17.2.6 管理配置选项
如果你建立了一个新子目录,而且也希望kconfig 文件存在子该目录中的话,那么你必须在一个己存在的kconfig 文件中将它引入。你需要加入下面一行指令
source "drivers/char/fishing/Kcor,lfig”
这里所谓存在的Kconfig 文件可能是drivers/char/Kcon句。
配置选项第一行定义了该选项所代表的配置目标。注意CONFIG_ 前缀并不需要写上。第二行声明选项类型为住istate,也就是说可以编译进内核( Y ),也可作为模块编译( M),或者干脆不编译它( N)。第三行指定了该选项的默认选择,这里默认操作是不编译它
17.2.7 模块参鼓
定义一个模块参数可通过宏module_p缸础。完成:
module_param(name, type, perm);
有可能模块的外部委数名称不同于它对应的内部变量名称,这时就该使用宏module_param_named()定义了:
modul e_param_named(name, variable, type, perm);
如果需要,也可使内核直接拷贝字符事到指定的字符数组。叫ule_param_s回可完成上述任务:
module_param_string(name, string, len, perm);
你可以将内部参数数组命名区别于外部参数,这时你需使用宏:
module_param_array_named(name, array, type, nump, perm) ;
其中参数和其他宏一致。
最后,你可使用MODULE_PARM_DESC()描述你的参数:
static unsigned short size = l;
module_param(size, ushort , 0644 ) ;
MODULE_PARM_DESC(size ,”The size in inches of the fishing pole. ”);
上述所有宏需要包含<linux/module.h>头文件。
17.2.8 导出符号表
在内核中,导出内核函数需要使用特殊的指令: EXPORT_ SYMBOL()和EXPORT_SYMBOL_GPL()。导出的内核函数可以被模块调用,而来导出的函数模块则无陆被调用。
假定get_pirate_ beard_ color() 同时也定义在一个可访问的头文件中,那么现在任何模块都可以访问它。有一些开发者希望自己的接口仅仅对GPL-兼容的模块可见,内核连接器使用MODULE_LICENSE()宏可满足这个要求。如果你希望先前的函数仅仅对标记为GPL 协议的模块可见,那么你就需要用:
EXPORT_SYMBOL_GPL(get_pirate_beard_color);
17.3 设备模型
设备模型提供了一个独立的机制专门来表示设备,并描述其在系统中的拓扑结构,从而使得系统具有以下优点:
·代码重复最小化.
.提供诸如引用计数这样的统一机制。
·可以列举系统中所有的设备,观察它们的状态,并且查看它们连接的总结.
·可以将系统中的全部设备结构以树的形式完整、有效地展现出来一一包括所有的总统和内部连接。
·可以将设备和其对应的驱动联系起来,反之亦然。
·可以将设备按照类型加以归类,比如分类为输入设备,而无需理解物理设备的拓扑结构.
·可以沿设备树的叶子向其根的方向依次遍历,以保证能以正确顺序关闭各设备的电源。
17.3.1 kobject
设备模型的核心部分就是kobject (kernel objt),它自struct kobject 结构体表示,定义于头文件<linux/k,ρ1lijt.b>中。sd 指针指向sysfs_ dirent 结构体,该结构体在叨地中表示的就是这kobject. 从sysfs 文件系统内部看,这个结构体是表示kobject 的一个inode 结构体.kref提供引用计数。ktype 和kset 结构体对kobject 对象进行描述和分类.
17.3.2 ktype
kobject 对象被关联到一种特殊的类型,目p ktype (kernel object type 的缩写)。ktype 由kobj_type 结构体表示,定义于头文件<linux/kobjt.h>中
ktype 的存在是为了描述一族kobject 所具有的普遍特性.如此一来,不再需要每个kolct部分别定义自己的特性,而是将这些普遍的特性在ktype 结构中一次定义,然后所有“同类”的kobject 都能共享一样的特性。
sysfs_ops 变量指向sysfs_ops 结构体。该结构体描述了sysfs 文件读写时的特性。
17.3.3 kset
kset 是kobject 对象的集合体。把它看成是一个容器,可将所有相关的koject 对象,比如“全部的块设备”置于同一位置。kobject 的kset 指针指向相应的kset 集合。kset 集合由kset 结构体表示,定义于头文件
<linux/kobject.h>中:
在这个结构中,其中list 连接该集合( kset )中所有的kobject 对象, list_lock 是保护这个链表中元素的自旋锁。uevent 就是用户事件(user event )的缩写,提供了与用户空间热插拔信息进行通信的机制。
17.3.4 kobject 、ktype 和kset 的相互关系
这里最重要的家伙是kobject,它由struct koject 表示。kobject 为我们引入了诸如引用计数(reference counting)、父子关系和对象名称等基本对象道具,并且是以一个统一的方式提供这些功能。不过kobject 本身意义井不大,通常情况下它需要被嵌入到其他数据结构中,让那些包含
它的结构具有了kobject 的特性。
kobject 叉归入了称作kset 的集合, kset 集合由struct kset 结构体表示。kset 提供了两个功能。
第一,其中嵌入的kobj创作为kobject 组的基类。
第二, kset 将相关的kobject 集合在一起。在sysfs 中,这些相关的koject 将以独立的目录出现在文件系统中。这些相关的目录,也许是给定目录的所有子目录,它们可能处于罔一个kset 。
17.3.5 管理和操作kobject
使用kobjcet 的第一步需要先来声明和初始化。kobject 通过函数ko均ect_init 进行初始化,该函数定义在文件<linux/kobject.h>中
void kobject_init(struct kobject •kobj, struct kobj_type •ktype);
这个工作往往会在kobject 所在的上层结构体初始化时完成。如果kobject 未被清空,那么只需要调用memset() 即可:
memset(kobj, 0, sizeof (*kobj ) );
这多步操作也可以自kobject.te()来自动处理,它返回一个新分配的kobject:
struct kobject •kobject_create(void);
17.3.6 引用计数
①递增和递减引用计数:增加一个引用计数可通过koject_getO 函数完成
struct kobject • kobject_get(struct kobject •kobj) ;
该函数正常情况下将返回一个指向kot项目t 的指针,如果失败则返回NULL 指针:碱少引用计数通过kobject_putO 完成,这个指令也声明在《linux/kob饵t.h》中
void kobject_put(struct kobject •kobj);
②kref:该函数将使得引用计数减1 ,如果计数减少到零,则要调用作为参数提供的release() 函数.注意ON()声明,提供的时回拢。函数不能简单地采用凶叫,它必须是一个仅接收一个kref结构体作为参数的特有函数,而且还没有返回值. kref_put() 函数返回0,但有一种情况下它返回I,
那就是在对该对象的最后一个引用减1时。
17.4 sysfs
sys俗文件系统是一个处于内存中的虚拟文件系统,它为我们提供kobject 对象层次结构的视圈。帮助用户能以一个简单文件系统的方式来观察系统中各种设备的拓扑结构.借助属性对象, kobject 可以用导出文件的方式,将内核变量提供给用户读取或写入
sysfs 的诀窍是把kobject 对象与目录项(directory entries)紧密联系起来,这点是通过kobject 对象中的dentry 字段实现的.
由于kobject 被映射到目录项,同时对象层次结构也已经在内存中形成了
一棵树,因此sys的生成便水到渠成般地简单了.
Sysf单的根目录下包含了至少十个目录。其中最重要的目录是devices , 该目录将设备模型导出到用户空间. 目录结构就是系统中实际的设备拓扑
17.4.1 sysfs 中添加和删除kobject
仅仅初始化kobject 是不能自动将其导出到sysfs 中的,想要把kot加ct 导入sysfs,你需要用到函数kobject_add():
int kobject_add (struct kobj ect •kobj , struct kobj ect •parent, const char •fmt , . .. ) ;
辅助函数kobject_create_and_add()把kobject_ createO 和koect_add()所的工作放在一个函数中:
struct kobject •kobject_create_and_add(const char • name, struct kobject •parent) ;
从sysfs 中删除一个kobject 对应文件目录,需使用函数kobject_del() :
void kobject _del(struct kobject *kobj );
17.4.2 向sysfs 中添加文件
①默认属性:默认的文件集合是通过kobject 和kset 中的ktype 字段提供的。因此所有具有相同类型的kobject 在它们对应的sysfs 目录下都拥有相同的默认文件集合。其中名称字段提供了该属性的名称,最终出现在sysfs 中的文件名就是它。owner 字段在存在所属模块的情况下指向其所属的module结构体。store()方法在写操作时调用,它会从buffer 中读取size 大小的字节,并将其存放入a即表示的属性结构体变量中。缓冲区的大小总是为PAGE_SIZE 或更小些。该函数如果执行成功,则将返回实际从buffer 中读取的字节数:如果失败,则返回负数的错误码。
②创建新属性:事实上,因为所有具有相同ktype 的kobject,在本质上区别不大的情况下,都应是相互接近的.也就是说,比如对于所有的分区而言,它们完全可以具有同样的属性集会。这不但可以让事情简单,有助于代码合井,还使类似对象在sy的目录中外现一致。sysfs_create_ file()接口:
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
除了添加文件外,还有可能需要创建符号连接。在sysfs 中创建一个符号连接相当简单:
int sysfs_create_link(struct kobject *kobj, struct kobject ·get, char 舍name);
③删除新属性:删除一个属性需通过函数sysfs_remove_ file() 完成:
void sysfs_remove_file (struct kobject *kobj, const struct attribute *at tr);
一旦调用返回,给定的属性将不再存在于给定的ko均ect 目录中。另外由sysfs_ creat_ link()创建的符号连接可通过函数sysfs_remove link()删除:
void sysfs_remove_link(struct kobject *kobj , char *name);
④sysfs约定:当前sy拙文件系统代替了以前需要由ioctl() (作用于设备节点)和procfs 文件系统完成的功能。但是为了保持sysfs 干净和直观,开发者必须遵从以下约定。
首先, sysfs 属性应该保证每个文件只导出一个值,该值应该是文本形式而且映射为简单C类型。
其次,在sy峙中要以一个清晰的层次组织数据。
最后,记住sysfs 提供内核到用户空闹的服务,这多少有些用户空间的ABI (应用程序二进制接口〉的作用。
17.4.3 内核事件层
每个事件都被赋予了一个动词或动作字符串表示信号。该字符串会以“被修改过”或“未挂载”等词语来描述事件。在内核代码中向用户空间发送信号使用函数kobject_uevent():
int kobject_uevent(struct kobject *kobj,enum kobject_action action);
第一个参数指定发送该信号的koject 对象。实际的内核事件将包含该koject 映射到sysfs 的路径。第二个参数指定了描述该信号的“动作”或“动词”
使用kobject 和属性不但有利于很好的实现基于sysfs 的事件,同时也有利于创建新kojects对象和属性来表示新对象和数据一一它们尚未出现在sy曲中.