3. kobject与sysfs
在Linux设备模型中,kobject是连接内核对象与用户空间sysfs的“骨架”。它本身不描述具体硬件,而是为设备、驱动、总线等对象提供统一的引用计数、父子层级、命名与sysfs目录映射能力。理解kobject,是掌握设备模型生命周期与sysfs属性文件创建的关键。
一、kobject结构:设备模型的通用基类
kobject定义在<linux/kobject.h>中,核心字段如下:
struct kobject {
const char *name; // sysfs目录名
struct list_head entry; // 挂入kset的链表节点
struct kobject *parent; // 父kobject,决定sysfs层级
struct kset *kset; // 所属集合
struct kobj_type *ktype; // 属性与释放回调
struct kernfs_node *sd; // sysfs目录项
struct kref kref; // 引用计数
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;
};
其中name决定sysfs中的目录名,parent决定挂载位置,ktype决定该kobject暴露哪些属性以及释放时如何回收内存,kref则负责生命周期。kobject通常不单独使用,而是内嵌在更大的结构体中,例如struct device内部就包含一个struct kobject kobj。
container_of()从kobject反推宿主结构体,这是Linux内核面向对象风格的核心手法。二、引用计数与生命周期管理
kobject使用struct kref实现原子引用计数,规则非常明确:
kobject_init():初始化kobject,引用计数置为1。kobject_add():将kobject注册到sysfs,建立目录与父节点关系。kobject_get():引用计数加1,表示“我要使用它”。kobject_put():引用计数减1,减到0时触发ktype->release回调。
生命周期管理的核心原则是:谁持有引用,谁负责释放。当引用计数归零时,内核不会自动释放kobject本身,而是调用kobj_type->release,由驱动作者在该回调中释放包含kobject的宿主结构体。若忘记实现release,内核会打印警告并导致内存泄漏。
static void my_obj_release(struct kobject *kobj)
{
struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
kfree(obj);
}
static struct kobj_type my_ktype = {
.release = my_obj_release,
.sysfs_ops = &my_sysfs_ops,
};
kobject_put(),或在release之前手动kfree()宿主结构体。前者造成递归释放,后者造成use-after-free。release是kobject生命周期的终点,只应做资源回收。三、sysfs接口与属性文件创建
sysfs是kobject在用户空间的可见形态。每个kobject对应一个目录,目录下的属性文件由struct attribute和struct sysfs_ops描述。属性分为普通属性、二进制属性和默认属性组。
| 对象 | 作用 | 关键接口 |
|---|---|---|
| struct attribute | 描述一个属性文件名与权限 | ATTRIBUTE_GROUPS() |
| struct sysfs_ops | 定义show/store读写行为 | show() / store() |
| struct kobj_attribute | kobject级属性封装 | __ATTR() |
| struct device_attribute | 设备级属性封装 | DEVICE_ATTR_RW() |
| struct bin_attribute | 二进制大对象属性 | sysfs_bin_attr_init() |
创建属性文件的典型流程是:定义show/store函数,使用__ATTR宏生成属性,放入属性组,再通过sysfs_create_group()或device_create_file()注册。读属性时内核调用show,写属性时调用store。
static ssize_t mode_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
return sprintf(buf, "%d\n", obj->mode);
}
static ssize_t mode_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
sscanf(buf, "%d", &obj->mode);
return count;
}
static struct kobj_attribute mode_attr =
__ATTR(mode, 0644, mode_show, mode_store);
注册后,用户空间即可通过/sys/.../mode读写。注意:show函数返回写入buf的字节数,store函数返回count表示全部消费;属性权限0644表示用户可读、root可写。
四、案例:一个可读写的kobject属性
假设我们要为一个虚拟对象创建sysfs节点,完整流程如下:
- 定义宿主结构体
struct my_obj,内嵌struct kobject kobj。 - 实现
my_obj_release(),在引用归零时释放宿主。 - 定义
kobj_type,绑定release与sysfs_ops。 - 调用
kobject_init_and_add()初始化并注册到父kobject下。 - 使用
sysfs_create_file()创建mode属性。 - 卸载时调用
kobject_put(),由release完成回收。
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_init_and_add(&obj->kobj, &my_ktype,
parent_kobj, "my_obj");
sysfs_create_file(&obj->kobj, &mode_attr.attr);
/* 使用阶段 */
kobject_put(&obj->kobj); // 引用归零后触发release
该案例体现了kobject的三个核心职责:在sysfs中建立目录、通过属性文件暴露参数、通过引用计数保证对象在使用期间不被释放。
五、误区与复盘
- 忘记kobject_put():引用计数永不归零,release不执行,内存泄漏。
- 在release中访问已销毁的父对象:父对象可能已先释放,应确保层级释放顺序正确。
- 属性show返回错误值:应返回实际写入buf的字节数,否则用户空间读取异常。
- 直接kfree宿主结构体:绕过release会导致sysfs残留和引用计数错乱。
- 混淆kobject与kset:kset是kobject的集合,提供uevent和默认属性,不是必须使用。
ls -l /sys/...确认目录与属性权限;通过echo和cat验证store/show;开启CONFIG_DEBUG_KOBJECT可捕获引用计数与release异常。总结来看,kobject是设备模型的通用粘合剂:它用引用计数管理生命周期,用parent/kset组织层级,用ktype绑定属性与释放逻辑,最终通过sysfs把内核对象暴露为用户可读写的文件。掌握这四者的关系,就能在设备驱动中安全地创建、使用和销毁sysfs属性节点。