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。

kobject是“被嵌入”的,不是“被继承”的。内核通过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,
};
常见误区:在release回调中再次调用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_attributekobject级属性封装__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节点,完整流程如下:

  1. 定义宿主结构体struct my_obj,内嵌struct kobject kobj。
  2. 实现my_obj_release(),在引用归零时释放宿主。
  3. 定义kobj_type,绑定release与sysfs_ops。
  4. 调用kobject_init_and_add()初始化并注册到父kobject下。
  5. 使用sysfs_create_file()创建mode属性。
  6. 卸载时调用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 生命周期与 sysfs 映射关系 struct my_obj struct kobject kobj int mode kobject 核心字段 name → "my_obj" parent → 父 kobject ktype → my_ktype kref → 引用计数 kobj_type release → my_obj_release sysfs_ops → show/store default_attrs → mode_attr 内嵌 绑定 sysfs 用户空间视图 /sys/.../my_obj/ kobject 目录 mode 属性文件 0644 cat / echo 读写触发 show/store kobject_add() kobject_init(1) → kobject_get(+1) → kobject_put(-1) → 归零 → release() → kfree(宿主) 宿主结构 kobject kobj_type sysfs 引用计数

总结来看,kobject是设备模型的通用粘合剂:它用引用计数管理生命周期,用parent/kset组织层级,用ktype绑定属性与释放逻辑,最终通过sysfs把内核对象暴露为用户可读写的文件。掌握这四者的关系,就能在设备驱动中安全地创建、使用和销毁sysfs属性节点。