Linux设备模型之kobject
前言
关于linux设备模型机sysfs的文章和参考资料太多了,这些技术内容已经被写烂了,所以我下面大部分内容是参考过来的。其中也加了一些我自己对以sysfs的理解。其中描述1来自《linux设备驱动程序》第三版,描述2来自内核文档/Documentation/filesystems/sysfs.txt,描述3是我自己撰的,描述4来自/Documentation/kobject.txt,网上也有对kobject.txt的翻译,但是我觉得有些地方不是很准确,所以我按照自己的理解加了一些修正。
概述
Linux设备模型是从linux内核2.6版本开始引进,sysfs实现了设备模型的管理,这是一个虚拟文件系统,以一个树状结构呈现出对系统中所有的设备管理。
Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。
下面我从以下几方面来描述一下我对linux设备模型的认识:
1. 实现linux设备模型的目的
2. Linux设备模型在linux系统中的体现-sysfs
3. sysfs中关键的三个结构及它们之间的关系
4. kobject存在的意义,数据结构及如何操作
1. 实现linux设备模型的目的
引用《linux设备驱动程序》第三版中的描述--为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述,它提供了已经几方面的服务:
2.6 设备模型提供了这个抽象. 现在它用在内核来支持不同的任务, 包括
电源管理和系统关机:这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。
与用户空间的通讯 : sysfs 虚拟文件系统的实现被紧密地捆绑进设备模型, 并且暴露它所代表的结构. 关于系统到用户空间的信息提供和改变操作参数的旋纽正越来越多地通过 sysfs 和 通过设备模型来完成.
可热插拔设备:计算机硬件正更多地动态变化; 外设可因用户的一时念头而进出. 在内核中使用的来处理和(特别的)与用户空间关于设备插入和拔出的通讯, 是由设备模型来管理
设备类别:系统的许多部分需要知道什么类型的设备可用. 设备模型包括一个机制来分配设备给类别, 它在一个更高的功能性的级别描述了这些设备, 并且允许它们从用户空间被发现.
对象生命期:设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。
2. Linux设备模型在linux系统中的体现-sysfs
sysfs是一种基于内存的虚拟文件系统,在内核启动过程中建立并挂载到/sys/目录的,它提供了一种倒出内核数据结构以及属性的方法,是它们到用户空间的链接。Sysfs的基础是kobject。
3. sysfs中关键的三个结构及它们之间的关系
要理解sysfs,就要理解建立sysfs的三个关键数据结构kobject, ktype,kset, 其中kobject是最关键的,它是构成sysfs的基础,kobject引入了面向对象的思想,是sysfs中所有类型的基类,其中涉及到的kset,bus,device,device_driver,class等等这些数据结构都要包含kobject属性,也就相当于继承,只有理解了这种面向对象的思想,才能更容易的理解sysfs的构成,kobject对象其实是对应sysfs底下的一个目录,也就是说创建一个kobject对象的话,就是在sysfs中创建一个目录,但是这个目录有个特点,它只能包含属性文件,不能包含子目录,下面我简单画了一下三个结构的关系:
图1:
一目了然,kobject是基类,kset是子类,继承了kobject的成员,所以kset中也有ktype类型的成员,并含有自己的成员。
4. kobject存在的意义,数据结构及如何操作
kobject是sysfs虚拟文件系统基础元素,kobject扩展成其他元素构成sysfs的树形结构。
kobject一般对自己是不感兴趣的,代码中应该关注内嵌kobject的结构。
没有任何其他的结构可以内嵌多于一个kobject成员,如果发生了多于一个情况,这个结构创建对象的引用计数是混乱的,不正确的,代码会出现bug,所以不要这么干。
一个ktype是一个内嵌有kobject结构的类型描述,每个内嵌kobject的结构都应该有一个对应的ktype,在kobject创建和销毁时,ktype用来处理相关的事件。
Kobject结构
structkobject {
/* 目录名称*/
const char *name;
/ * 所属kset的节点 */
struct list_head entry;
/* 指向父对象 */
struct kobject *parent;
/* 指向所属的kset */
struct kset *kset;
/* 属性文件及操作 */
struct kobj_type *ktype;
struct sysfs_dirent *sd;
/* 引用计数 */
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned intstate_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
Kobject初始化
创建kobject对象后一定要初始化,调用kobject_init()函数建立kobject对象的内部成员。
void kobject_init(struct kobject*kobj, struct kobj_type *ktype);
每个kobject必须有一个关联的kobj_type,正确的创建一个ktype是必须的。调用kobject_init()以后,要调用kobject_add()函数把建立的kobject对象注册到sysfs中。
int kobject_add(struct kobject *kobj,struct kobject *parent, const char *fmt, ...);
这个函数会赋值kobject的父对象和名称,如果这个对象属于某一个具体的kset,在调用kobject_add()之前,kobj->kset必须被赋值。如果一个kset被关联到一个kobject,那么调用kobject_add()的时候,parent可以使NULL,这时kobject的parent是它所关联的kset对象。
由于把kobject注册到内核时会设置名字,这个名字不能去直接操作。如果一定要改名字的话,调用kobject_rename()函数:
int kobject_rename(struct kobject *kobj,const char *new_name);
kobject_rename()函数不会执行任何锁定操作,也不会对name进行可靠性检查,所以调用者自己检查和串行化操作是明智的选择。
kobject_set_name()是遗留下来的函数,已经被移除了。调用这个函数是错误的,必须明确这个问题,正确的访问kobject的name成员,调用kobject_name()函数:
const char *kobject_name(const structkobject * kobj);
有一个在初始化kobject对象同时注册它到内核的函数kobject_init_and_add():
intkobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, structkobject *parent, const char *fmt, ...);
参数参考kobject_init()和kobject_add()即可
uevents
在一个kobject对象注册进kobject核心以后,你要向世界宣布创建了一个kobject对象。调用kobject_uevent()函数可以达到这个目的:
int kobject_uevent(struct kobject*kobj, enum kobject_action action);
kobject对象添加到内核以后首先应该发出KOBJ_ADD事件。但是必须在kobject所有的属性和成员已经正确初始化的前提下,因为一旦调用kobject_uevent后用户空间的程序会立即查询这些属性和成员。
当把kobject对象从内核移除时,kobject核心会自动触发KOBJ_REMOVE事件,所以调用者不用去手动发出。
引用计数
kobject中内嵌了一个引用计数。只要引用这个对象一直不为0,这个对象就必须一直存在。底层操作引用计数的函数是:
struct kobject *kobject_get(structkobject *kobj);
void kobject_put(struct kobject*kobj);
成功的的调用kobject_get()函数会增加kobject的引用计数,同时返回这个kobject对象的指针。
当释放一个对kobject对象引用时,调用kobject_put()减少引用计数,可能的话,会释放对象。要注意调用kobject_init()会设置引用计数为1,所以创建kobject的代码最后要调用kobject_put()释放引用计数。因为kobject是动态的,所以它们不能被声明为静态的或者在栈区分配空间,要动态去分配。将来的内核会包含对kobjects是否是静态的运行时检查,并且警告开发者这么使用是不合适的。
如果你使用kobject仅仅是使用引用计数的话,请使用kref代替。
创建简单的kobjects
有时候开发者只是想简单的在sysfs某个目录中创建一个目录,不需要复杂的kset,show和store函数和其他细节成员。单独创建kobject是一个特例。可以使用下面的函数:
struct kobject*kobject_create_and_add(char *name, struct kobject *parent);
这个函数创建一个kobject并且把他放在指定父目录的下面。创建对应的kobject的属性文件使用下面的函数:
int sysfs_create_file(struct kobject*kobj, struct attribute *attr);
或
int sysfs_create_group(struct kobject*kobj, struct attribute_group *grp);
这两个函数中使用的属性类型都可以是kobj_attribute,因为kobject_create_and_add创建的kobject只要使用kobj_attribute就可以了,不需要再创建指定属性。
kobject移除
在kobject核心注册kobject对象成功以后,不再使用这个对象时必须要清除。为了达到这个目的,调用kobject_put()函数。这样的话kobject核心会自动释放这个对象所分配过的所有内存资源。如果针对这个对象发送过uevent KOBJ_ADD,在移除该对象时也会发出uevnet KOBJ_REMOVE,也会为调用者处理任何其他的sysfs释放资源的常规处理。
在构建了循环引用的情况下,kobject_del()可以减少kobject对父对象的引用。在某些情况下,父对象引用子对象是有效的(我理解环形结构中会有第一个父对象以最后一个子对象为自己父对象的情况)。环形引用必须通过显示调用kobject_del()来解开环形,然后调用一个释放函数(这里边说的应该是kobj_kset_leave函数,在kset list中去掉对应的kobject),对象之间的环形引用被释放。
如果需要分两步来删除kobject对象,调用kobject_del()函数在sysfs中注销,这会让kobject变得不可见,但是并没有释放资源,引用计数也不会变化。然后需要调用kobject_put()函数释放这个对象相关的内存资源。
参考:
《linux设备驱动程序》第三版
/Documentation/filesystems/sysfs.txt
/Documentation/kobject.txt
http://blog.chinaunix.net/uid-20522771-id-3447116.html
个人微信公众号:marquissong