Linux内核设计与实现---kobject sysfs


2.6内核增加了一个引人注目的新特性—同一设备模型。设备模型提供了独立的机制专门表示设备,并描述在系统中的拓扑结构。

1 kobject

设备模型的核心部分就是kobject,它由struct kobject,定义于头文件linux/kobject.h中。kobject类似于C#或java这些面向对象语言中的object对象类,提供了诸如计数、名称和父指针等字段,可以创建对象的层次结构。

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的名称。KOBJ_NAME_LEN当前为20个字节。

parent指针指向kobject的父对象。因此,kobject就会在内核中构造一个对象层次结构,并且可以将多个对象间的关系表项出来。

dentry指针指向dentry结构体,在sysfs中该结构体就表示这个kobject。

kref实现了kobject的引用计数。

kobject通常是嵌入到其他结构体中的,其单独意义其实并不大。当kobject被嵌入到其他结构体中时,该结构体便拥有了kobject提供的标准功能。

2 ktype

kobject对象中的ktype域,该结构体定义于头文件linux/kobject.h中

struct kobj_type {
	void (*release)(struct kobject *);
	struct sysfs_ops	* sysfs_ops;
	struct attribute	** default_attrs;
};

ktype是为了描述一族kobject所具有的普遍特性。因此,不在需要每个kobject都分别定义自己的特性,而是将这些特性在ktype结构体中一次定义,然后所有“同类”的kobject都能共享一样的特性。

release指针指向在kobject引用计数减为零时要被调用的析构函数。该函数负责释放所有的kobject使用的内存和其他相关清理工作。

sysfs_ops变量指向sysfs_ops结构体。该结构体表述了sysfs文件读写是的特性。

default_attrs指向一个attribute结构体数组。这些结构体定义了该kobject相关的默认属性。属性描述了给定对象的特征,如果该kobject被导出到sysfs中,那么这些属性都将相应地作为文件而导出。数组中的最后一项必须为NULL。

3 kset

kobject中的kset域,是kobject对象的集合体。把它看成是一个容器,可将所有相关的kobject对象,比如“全部的块设备”置于一个位置。kset把kobject集中到一个集合中。

kset指针指向kset集合,kset集合由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;
};

其中ktype指向kset集合中kobject对象的类型。list连接该集合中所有的kobject对象,kobj指向的kobject对象代表了该集合的基类。hotplug_ops指向一个用于处理集合中kobject对象的热插拔操作的结构体。
subsys指针指向该结构体相关的struct subsystem结构体。

4 subsystem

subsystem在内核中代表高层概念,它是一个或多个kset的大集合。

struct subsystem {
	struct kset		kset;
	struct rw_semaphore	rwsem;
};

虽然subsystem结构体只指向一个kset,但是多个kset可以通过其subsys指针指向一个subsystem。这种单向关系意味着不可能仅仅通过一个subsystem结构体就找到所有的kset。

subsystem中的kset字段指向的是subsystem中默认的kset,任rwsem字段是一个读写信号量,该信号量用来对subsystem和它的所有的kset进行并发访问保护。

5 别混淆了这些结构体

这里最重要的是kobject,它由struct kobject表示。kobject为我们引入了诸如计数、父子关系和对象名称等基本对象道具,并且是以一个统一方式提供这些功能。不过kobject本身意义不大,需要被嵌入到其他数据结构中。

kobject与一个特定的ktype对象关联,ktype由struct kobject_type结构体表示,在kobject中ktype字节指向该对象。ktype定义了一些kobject相关的默认特性:析构行为、sysfs行为以及其他一些默认属性。

kset集合由struct kset结构体表示。kset提供了两个功能。第一,其中嵌入的kobject作为kobject组合基类。第二,kset将相关的kobject集合在一起。在sysfs中,这些相关的kobject将以独立的目录出现在文件系统中。

subsystem表示的更为宏观,它是包含kset的集合,由struct subsystem结构体表示。sysfs中的根目录映射的便是subsystem。

这些数据结构的内在关系如下:
在这里插入图片描述

6 管理和操作kobject

多数时候,驱动程序开发者并不直接处理kobject,因为kobject是被嵌入到一些特殊类型结构体中的,而且由相关的 设备驱动程序在幕后管理。

使用kobject的第一步需要先来声明初始化它。kobject通过函数kobject_init进行初始化,该函数定义在文件linux/kobject.h中:

void kobject_init(struct kobject *kobj);

该函数的唯一参数就是需要初始化的kobject对象,在调用初始化函数前,kobject必须清空。如果kobject未被清空,那么只需要调用memset()即可。

在清理后,就可以安全地初始化parent和kset字段。

在这里插入图片描述
初始化后,必须采用kobject_set_name()函数为kobject设置名称:

int kobject_set_name(struct kobject *kobj,const char *fmt,...);

该函数利用了printf()和printk()风格的可变参数队列来为kobject设置名称。

初始化了kobject并设置名称后,还需要为它设置kset字段以及可能的ktype字段(可选)。如果kset没有被提供,那么仅需要设置ktype,否则kset中的ktype字段将优先被使用。

7 引用计数

kobject的主要功能之一就是为我们提供了一个同一的引用计数系统。初始化后,kobject的引用计数设置为1,只要引用计数不为0,那么该对象就会继续保留在内存中。任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后则减少它的引用计数。增加引用计数称为获得(get)对象的引用,减少引用计数称为释放(put)对象的引用。当引用计数减少到0时,对象便可以被销毁。

增加一个引用计数可通过kobject_get()函数完成:

struct kobject * kobject_get(struct kobject *kobj);

该函数正常情况下返回一个指向kobject的指针,如果失败,返回NULL。

减少引用计数是通过kobject_put()完成:

void kobject_put(struct kobject *kobj);

如果对应的kobject的引用计数减少到0,则与该kobject关联的ktype中的析构函数将被调用。

kref

kobject的引用计数是通过kref结构体实现的,该结构体定义在头文件linux/kref.h中:

struct kref {
	atomic_t refcount;
};

其中唯一的字段是用来存放引用计数的原子变量。在使用kref之前,必须先通过kref_init来初始化它:

void kref_init(struct kref *kref)
{
	atomic_set(&kref->refcount,1);
}

这个函数简单地将原子变量置为1,所以kref一旦被初始化,它表示的引用计数便固定为1。
要获得对kref的引用,需要调用kref_get()函数

void kref_get(struct kref *kref)
{
	WARN_ON(!atomic_read(&kref->refcount));
	atomic_inc(&kref->refcount);
}

该函数增加引用计数值,它没有返回值。减少对kref的引用,调用函数kref_put():

void kref_put(struct kref *kref,void (*release)(struct kref *kref))
{
	WARN_ON(release == NULL);
	WARN_ON(release == (void (*)(struct kref*))kfree);
	if(atomic_dec_and_test(&kref->refcount))
		release(kref);
}

该函数将使引用计数减1,如果减少到0,则调用作为参数提供的release函数,注意WARN_ON()声明,提供的release函数不能简单地采用kfree,它必须是一个仅接受一个kref结构体作为参数的特有函数,而且还没有返回值。

上述的所有函数定义与声明分别在文件lib/kref.c和文件linux/kref.h中。

8 sysfs

sysfs文件系统是一个处于内存中的虚拟文件系统,它为我们提供了kobject对象层次结构的视图。帮助用户能以一个简单文件系统的方式来观察系统中各种设备的拓扑结构,借助属性对象,kobject可以用到处文件的方式,将内核变量提供给用户读取或写入。

sysfs的诀窍是把kobject对象与目录项紧紧联系起来,这是通过kobject对象中的dentry域实现的。dentry结构体表示目录项,通过连接kobject到指定的目录项上,无疑方便地将kobject映射到该目录上。从此,把kobject导出形成文件系统就变得如同在内存中构建目录项一样简单。由于kobject被映射到目录项,同时kobject形成一棵树,因此sysfs的生成就很简单了。

sysfs中添加和删除kobject

仅仅初始化kobject是不能自动将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数kobject_add():

int kobject_add(struct kobject *kobj);

kobject在sysfs中位置取决于kobject在对象层次结构中的位置。如果kobject的parent指针被设置,那么在sysfs中kobject将被映射为父目录下的子目录,如果parent没有设置,那么kobject将被映射为kset->kobj中的子目录。如果给定的kobject中parent或kset字段都没有设置,那么就认为kobject没有父对象,所以就会被映射成sysfs下的根级目录。

你不必分别调用kobject_init()和kobject_add(),因为系统提供函数kobject_register()可帮助你一步到位:

int kobject_register(struct kobject *kobj);

该函数即初始化给定的kobject对象,同时又将其加入到对象层次结构体中。

从sysfs中删除一个kobject对应文件目录,需要使用函数kobject_del():

void kobject_del(struct kobject *kobj);

类似地,函数kobject_unregister()包含了kobjeect_del和kobject_put二者的功能:

void kobject_unregister(struct kobject *kobj);

上述四个函数都定义与文件lib/kobject.c中,声明于文件linux/kobject.h中。

向sysfs添加文件

sysfs仅仅是一个漂亮的树,但是没有提供实际数据的文件。

默认的文件集合是通过kobject和kset中的ktype字段提供的。因此所有具有相同类型的kobject在它们对应的sysfs目录下都拥有相同的默认文件集合,kobj_type字段含有一个字段,default_attrs,它是一个attribute结构体数组。这些属性负责将内核数据映射成sysfs中的文件。

attribute结构体定义在文件linux/sysfs.h中:

struct attribute {
	char			* name;	/* 属性名 */
	struct module 		* owner;	/* 所属模块,如果存在 */
	mode_t			mode;	/* 权限 */
};

name提供该属性的名称,最终出现在sysfs中的文件名就是它;owner字段在存在所属模块的情况下指向其所属module结构体。如果一个模块没有该属性,那么该字段为NULL。mode表示了sysfs中该文件的权限。sysfs中的所有文件和目录的uid和gid标志均为0。

sysfs_ops字段描述了如何使用他们,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方法在读操作时被调用。它会拷贝由attr提供的属性值到buffer指定的缓冲区中,缓冲区大小为PAGE_SIZE字节。
store方法在写操作时调用,它会从buffer中读取size大小的字节,并将其存放入attr表示的属性结构体变量中。

  • 创建新属性
int sysfs_create_file(struct kobject *kobj,const struct attribute *attr);

sysfs_create_file来创建新属性,通过attr参数指向相应的attribute结构体,而kobj则制定了属性所在的kobject对象。

除了添加文件外,还有可能需要创建符号连接,在sysfs中创建一个符号连接相当简单:

int sysfs_create_link(struct kobject *kobj,struct kobject *target,char *name);

该函数创建的符号连接名由name指定,连接则由kobj对应的目录映射到target指定的目录

  • 删除新属性
    删除一个属性需通过函数sysfs_remove_file完成:
sysfs_remove_file(struct kobject *kobj,const struct attribute *attr);

另外由sysfs_create_link()创建的符号连接可通过函数sysfs_remove_link删除:

void sysfs_remove_link(struct kobject *kobj,char *name);

上述的四个函数在文件linux/kobject.h中声明,sys_create_file和sysfs_remove_file函数定义于文件fs/sysfs/file.c中;sysfs_create_link和sysfs_remove_link函数定义在文件fs/sysfs/symlink.c中。

9 内核事件层

内核事件层把事件模拟为信号,从明确的kobject发出,所以每个事件源都是一个sysfs路径。

每个事件都有一个可选的负载,相比传递任意一个表示负载的字符串到用户空间而言,内核事件层使用sysfs属性代表负载。

在内核代码中向用户空间发送信号使用函数kobject_uevent():

int kobject_uevent(struct kobject *kobj,enum kobject_action action struct attribute *attr);

第一个参数指定发送该信号的kobject对象,实际的内核事件将包含该kobject映射到sysfs的路径。
第二个参数指定来描述该信号的动作。实际的内核事件将包含一个映射成枚举类型kobject_action的字符串。该函数不是直接提供一个字符串,而是利用一个枚举变量来提高可重用性和保证类型安全,而且也消除了打字错误或别的其他错误,该枚举变量定义于文件linux/kobject_uevent.c中,其形式为KOBJ_foo。当前值包含KOBJ_MOUNT、KOBJ_UNMOUNT、KOBJ_ADD等,这些值分别映射"mount"、“unmonut”、“add”等。
最后一个参数是指向attribute结构体的可选指针,它可看作为事件的“负载”。

该函数分配内存,所以可以睡眠。但在内核中实现了原子和非原子操作两个版本,其不同在于原子操作使用GFP_ATOMIC标志分配内存。

int kobject_uevent_atomic(struct kobject *kobj,enum kobject_action action,struct attribute *attr);

这两个函数分别定义和声明在文件lib/kobject_uevent.c于文件linux/kobject_uevent.h中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值