structkobject { const char *name; // 对象名称,会作为 sysfs 目录名 struct list_head entry; // 用于将该对象加入 kset 的链表 struct kobject *parent; // 指向父对象的指针,构建层次结构 struct kset *kset; // 指向所属的 kset 集合 struct kobj_type *ktype; // 指向对象类型描述符,定义共同行为 struct kernfs_node *sd; // 指向 sysfs 中对应的目录项(VFS 层节点) struct kref kref; // 引用计数,管理对象生命周期 unsigned int state_initialized:1; // 初始化状态位 unsigned int state_in_sysfs:1; // 是否已添加到 sysfs unsigned int state_add_uevent_sent:1; // 添加事件已发送标志 unsigned int state_remove_uevent_sent:1;// 移除事件已发送标志 unsigned int uevent_suppress:1; // 是否抑制发送 uevent 事件};
🔍 扩充:最后5个1 bit字段state_*是kobject的生命周期状态:
大多数内核对象结构体内部都内嵌了一个kobject成员。例如:
源码位置:include/linux/device.h
structdevice { struct device *parent; struct device_privatet *p; struct kobject kobj; // <-- device 通过内嵌 kobject 接入设备模型 const char *init_name; const struct device_type *type; struct bus_type *bus; struct device_driver *driver;dev_tdevt; struct class *class; void (*release)(structdevice *dev); /* ... 省略 ... */};// 从 kobject 反推所在 device(典型用法)struct device *dev = container_of(kobj, struct device, kobj);
2.2 kernfs_node
核心功能
在Linux内核中,kernfs_node是kernfs子系统定义的数据结构,是构建kernfs层次结构的基本单元。无论是/sys下的一个子目录,还是一个普通属性文件,在kernfs内部都由一个kernfs_node结构体来表示
桥梁作用
kernfs_node是连接内核对象模型(kobject)和用户空间文件系统(sysfs)的桥梁。 当一个kobject被注册并需要在sysfs中创建目录时,kernfs会创建一个对应的kernfs_node。这个kernfs_node会存储一个指向其背后kobject的指针。反过来,kobject结构体中也有一个名为sd(sysfs directory entry)的成员,指向关联的kernfs_node。 通过这种双向关联,kernfs_node将内核对象(kobject)与其在sysfs中的表示(目录或文件)联系在一起
源码位置:include/linux/kernfs.h
// 定义在 include/linux/kernfs.hstruct kernfs_node { const char *name; /* 节点名称(目录名/文件名) */ struct rb_node rb; /* 红黑树节点,用于在父目录中按名称查找 */ struct kernfs_node *parent; /* 指向父节点的指针 */ unsigned int hash; /* 名称哈希值,加速查找 */ const void *ns; /* 命名空间标签,用于支持命名空间隔离 */ union { struct kernfs_elem_dir dir; /* 目录特有信息 */ struct kernfs_elem_symlink symlink; /* 符号链接特有信息 */ struct kernfs_elem_attr attr; /* 文件(属性)特有信息 */ }; void *priv; /* 私有数据,可以存储kobject */ unsigned int flags; /* 节点标志(类型、状态等) */ umode_t mode; union kernfs_node_id id; struct kernfs_iattrs *iattr; /* inode 属性(权限、时间戳等),延迟分配 */ atomic_t count; /* 引用计数,管理生命周期 */ atomic_t active; /* 活动引用计数,用于保护正在进行的操作 */};
🔍 扩充:kernfs与sysfs的分层:早期的sysfs直接基于ramfs/kobject实现,代码耦合严重。内核后来抽出了独立的kernfs层(sysfs、cgroup、pstore、tracefs都建在它之上)
VFS接口(用户空间) →
sysfs层(把kobject映射到目录/文件) →
kernfs层(伪文件系统树)
kobject与kernfs_node 是双向绑定:kobj->sd 指向目录节点,kn->priv 指回 kobject(后面源码会看到)三、kobject_create_and_add
本小节介绍内核内核提供的工具函数,用来辅助理解内核kobject结构体的功能之一,通过sysfs虚拟文件系统实现与用户空间的通信。该工具函数动态创建一个kobject,并将其注册到sysfs中,实现在/sys下创建一个目录节点。该函数的调用链,以及对上一小节介绍的两个结构体对象进行的操作如下:
kobject_create_and_add()
├── kobject_create() // 1. 创建并初始化kobject
│ └── kzalloc() // 1.1 分配内存
│ └── kobject_init() // 1.2 初始化结构体
│ ├── kref_init() // 1.2.1 初始化引用计数为1
│ └── INIT_LIST_HEAD()// 1.2.2 初始化链表节点
└── kobject_add() // 2. 添加到内核并关联sysfs
└── kobject_add_varg() // 2.1 处理可变参数
└── kobject_set_name_vargs() // 2.1.1 设置kobject的名称
└── kobject_add_internal() // 2.2 核心添加操作
├── kset检查,加入kset链表 // 2.2.1 处理父子关系
└── create_dir() // 2.2.2 在sysfs中创建目录
├── sysfs_create_dir() // 在sysfs中创建目录项
├── populate_dir() // 填充default_attrs属性
└── sysfs_create_groups() // 填充default_groups属性
kobject结构体
源码位置:/lib/kobject.c
/*** 动态创建kobject并将其注册到sysfs* @name: kobject的名称* @parent: 该kobject的父kobject指针*/struct kobject *kobject_create_and_add(const char *name, struct kobject *parent){ struct kobject *kobj; int retval; // 第一步:动态创建并初始化一个kobject kobj = kobject_create(); if (!kobj) return NULL; // 第二步:将创建好的kobject添加到设备模型并注册到sysfs retval = kobject_add(kobj, parent, "%s", name); if (retval) { printk(KERN_WARNING"%s: kobject_add error: %d\n", __FUNCTION__, retval); // 如果添加失败,释放引用计数,触发清理 kobject_put(kobj); kobj = NULL; } return kobj;}EXPORT_SYMBOL_GPL(kobject_create_and_add);
3.1 kobject_create
kobject_create用于动态分配并初始化一个kobject实例:
源码位置:/lib/kobject.c
structkobject *kobject_create(void){ struct kobject *kobj; kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); if (!kobj) return NULL; // 使用默认的 dynamic_kobj_ktype 进行初始化 kobject_init(kobj, &dynamic_kobj_ktype); return kobj;}EXPORT_SYMBOL_GPL(kobject_create);
3.1.1 对象行为:kobj_sys_ops
kobject_init函数传入的全局变量dynamic_kobj_ktype定义如下:
源码位置:/lib/kobject.c
staticconststructkobj_typedynamic_kobj_ktype = {
// 引用计数器为0时,释放内存
.release = dynamic_kobj_release, .sysfs_ops = &kobj_sysfs_ops,};
kobj_sysfs_ops本身一直是内核中一个全局的、标准的sysfs操作函数集。它的作用是为那些没有自定义sysfs_ops的 kobject 提供一套默认的、通用的读写方法:
源码位置:/lib/kobject.c
conststructsysfs_opskobj_sysfs_ops = { .show = kobj_attr_show, // 取出数据 .store = kobj_attr_store, // 存入数据};
3.1.2 核心机制:读写操作转发
kobj_attr_show和kobj_attr_store这两个是静态函数。它们的核心逻辑是转发。它们自己不处理具体的读写操作,而是从标准的属性struct attribute推导出更具体的struct kobj_attribute,然后调用后者中由开发者定义的show和store函数
源码位置:/lib/kobject.c
// 读操作转发函数static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf){ struct kobj_attribute *kattr; ssize_t ret = -EIO; // 通过 container_of 从基础类型 attribute 获取包含它的 kobj_attribute kattr = container_of(attr, struct kobj_attribute, attr); if (kattr->show) ret = kattr->show(kobj, kattr, buf); // 调用开发者自定义的 show 函数 returnret;}// 写操作转发函数 (逻辑完全相同)static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count){ struct kobj_attribute *kattr; ssize_t ret = -EIO; kattr = container_of(attr, struct kobj_attribute, attr); if (kattr->store) ret = kattr->store(kobj, kattr, buf, count); // 调用开发者自定义的 store 函数 return ret;}
💡container_of sysfs的回调使用通用的struct attribute *,但开发者使用的带函数指针struct kobj_attribute *。内核通过container_of宏,从成员attr反推出外层结构体,从而找回用户定义的 show/store。这是c语言实现面向对象多态的手法3.1.3 转发机制:kobj_attribute
为了实现上述的转发机制,内核定义了struct kobj_attribute。它是一个标准属性(struct attribute)加上对应的两个读取和写入操作函数指针
源码位置:include/linux/sysfs.h
structkobj_attribute { struct attribute attr; // 嵌入一个标准属性,包含 char* name 和 short mode ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf); ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);};
3.2 kobject_add
kobject_add是Linux内核设备模型中将一个已初始化的kobject正式注册到系统中的函数。它配置了kobject的name字段,负责将kobject连接到设备模型的树形结构中,并在sysfs中创建对应的目录
源码位置:/lib/kobject.c
intkobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...) {va_listargs; int retval; // 检查空指针等 va_start(arg, fmt); retval = kobject_add_varg(kobj, parent, fmt, args); va_end(args); return retval;}
内部的函数调用链为:
调用链(伪代码)
kobject_add() --> kobject_add_varg() // 处理变参,设置名称 --> kobject_add_internal() // 核心实现 --> create_dir() // 创建 sysfs 目录
3.2.1 kobject_add_internal 函数
该函数是kobject_add函数中调用的核心函数:
源码位置:/lib/kobject.c
staticint kobject_add_internal(struct kobject *kobj) { int error = 0; struct kobject *parent; // 省略错误检查... parent = kobject_get(kobj->parent); // 增加 parent 的 kref 引用计数 if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); // 如果未设置 parent,则使用 kset 的 kobject 作为 parent kobj_kset_join(kobj); // 将 kobject 加入 kset 的链表 kobj->parent = parent; } // 在父目录 parent 下创建目录 error = create_dir(kobj); return error;}
🔍 扩充:parent与kset的优先级 内核设计的顺序:显式parent优先;只有当parent为空且属于某个kset时,内核才用kset->kobj顶上父节点。这就是为什么很多子系统只要把kobj->kset设好,就能自动在正确目录下建出子目录,无需手动指定 parent3.2.2 create_dir 函数
源码位置:/lib/kobject.c
// 定义在 /lib//kobject.c 文件中static int create_dir(struct kobject *kobj) { const struct kobj_type *ktype = get_ktype(kobj); const struct kobj_ns_type_operations *ops; // 1.为kobj创建目录节点 sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); // 2.实际上没有操作 populate_dir(kobj); if (ktype) sysfs_create_groups(kobj, ktype->default_groups);}
3.2.2.1 sysfs_create_dir_ns 创建目录节点
在sysfs文件系统中为kobject创建目录节点,后续所有属性文件都在此目录下创建。
此函数将kobject->sd成员变量设置为kn当前创建的目录节点
源码位置:/lib/kobject.c
/*** @kobj: 目标 kobject* @ns: 命名空间标签(可选)* 返回值:成功返回 0,失败返回负错误码。*/int sysfs_create_dir_ns(struct kobject *kobj, const void *ns){ struct kernfs_node *parent, *kn; kuid_t uid; kgid_t gid; /* 如果 kobject 有父对象,则父目录为父 kobject 的 kernfs 节点;否则为 sysfs 根目录 */ if (kobj->parent) parent = kobj->parent->sd; else parent = sysfs_root_kn; /* 获取该 kobject 目录的所有者(uid/gid) */ kobject_get_ownership(kobj, &uid, &gid); /* 调用 kernfs 创建目录节点 */ kn = kernfs_create_dir_ns(parent, kobject_name(kobj), S_IRWXU | S_IRUGO | S_IXUGO, uid, gid, kobj, ns); if (IS_ERR(kn)) { if (PTR_ERR(kn) == -EEXIST) sysfs_warn_dup(kobject_name(kobj)); return PTR_ERR(kn); } /* 将新创建的 kernfs 节点指针保存到 kobject 中 */ kobj->sd = kn; return 0;}EXPORT_SYMBOL_GPL(sysfs_create_dir_ns);
3.2.2.2 kernfs_create_dir_ns 文件系统目录物理创建
该函数完成kernfs_node内核节点的创建,并将其挂入父目录的红黑树中。
源码位置:fs/kernfs/dir.c
structkernfs_node *kernfs_create_dir_ns(structkernfs_node *parent, constchar *name, umode_t mode, kuid_t uid, kgid_t gid, void *priv, constvoid *ns){ struct kernfs_node *kn; int rc; /* 1. 参数校验:父节点必须存在 */ if (!parent) return ERR_PTR(-EINVAL); /* 2. 创建新的 kernfs 节点,类型为目录 */ kn = kernfs_new_node(parent, name, mode | S_IFDIR, uid, gid, KERNFS_DIR); if (!kn) return ERR_PTR(-ENOMEM); /* 3. 设置私有数据和命名空间标签 */ kn->priv = priv; kn->ns = ns; /* 4. 将新节点添加到父目录的红黑树中 */ rc = kernfs_add_one(kn); if (rc) { kernfs_put(kn); return ERR_PTR(rc); } /* 5. 返回新节点 */ return kn;}EXPORT_SYMBOL_GPL(kernfs_create_dir_ns);
🔍 扩充:红黑树+哈希,查找O(logn) kernfs 用红黑树(kn->rb)在父目录下按名字组织子节点,配合kn->hash名字哈希,使按名查找目录项高效且稳定。同时kn->ns命名空间标签让同一父目录下可以存在同名但不同命名空间的节点(例如不同网络命名空间里的网络设备在sysfs中互不冲突)3.2.3 populate_dir
该函数是在kobject注册时自动调用的内部辅助函数,用于创建“默认属性”
由于kobject_create_and_add()函数创建kobject时没有定义属性文件,所以不会执行
源码位置:/lib/kobject.c
/*** @kobj: 目标 kobject* 返回值:成功返回 0,失败返回负错误码。*/static int populate_dir(struct kobject *kobj){ struct kobj_type *t = get_ktype(kobj); const struct attribute *attr; int error = 0; int i; if (t && t->default_attrs) { for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) { error = sysfs_create_file(kobj, attr); if (error) break; } if (error) while (--i >= 0) sysfs_remove_file(kobj, t->default_attrs[i]); } return error;}
3.2.4 sysfs_create_groups
该函数是内核sysfs核心API,用于在指定kobject下创建一个或多个属性组
工作方式:它接收一个attribute_group指针数组(以 NULL 结束)。每个组可以包含多个属性,并拥有独立的子目录(指定name),能对属性进行分类管理。
原子性与错误处理:具备原子性。创建过程中任何一步出错,它会自动移除所有已创建的组,将sysfs状态回滚到函数调用之前
使用场景:它是当前驱动开发的主流方式。在kobject或device注册时,常通过ktype->default_groups或device->groups来使用,实现属性的统一管理和自动清理
由于kobject_create_and_add()函数创建kobject时没有定义属性文件,所以不会执行
源码位置:/lib/kobject.c
/*** @kobj: 目标 kobject* @groups: 以 NULL 结尾的属性组指针数组* 返回值:成功返回 0,失败返回负错误码。*/int __must_check sysfs_create_groups(struct kobject *kobj, const struct attribute_group **groups){ int i, error = 0; if (!groups) return0; for (i = 0; groups[i] && !error; i++) error = Internal_create_group(kobj, update, groups[i]); if (error) while (--i >= 0) sysfs_remove_group(kobj, groups[i]); return error;}EXPORT_SYMBOL_GPL(sysfs_create_groups);
四、引用计数kref与对象生命周期
kobject的自动释放能力,底层来自struct kref。kref本质就是对一个atomic_t原子计数器的封装,支持无锁的get/多线程安全的put
include/linux/kref.h
// include/linux/kref.hstruct kref { atomic_t refcount;};static inline void kref_init(struct kref *kref){ atomic_set(&kref->refcount, 1);}static inline void kref_get(struct kref *kref){ atomic_inc(&kref->refcount);}
源码定义在lib/kref.c:kref_get原子加一;kref_put原子减一并测试,若减到 0 就调用用户传入的release回调释放对象
lib/kref.c(简化)
// lib/kref.c (简化自内核实现)void kref_get(struct kref *kref){ WARN_ON(!atomic_read(&kref->refcount)); atomic_inc(&kref->refcount); smp_mb__after_atomic();}int kref_put(struct kref *kref, void (*release)(struct kref *kref)){ WARN_ON(release == NULL); if (atomic_dec_and_test(&kref->refcount)) { release(kref); /* 计数归零,释放对象 */ return 1; } return 0;}
kobject在自己的kref之上封装了kobject_get/kobject_put,并在put归零时走kobject_release→kobject_cleanup,最终调用用户ktype->release里定义的释放函数:
lib/kobject.c
// lib/kobject.cstruct kobject *kobject_get(struct kobject *kobj){ if (kobj) kref_get(&kobj->kref); return kobj;}void kobject_put(struct kobject *kobj){ if (kobj) { if (!kobj->state_initialized) WARN(1, KERN_WARNING"kobject: '%s' is not initialized, " "yet kobject_put() is being called.\n", kobject_name(kobj)); kref_put(&kobj->kref, kobject_release); }}static void kobject_release(struct kref *kref){ kobject_cleanup(container_of(kref, struct kobject, kref));}
五、kset与uevent热插拔机制
kset是kobject的集合容器。它本身内嵌一个kobject(所以也会在sysfs中占一个目录),并持有一组uevent_ops回调,负责在集合内对象增删时向用户空间广播事件
include/linux/kobject.h
// include/linux/kobject.hstruct kset { struct list_head list; // 链表头,串起本集合内所有 kobjectspinlock_t list_lock; // 保护链表的自旋锁 struct kobject kobj; // kset 内嵌的 kobject,自身在 sysfs 中也占一个目录 const struct kset_uevent_ops *uevent_ops; // uevent 过滤/构造回调};struct kset_uevent_ops { int (*filter)(struct kset *kset, struct kobject *kobj); // 是否允许发送 const char *(*name)(struct kset*kset, struct kobject *kobj); int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env); // 追加环境变量};
典型的 kset 创建方式:
lib/kobject.c
// lib/kobject.cstruct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj){ struct kset *kset; int error;kset = kset_create(name, uevent_ops, parent_kobj); if (!kset) return NULL; error = kset_register(kset); if (error) { kfree(kset); return NULL; } return kset;}EXPORT_SYMBOL_GPL(kset_create_and_add);
当设备插入/拔出时,内核通过kobject_uevent()触发事件。下面是kobject_uevent_env的部分代码,包含一次uevent的完整生命周期:
lib/kobject_uevent.c(裁剪)
// lib/kobject_uevent.c (核心调用链裁剪)int kobject_uevent(struct kobject *kobj, enum kobject_actionaction){ return kobject_uevent_env(kobj, action, NULL);}int kobject_uevent_env(struct kobject *kobj, enum kobject_actionaction, char *envp_ext[]){ struct kobj_uevent_env *env; const char *action_string = kobject_actions[action]; const char *devpath = NULL; struct kset *kset = NULL; /* 1. 向上回溯,找到所属的最顶层 kset */ struct kobject *top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent; if (!top_kobj->kset) return -EINVAL; /* 没有 kset 就无法发送 uevent */ kset = top_kobj->kset; /* 2. uevent_suppress 为真则丢弃事件 */ if (kobj->uevent_suppress) return 0; /* 3. 通过 kset->uevent_ops->filter 决定该对象是否允许上报 */ if (kset->uevent_ops && kset->uevent_ops->filter) if (!kset->uevent_ops->filter(kset, kobj)) return 0; /* 4. 构造环境变量:ACTION / DEVPATH / SUBSYSTEM / SEQNUM ... */ add_uevent_var(env, "ACTION=%s", action_string); add_uevent_var(env, "DEVPATH=%s", devpath); add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum); /* 5. 通过 Netlink 广播到用户空间 (NETLINK_KOBJECT_UEVENT = 15) */ return kobject_uevent_net_broadcast(kobj, env, action_string, devpath);}
🌐 uevent如何到达用户空间
内核通过Netlink协议NETLINK_KOBJECT_UEVENT把事件广播出去,用户空间的udev/systemd-udevd监听该 socket,按规则(/etc/udev/rules.d)匹配ACTION、SUBSYSTEM、DEVPATH等环境变量,执行建节点、加载模块、跑脚本等动作
六、container_of —— 内核面向对象的基石
前面多次看到container_of。它是设备模型以成员反推宿主的宏,也是C语言实现继承/多态的基石:只要结构体里内嵌了某成员,就能从成员的指针反推出外层大结构体的指针
include/linux/kernel.h
// include/linux/kernel.h#definecontainer_of(ptr, type, member) ({ \ consttypeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); })// 实际使用示例:从 attribute 反推包含它的 kobj_attributestructkobj_attribute *kattr;kattr = container_of(attr, structkobj_attribute, attr);
七、写一个内核模块,在sysfs导出可读写属性
本小节完成内核模块的一个简单案例。模块加载后会在/sys/kernel/kobject_example下创建foo、baz、bar 三个文件,向文件写数字即可读出,相当于给用户空间开了一个配置内核变量的窗口
sp-kobject-example.c
#include<linux/module.h>#include <linux/kernel.h>#include <linux/kobject.h>#include <linux/sysfs.h>#include <linux/init.h>static int foo;static int baz;static int bar;static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ return sprintf(buf, "%d\n", foo);}static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){ sscanf(buf, "%d", &foo); return count;}static struct kobj_attribute foo_attribute = __ATTR(foo, 0666, foo_show, foo_store);static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ int var; if (strcmp(attr->attr.name, "baz") == 0) var = baz; else var = bar; return sprintf(buf, "%d\n", var);}static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){ int var; sscanf(buf, "%d", &var); if (strcmp(attr->attr.name, "baz") == 0) baz = var; else bar = var; return count;}static struct kobj_attribute baz_attribute = __ATTR(baz, 0666, b_show, b_store);static struct kobj_attribute bar_attribute = __ATTR(bar, 0666, b_show, b_store);static struct attribute *attrs[] = { &foo_attribute.attr, &baz_attribute.attr, &bar_attribute.attr, NULL,};static struct attribute_groupattr_group = { .attrs = attrs,};static struct kobject *example_kobj;static int __init example_init(void){ int retval; example_kobj = kobject_create_and_add("kobject_example", kernel_kobj); if (!example_kobj) return -ENOMEM; retval = sysfs_create_group(example_kobj, &attr_group); if (retval) kobject_put(example_kobj); return retval;}static void __exit example_exit(void){ kobject_put(example_kobj);}module_init(example_init);module_exit(example_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Greg Kroah-Hartman");
模块加载后可以查看创建的3个属性文件
ls /sys/kernel/kobject_example/
通过echo和cat命令可以对属性文件进行操作
八、device / driver 内嵌 kobject
前面说过kobject几乎不单独存在。以struct device为例,其内部内嵌了struct kobject kobj,因此每个设备自动获得:引用计数、sysfs 目录(/sys/devices/...)、uevent 能力,以及通过container_of(kobj, struct device, kobj)从kobject反推回device的能力。
include/linux/device.h(节选)
// include/linux/device.h (节选)struct device { struct device *parent; struct device_private *p; struct kobject kobj; // <-- device 通过内嵌 kobject 接入设备模型 const char *init_name; const struct device_type *type; struct bus_type *bus; struct device_driver *driver; dev_t devt; struct class *class; void (*release)(structdevice *dev); /* ... */};// 从 kobject 反推所在 device(典型用法)struct device *dev = container_of(kobj, struct device, kobj);
总线、设备、驱动、class全都内嵌kobject,它们能在同一棵sysfs树里被统一管理,也能用同一套引用计数/uevent机制
文章内容为作者过往学习的笔记,接下来会按期更新,预计下一期更新与kobject紧密相关的属性kobj_attribute。如果本文对你有帮助,欢迎点赞、在看、转发,让更多 Linux 开发者受益 🚀