Linux内核设计与实现(17)第十七章:设备与模块
1. 设备类型,内核模块,内核对象,sysfs
linux 的设备驱动和设备管理的相关的4个内核成分,设备类型,内核模块,内核对象,sysfs。
2. 设备类型
2.1 块设备,字符设备,网络设备
linux 中主要由3种类型的设备,分别是:块设备,字符设备,网络设备
设备类型 代表设备 特点 访问方式
块设备 硬盘,光盘 随机访问设备中的内容 一般都是把设备挂载为文件系统后再访问
字符设备 键盘,打印机 只能顺序访问(一个个字符或字节) 一般不挂载,直接和设备交互
网络设备 网卡 打破了Unix "一切皆文件" 的设计原则 通过套接字API来访问
2.1. 伪设备
除了以上3种典型的设备之外,其实Linux中还有一些其他的设备类型,其中见的较多的应该算是"伪设备"。
所谓"伪设备",其实就是一些虚拟的设备,仅提供访问内核功能而已,没有物理设备与之关联。
典型的"伪设备"就是
/dev/random(内核随机数发生器)
/dev/null(空设备) 黑洞
/dev/zero(零设备)
/dev/full(满设备)
3. 内核模块
3.1 动态加载:
Linux内核是模块化组成的,内核中的模块可以按需加载,
从而保证内核启动时不用加载所有的模块,即减少了内核的大小,也提高了效率。
3.2 带参数的内核模块
内核模块可以带参数也可以不带参数,不带参数的内核模块比较简单。
构造带参数的内核模块其实也不难,内核中已经提供了简单的框架来给我们声明参数。
1. module_param(name, type, perm) : 定义一个模块参数
+ 参数 name : 既是用户可见的参数名,也是模块中存放模块参数的变量名
+ 参数 type : 参数的类型(byte, short, int, uint, long, ulong, charp, bool...)
byte型存放在char变量中,bool型存放在int变量中
+ 参数 perm : 指定模块在 sysfs 文件系统中对应的文件权限(关于 sysfs 的内容后面介绍)
如:
static int stu_id = 0; // 默认id
module_param(stu_id, int, 0644);
2. module_param_named(name, variable, type, perm) : 定义一个模块参数,并且参数对内对外的名称不一样
+ 参数 name : 用户可见的参数名
+ 参数 variable : 模块中存放模块参数的变量名
+ 参数 type和perm : 同 module_param 中的 type 和 perm
如:
static char* stu_name_in = "default name"; // 默认名字
module_param_named(stu_name_out, stu_name_in ,charp, 0644);
/* stu_name_out 是对用户开放的名称
stu_name_in 是内核模块内部使用的名称*/
3. module_param_string(name, string, len, perm) : 拷贝字符串到指定的字符数组
+ 参数 name : 用户可见的参数名
+ 参数 string : 模块中存放模块参数的变量名
+ 参数 len : string 参数的缓冲区长度
+ 参数 perm : 同 module_param 中的 perm
如:
static char str_in[BUF_LEN];
module_param_string(str_out, str_in, BUF_LEN, 0);
/* perm=0 表示完全禁止 sysfs 项 */
4. module_param_array(name, type, nump, perm) : 定义数组类型的模块参数
+ 参数 name : 同 module_param 中的 name
+ 参数 type : 同 module_param 中的 type
+ 参数 nump : 整型指针,存放数组的长度
+ 参数 perm : 同 module_param 中的 perm
如:
#define MAX_ARR_LEN 5
static int arr_len;
static int arr_in[MAX_ARR_LEN];
module_param_array(arr_in, int, &arr_len, 0644);
5. 参数描述宏
可以通过 MODULE_PARM_DESC() 来给内核模块的参数添加一些描述信息。
这些描述信息在编译完内核模块后,可以通过 modinfo 命令查看。
static int stu_id = 0; // 默认id
module_param(stu_id, int, 0644);
MODULE_PARM_DESC(stu_id, "学生ID,默认为 0"); // 这句就是描述内核模块参数 stu_id 的语句
3.3 内核模块的位置
内核模块可以放内核代码外,也可以内核代码树中。
如果你开发了一种驱动,并且希望被加入到内核中,那么,可以在编写驱动的时候就将完成此驱动功能的内核模块加到内核代码树中 driver 的相应位置。
将内核模块加入内核代码树中之后,不需要另外写 Makefile,修改内核代码树中的已有的 Makefile 就行。
3.4 内核模块相关操作
模块安装:
make modules_install <-- 把随内核编译出来的模块安装到合适的目录中( /lib/modules/version/kernel )
模块依赖性:
linux中自动生产模块依赖性的命令:
depmod <-- 产生内核依赖关系信息
depmod -A <-- 只为新模块生成依赖信息(速度更快)
模块的载入:
insmod module.ko
<-- 推荐使用以下的命令, 自动加载依赖的模块
如:
insmod ie40.ko //载入模块驱动
modprobe vfio-pci
查看: lsmod | grep vfio //lsmod 显示已载入系统的模块
模块的卸载:
rmmod module.ko
<-- 推荐使用以下的命令, 自动卸载依赖的模块
如:
modprobe -r vfio-pci
模块导出符号表
内核模块被载入后,就动态的加载到内核中,为了能让其他内核模块使用其功能,需要将其中函数导出。
内核模块中导出函数的方法:
EXPORT_SYMBOL(函数名) <-- 接在要导出的函数后面即可
EXPORT_SYMBOL_GPL(函数名) <-- 和EXPORT_SYMBOL一样,区别在于只对标记为GPL协议的模块可见
4. 内核对象
4.1 统一设备模型
2.6内核中增加了一个引人注目的新特性–统一设备模型(device model)
统一设备模型的最初动机是为了实现智能的电源管理,linux 内核为了实现智能电源管理。
需要建立表示系统中所有设备拓扑关系的树结构,这样在关闭电源时,可以从树的节点开始关闭。
优点:
实现了统一设备模型之后,还给内核带来了如下的好处:
1. 代码重复最小化(统一处理的东西多了)
2. 可以列举系统中所有设备,观察它们的状态,并查看它们连接的总线
3. 可以将系统中的全部设备以树的形式完整,有效的展示出来--包括所有总线和内部连接
4. 可以将设备和其对应的驱动联系起来,反之亦然
5. 可以将设备按照类型加以归类,无需理解物理设备的拓扑结构
6. 可以沿设备树的叶子向其根的反向依次遍历,以保证能以正确的顺序关闭设备电源
4.2 kobject 简介
统一设备模型的核心部分就是 kobject,通过下面对kobject结构体的介绍,可以大致了解它是如何使得各个物理设备能够以树结构的形式组织起来的。
struct kobject { kobject的定义在 <linux/kobject.h> 中
kobject 本身不代表什么实际的内容,一般都是嵌在其他数据结构中来发挥作用。(类似内核链表的节点)
比如 <linux/cdev.h> 中的 struct cdev (表示字符设备的struct)
struct cdev {
struct kobject kobj; /* 嵌在 cdev 中的kobject */
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev 中嵌入了kobject之后,就可以通过 cdev->kboj.parent 建立cdev之间的层次关系,通过 cdev->kobj.entry 获取关联的所有cdev设备等。
总之,嵌入了kobject之后,cdev设备之间就有了树结构关系,cdev设备和其他设备之间也有可层次关系。
4.3 ktype
ktype是为了描述一族的kobject所具有的普遍属性,也就是将这一族的kobject的属性统一定义一下,避免每个kobject分别定义。
ktype 的定义很简单,参见<linux/kobject.h>
struct kobj_type {
void (*release)(struct kobject *kobj); /* kobject的引用计数降到0时触发的析构函数,负责释放和清理内存的工作 */
struct sysfs_ops *sysfs_ops; /* sysfs操作相关的函数 */
struct attribute **default_attrs; /* kobject 相关的默认属性 */
};
4.4 kset
kset是kobject对象的集合体,可以所有相关的kobject置于一个kset之中,比如所有“块设备”可以放在一个表示块设备的kset中。
kset的定义也不复杂,参见 <linux/kobject.h>
struct kset {
struct list_head list; /* 表示kset中所有kobject的链表 */
spinlock_t list_lock; /* 用于保护 list 的自旋锁*/
struct kobject kobj; /* kset中嵌入的一个kobject,使得kset也可以表现的像一样kobject一样*/
struct kset_uevent_ops *uevent_ops; /* 处理kset中kobject的热插拔事件 提供了与用户空间热插拔进行通信的机制 */
};
4.5 kobject ktype 和 kset 之间的关系
1. 这3个概念中,kobject是最基本的。
2. kset 和 ktype 是为了将kobject进行分类,以便将共通的处理集中处理,从而减少代码量,也增加维护性。
3. kset和ktype都是为了将kobject进行分类,为什么会有2中分类呢?
从整个内核的代码来看,其实kset的数量是多于ktype的数量的,同一种ktype的kobject可以位于不同的kset中。
通俗理解:
做个不是很恰当的比喻,如果把kobject比作一个人的话,kset相当于一个一个国家,ktype则相当于人种(比如黄种人,白种人等等)。
人种的类型只有少数几个,但是国家确有很多,人种的目的是描述一群人的共通属性,而国家的目地则是为了管理一群人。
同样,ktype侧重于描述,kset侧重于管理。
4.6 kref
kref 记录 kobject 被引用的次数,当引用计数降到0的时候,则执行release函数释放相关资源。
5. sysfs 虚拟文件系统
5.1 定义:
sysfs 是一个处于内存中的虚拟文件系统,它提供了 kobject 对象层次结构的视图。
5.2 kobject 和 sysfs
内核中提供了在sysfs中操作kobject的API
kobject 结构体中与 sysfs 关联的字段就是
「struct sysfs_dirent *sd; 」
这是一个目录项结构,它表示kobject在sysfs中的位置。
kobject 是映射成 sysfs 中的目录; sysfs 中的文件是就是kobject的属性。
属性的来源有2个:
1. 默认属性 : kobject 所关联的ktype中的 default_attrs 字段
默认属性 default_attrs 的类型是结构体 struct attribute
2. 新属性 : kobject 自己定义的属性
一般来说,使用默认属性就足够了。在一些特殊的情况下,kobject可能会需要自己特有的属性。
5.3 基于sysfs的内核事件
内核事件层也是利用 kobject 和 sysfs 来实现的,用户空间通过监控 sysfs 中 kobject 的属性的变化来异步的捕获内核中kobject发出的信号。
用户空间可以通过一种 netlink 的机制来获取内核事件。
内核空间向用户空间发送信号使用 kobject_uevent() 函数