20135327郭皓--读书笔记六

第十七章 设备与模块

在本章中,关于设备驱动和设备管理,咱们讨论四种内核成分。node

  • 设备类型:在全部 Unix 系统中为了统一普通设备的操做所采用的分类.
  • 模块: Linux 内核中用于按需加载和卸载目标码的机制.
  • 内核对象:内核数据结构中支持面向对象的简单操做,还支持维护对象之间的父子关系。 
  • sysfs :表示系统中设备树的一个文件系统。

17 .1 设备类型

在 Linux 以及全部 Unix 系统中,设备被分为如下三种类型:linux

  • 块设备
  • 字符设备
  • 网络设备 
块设备一般缩写为 blkdev,它是可寻址的,寻址以块为单位,块大小随设备不一样而不一样;块设备一般支持重定位(seeking)操做,也就是对数据的随机访问。 
字符设备一般缩写为 cdev,它是不可寻址的,仅提供数据的流式访问,就是一个个字符,或者一个个字节。
网络设备最多见的类型有时也以以太网设备(ethemet devices)来称呼,它提供了对网络(例如Internet)的访问,这是经过-个物理适配器(如你的膝上型计算机的 802.11 卡)和一种特定的协议(如 IP协议)进行的。网络设备打破了Unix的“全部东西都是文件”的设计原则,它不是经过设备节点来访问,而是经过套接字 API 这样的特殊接口来访问。

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模块化

1. 放在内核派代码树中 函数

你须要在 drivers/char/目录下建 立一个名为 fishing 的子目录。接下来须要向drivers/cbar/下的Makefile文件中添加一行。编辑drivers/char/Makefile/并加入:工具

obj -m +• fishing/ 

这行编译指令告诉模块构建系统,在编译模块时须要进入 fishing/子目录中。 学习

在drivers的bar/fishing/下,须要添加一个新Makefile 文件,其中须要有下面这行指令:

obj-m +=fishing.o

一切就绪了,此刻构建系统运行将会进入 fishing/ 目录下,而且将 fishing.c 编译为 fishing.ko 模块.虽然你写的扩展名是.o,可是模块被编译后的扩展名倒是.ko。

2. 放在内核代码外 

若是你喜欢脱离内核源代码树来维护和构建你的模块,把本身做为一个圈外入,那你要作的就是在你本身的源代码树目录中创建一个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/ location SUBDI RS=$PWD modules 

在这个例子中, kernel/source/location是你配置的内核源代码树。 

 

17.2.3 安装模块

下面的构建命令用来安装编译的模块到合适的目录下:

make modules install 

17.2.4 产生模块依赖性 

多数 Linux发布版都能自动产 生这些依赖关系信息,并且在每次启动时更新。若想产生内核依赖关系的信息, root用户可运行命令:

depmod 

为了执行更快的更新操做,那么能够只为新模块生成依赖信息,而不是生成全部的依赖关系,这时root用户可运行命令:

depmod -A 

17.2.5 戴入模块

载入模块最简单的方法是经过 insmod 命令,这是个功能颇有限的命令,它能傲的就是请求内核载入指定的模块。 insmod程序不执行任何依赖性分析或进一步的错误检查。它用法简单, 以 root 身份运行命令:

insmod module.ko 

相似的,卸载一个模块,你可以使用 rmmod 命令,它一样须要以 root 身份运行:

rmmod module 

这两个命令是很简单,可是它们一点也不智能。先进工具 modprobe 提供了模块依赖性分析、错误智能检查、错误报告以及许多其余功能和选项。

我强烈建议你们用这个命令。 为了在内核via modprobe中插入模块,须要以root身份运行:

modprobe module [ module parameters ] 

modprobe 命令也可用来从内核中卸载模块,固然这也须要以 root 身份运行:

modprobe -r modules 

17.2.6 管理配置选项

若是你创建了一个新子目录,并且也但愿kconfig 文件存在子该目录中的话,那么你必须在一个己存在的kconfig文件中将它引入。你须要加入下面一行指令

source "drivers/char/fishing/Kcor,lfig”

这里所谓存在的Kconfig 文件多是drivers/char/Kconfig。

配置选项第一行定义了该选项所表明的配置目标。注意CONFIG_ 前缀并不须要写上。第二行声明选项类型为住istate,也就是说能够编译进内核( Y ),也可做为模块编译( M),或者干脆不编译它( N)。第三行指定了该选项的默认选择,这里默认操做是不编译它。

17.2.7 模块参数

Linux提供了这样一个简单框架一一它可容许驱动程序声明参数,从而用户能够在系统启动 或者模块装载时再指定参数值,这些参数对于驱动程序属于全局变量。

定义一个模块参数可经过宏module_param() 完成:

module_param(name, type, perm); 

参数name既是用户可见的参数名,也是你模块中存放模块参数的变量名.参数 type 则存放 了参数的类型,它能够是byte、 short、 ushort、 int、 uint、 long、 ulong、 charp、bool或invbool, 它们分别表明字节型、短整型、无符号短整形、整型、无符号整型、长整形、无符号长整型;最后一个参数 perm 指定了模块在 sysfs 文件系统下对应文件的权限,该值能够是八进制的格式, 好比“44 (全部者 能够读写,组内能够读,其余人能够读):或是 S_Ifoo 的定义形式,好比 S_IRUGO I S_IWUSR (任何人可读, user可写):若是该值是零,则表示禁止全部的 sysfs项。 

有可能模块的外部参数名称不一样于它对应的内部变量名称,这时就该使用宏 module_param_ named()定义了:

modul e_param_named(name, variable, type, perm); 

若是须要,也可以使内核直接拷贝字符事到指定的字符数组。module_param_string()完成上述任务:

module_param_string(name, string, len, perm); 

你能够将内部参数数组命名区别于外部参数,这时你需使用宏: 

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. ”); 

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/kobject>中。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) ;

该函数正常状况下将返回一个指向kobject的指针,若是失败则返回NULL指针:碱少引用计数经过kobject_putO 完成,这个指令也声明在《linux/kobject.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对象和属性来表示新对象和数据一一它们还没有出如今sysfs中.

17.5 小结

  本章中,咱们考察的内核功能涉及设备驱动的实现和设备树的管理,包括模块、 kobject (以 及相关的 kset和 ktype)和 sysfs。这些功能对于设备驱动程序的开发者来讲是相当重要的,由于 这可以让他们写出更为模块化、更为高级的驱动程序. 这章讨论了内核中咱们要学习的最后一个子系统,从下面开始要介绍一些广泛的但却重要的 主题,这些主题是任何一个内核开发者都须要了解的,首先要讲的就是调试!

相关文章
相关标签/搜索