Linux内核基于kobject内核对象机制将系统中总线类型、设备和驱动分别用bus_type、device和device_driver对象描述,并且用各种类别(class)的设备及其接口(class_interface)对其管理,最终通过sysfs文件系统在用户态进行展示。本文主要介绍kobject的基本数据结构以及如何在sysfs中运用。
1.基本数据结构
- kobject
- name kobject对象的名字;
- entry 通过它链接到kset上;
- parent 指向一个kobject;
- kref kobject的引用计数;
- state_initialized 为1表示已经被初始化过
- state_in_sysfs 为1表示已经添加到sysfs文件系统中
- state_add_uevent_sent 为1表示发送过添加事件到用户态
- state_remove_uevent_sent 为1表示删除过添加事件到用户态
- uevent_suppress 为1屏蔽发送事件到用户态
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
- ktype
同一类型的某些成员和方法是相同的,例如某类内核对象的默认属性以及属性的读/写实现方法等,可以将这些共有的方法抽象出来,因此就有可kobj_type类型结构。
- release 实现这类kobject释放自己的方法,配合引用计数使用;
- sysfs_ops 实现这类kobject的读写方法,sysfs_ops 在下面会有详细描述,sysfs文件系统的读写操作对应kobject的show和store操作;
- default_attrs 表示该类对象的默认属性;
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
- kset
表示内核对象的集合,kset本身可以被当做一个kobject对象对待,用kobj成员表示,list成员将kobject组织成一个链表。kset的存在是为了在它之下的kobject有相同的操作方式。
- list 表头,用来链接所有的kobject对象;
- list_lock 遍历这个kset上所有kobject的自旋锁;
- kobj 对于kset而言也是一个kobject对象;
- uevent_ops uevent操作函数,当内核对象发生了特定事件需要通知到用户空间时,需要调用
kset_uevent_ops中的回调函数,如热插拔特性就是依靠kobject_event函数来通知的。与之对应的有kobj_uevent_env数据结构。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
- 三者之间的关系
1.kset集成了kobject,本身也可以作为kobject对待;
2.每个object有一个parent指针,通常情况下kset下包含的kobject通过它指向kset内嵌的kobject,不过也可以指向其他的object或者置为null;
3.kset下的kobject通过list双向链表串起来;
4.kobject也可以单独存在,即其kset域为NULL;
尽管内核版本在不断演进,但是kobject这套机制从2.6开始一直没有很大的变化,而基于此机制之上的如sysfs倒是有很大的改动。
kobject的释放通常通过引用计数来完成,引用计数在内核中提供如下数据结构:
struct kref {
atomic_t refcount;
};
struct kref一般都会内嵌在实际构造的内核对象中。
下面介绍下如何将kobject添加到sysfs中。
1.创建初始化内核对象
kobject_create-->kobject_init-->kobject_init_internal
kobject_create中负责创建一个kobject对象;
kobject_init中负责检查这个kobject是否重复创建;
kobject_init_internal中初始化kobject的各个字段;
struct kobject *kobject_create(void)
{
struct kobject *kobj;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;
kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}
2.将内核对象添加到sysfs文件系统
sysfs是一种表示内核对象、对象属性,以及对象关系的一种机制,一般内核对象、属性、以及对象关系组织成树状形式。其中内核对象被映射为用户态的目录;对象属性被映射为用户态的文件,文件在目录下;对象关系被映射成用户空间的符号链接。
如果koibject需要添加到sysfs中,则必须要调用kobject_add函数
- kobjset_set_name_vargs
解析fmt传入的参数,设置kobject的name,并指向传进来的parent;
- kobject_add_internal
1. 对parent的引用计数加1,确保parent不会退出;
2. 调用create_dir接口,完成sysfs的注册,其中sysfs_create_dir中负责创建目录,populate_dir中负责创建file;
3. 设置state_in_sysfs,表示已经添加到sysfs中
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
printk(KERN_ERR "kobject '%s' (%p): tried to add an "
"uninitialized object, something is seriously wrong.n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
populate_dir中逐个处理内核对象所属对象类型的默认属性,对每个属性,调用sysfs_create_file函数在内核对象的目录下创建以属性名为名字的文件。kobject_add中只会为默认属性自动创建文件。
3.创建内核对象集kset
这里不再展开与创建kobject类似。
4.发送内核对象变化事件到用户空间
当内核对象创建好以后,如果其发生了特定的事件变化,需要通知到用户态,则可以通过调用kobject_uevent函数完成,一种典型的运用就是热插拔技术。
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
enum kobject_action {
KOBJ_ADD, // 内核对象被添加 比如热插拔一个设备
KOBJ_REMOVE, // 内核对象被删除,同上
KOBJ_CHANGE, // 内核对象属性发生变化,比如分区表发生变化
KOBJ_MOVE, // 内核对象位置发生改变,比如设备名改变
KOBJ_ONLINE, // 内核对象在线,比如cpu在线或离线
KOBJ_OFFLINE,
KOBJ_MAX // 事件种类的最大值
};
kobject_action指定了内核对象发生的事件类型。
5.举个列子
可以参考内核samples/kobject/kobject-example.c写的列子
- 首先在sys/kernel目录下创建一个kobject_example目录
example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
- 对这个kobject_eaample对象设置了三个属性,其属性名分别是bar、baz、foo
static struct attribute *attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
- 设置文件的读写函数,对应的内核对象的show/store函数
列子中比较简单就是将一个整数写入,当尝试写入字符"aaa"时会报如下错误:
6.参考
- 《存储技术原理分析》
- 《linux内核探秘》
- linux kernel 4.19