2.6内核引入了一个新特性--统一的设备模型.其优点如下:
.代码重复最小化;
.提供诸如引用计数这样的统一机制;
.可以列举系统中所有的设备,观察它们的状态,并且查看它们连接的总线;
.将系统中全部设备结构以树的形式完整、有效地展现出来,包括所有的总线和内部连接;
.可以将设备和其对应的驱动联系起来,反之亦然;
.可以将设备按照类型加以归类,如分类输入设备,而无需要理解设备的拓扑结构;
.可以沿设备树的叶子向其根方向依次遍历,保证能以正确的顺序关闭各设备的电源.
最后一点是实现设备模型的最初动机.若想在内核中实现智能电源管理,就需要建立表示系统中设备拓扑关系的树结构.当在树上端的设备关闭电源时,内核必须首先关闭该设备节点以下的(处于叶子上)设备电源.比如内核需要先关闭一个USB鼠标,然后才可关闭USB控制器;同样内核也必须在关闭PCI总线前先关闭USB控制器.简而言之,若要准确而又高效地完成上述电源管理,内核无疑需要一颗设备树.
17.1 kobject
设备模型的核心是kobject,它由struct kobject结构体表示.kobject对象最形象的意义是对应sys目录下的一个目录.
struct kobject {
char * k_name;
char name[KOBJ_NAME_LEN];
struct kref kref;
struct list_head entry;
struct kobject * parent;
struct kset * kset;
struct kobj_type * ktype;
struct dentry * dentry;
};
各域的意义如下:
K_name:指向kobject名称的起始位置.如果名称长度小于KOBJ_NAME_LEN,该kobject的名称存放到name数组中,k_name指向数组头;如果名称长度大于KOBJ_NAME_LEN,则动态分配一个足够大的缓冲区存放kobject的名称,这时k_name指向分配的缓存区;
Parent:同样是struct kobject类型.指向kobject的父对象,用来实现sysfs的层次结构;
Dentry:指向dentry结构体,在sysfs中该结构体就是表示这个kobject;
Kref、ktype、和kset这些字段指向了kobject所需要的其他结构体;
Kobject单独存在的意义不大,一般嵌入到其他的结构体中,如struct cdev.如下:
struct cdev {
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
Kobject对应sysfs下的一个目录,这个目录下面可以包含文件,而这些文件可以包含一些设备及其驱动的一些信息.这些文件及文件的操作集由kobject结构体中的kobj_type指向.如下:
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops * sysfs_ops;
struct attribute ** default_attrs;
};
其中,default_attrs便是文件;
Sysfs_ops便是对应文件的操作集.
下面是对kobj_type的解说.
17.2 ktype
上面说了,kobject是目录,而我们一般的信息都存放在文件里面,而这些信息的读写操作也必须有相应的方法.因此,也必须得包含在其中.
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops * sysfs_ops;
struct attribute ** default_attrs;
};
各域意义如下:
Release:在kobject引脚计数减至0时被调用的析构函数.
Sysfs_ops:当前kobject目录下文件的操作方法集合;
Default_attrs:是一个数组,其中的每个元素表示一个文件.因此,一个目录下面可以有多个文件便是依此来实现的.数组中最后一项必须为NULL.
17.3 kset
Kset可以现在成一个容器,里面存放相关的kobject对象.换句话来说,kset在sysfs的最表象的意义是可包含目录的目录,当然,它还可以包含文件(里面的ktype域).实现思想源码如下:
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
{
... ...;
retval = kobject_add_varg(kobj, parent, fmt, args);
... ...;
}
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
const char *fmt, va_list vargs)
{
... ...;
return kobject_add_internal(kobj);
}
static int kobject_add_internal(struct kobject *kobj)
{
... ...;
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
}
... ...;
}
Kset用结构体struct kset表示.定义于<linux/kobject.h>中.
struct kset {
struct subsystem * subsys;
struct kobj_type * ktype;
struct list_head list;
struct kobject kobj;
struct kset_hotplug_ops * hotplug_ops;
};
各域意义如下:
Subsys:指向该结构体相关的struct subsystem,下面讲述;
Ktype:用于指向本kset中kobject对象中的文件的集合;
List:连接该集合(kset)中所有的kobject对象;
Kobj:指向本kset下的kobject对象;
Hotplug_ops:指向一个用于处理集合中kobject对象的热插拔操作的结构体.
17.4 subsystem
Subsystem的意义是一个或多个kset的大集合.其实,它只比kset多了一个信号灯量.在后期的LK2.6内核,便把subsystem去掉了.如下:
struct subsystem {
struct kset kset;
struct rw_semaphore rwsem;
};
17.5 别混淆了这些结构体
Ktype、kobject、kset和subsystem四者的关系如下图所示:
17.6 管理和操作kobject
在实际的驱动编程中,我们并不需要直接处理kobject.这里了解下管理和操作它的外部接口就可以了.
初始化kobject对象的示意代码如下:
Kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL);
If(!kobj)
Return -ENOMEM;
Memset(kobj,0,sizeof(*kobj));
Kobj->kset = kset;
Kobj->parent = parent_kobj;
Kobject_init(struct kobject *kobj);
初始完后,用kobject_set_name()函数为该kobject设置名称:
Int kobject_set_name(struct kobject *kobj,const char *fmt,...);
17.7 引用计数
Kobject主要功能之一就是为我们提供了一个统一的引用计数系统.初始化后,kobject的引用计数为1.只要其计数不为0,该对象继续保留在内存中.当该对象的引用计数为0,对象可以被销毁.任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后减少它的引用计数.
增加引用计数:
Struct kobject *kobject_get(struct kobject *kobj);
减少引用计数:
Void kobject_put(struct kobject *kobj);
如下代码:
void kobject_init(struct kobject * kobj)
{
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->kset = kset_get(kobj->kset);
}
void kobject_put(struct kobject * kobj)
{
if (kobj)
kref_put(&kobj->kref, kobject_release);
}
17.8 sysfs
Sysfs文件系统是基于RAM的虚拟文件系统.它为我们提供了kobject对象层次的结构视图.帮助用户能以一个简单的文件系统方式来观察系统中各设备的拓朴结构.
Sysfs的把kobject对象与目录项(directory entry)紧密联系起来,这点通过kobject对象中的dentry字段实现.下面是挂载于/sys目录下的sysfs文件系统的局部视图:
Sysfs的根目录下包含了七个目录:block、bus、class、devices、firwwear、module和power.
Block:此目录下的每个子目录都对应着系统中一个块设备;
Bus:此目录提供了一个系统总线的拓朴结构视图直接映射内核中设备结构的组织层次;
Firmware:此目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树;
Power:包含了系统范围的电源管理数据.
Devices:此目录是最重要的一个目录,该目录将设备模型导出到用户空间.目录结构就是系统中实际的设备拓扑.其他目录中的很多数据都是将devices目录下的数据加以转换加工而得.比如/sys/class/net/目录就是注册网络接口这一高层概念来组织设备关系的,这个目录可能会有目录eth0,它里面包含的devices文件其实就是一个指回devices下实际的设备目录符号链接.
17.8.1 sysfs中添加和删除kobject
上述初始化的kobject对象是不能将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数:kobject_add():
Int kobject_add(struct kobject *kobj);
Kobject在sysfs中的位置 取决于kobject在对象层次结构的位置.如果kobject的父指针被设置,那么在sysfs中的kobject将被映射为其父目录下的子目录,如果parent没有设置,那么kobject将被映射为kset->kobj中的子目录.如果给定的kobject中的parent或kset字段都没有设置,那么就认为kobject没有父对象,所以就会映射成sysfs下的根目录.sysfs中代表的kobject的目录名字是由kobj->k_name指定的.
删除sysfs子目录:
Void kobject_del(struct kobject *kobj);
Void kobject_put(struct kobject *kobj);
简化操作的API如下:
Int kobject_register(struct kobject *kobj)
Void kobject_unregister(struct kobject *kobj)
17.8.2 向sysfs中添加文件
默认的文件集合通过kobject和kset中的ktype字段中的default_attrs域提供的.它负责将内核数据映射成sysfs中的文件.
struct attribute {
char * name;
struct module * owner;
mode_t mode;
};
Name:sysfs下的文件名;
Owner:所属模块;
Mode:读写权限.
文件的操作方法集合由kobject和kset中的ktype字段中的sysfs_ops.提供.此结构体定义在<linux/sysfs.h>文件中.
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
};
Show:读sysfs文件时该方法被调用;
Store:写sysfs文件时该方法被调用.
创建sysfs文件:
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
参数意义如下:
Kobj:sysfs文件所在的目录(kobject对象);
Attr:文件
创建sysfs符号连接:
Int sysfs_create_link(struct kobject *kobj,struct kobject *target,char *name);
参数意义如下:
Kobj:源目录(kobject对象);
Target:目标目录(kobject对象);
Name:创建的符号连接名.
删除文件:
Void sysfs_remove_file(struct kobject *kobj,const struct attribute *attr);
删除连接:
Void sysfs_remove_link(struct kobject *kobj,char *name);
Sysfs约定
Sysfs提供内核到用户空间很多服务,用户程序可以检测和获得其存在性、位置、取值以及sysfs目录和文件的行为.因此,必须遵守一定的约定:
一、sysfs文件应该保证每个文件只导出一个值或者可以将同一类型的多个值放入一个文件中.总而言之,要做到精简;
二、保证目录(kobject对象)的层次清晰;
三、向用户空间提供内核、设备和驱动的服务.
17.9 内核事件层
内核事件层实现了内核到用户的消息传递,其就是建立在kobject基础之上的.比如说,系统插入一个U盘,需要上报给上层用户空间有一个U盘插进来的消息.
内核代码中向用户空间发送信号使用函数kobject_uevent()函数实现.
int kobject_uevent(struct kobject *kobj, enum kobject_action action,
struct attribute *attr);
参数意义如下:
Kobj:指定发送该信号的kobject对象,此对象中包含该kobject映射到sysfs的路径;
Action:该信号的"动作",这些动作包括"mount"、"unmount"、"add"、"remove"和"change"等;
Attr:文件
该函数分配内存,所以可以睡眠.下面的是原子操作,是不允许睡眠.
int kobject_uevent_atomic(struct kobject *kobj, enum kobject_action action,
struct attribute *attr)