Linux设备模型基础:kobject、kset、sysfs文件系统
说实话,我刚接触Linux设备模型那会儿,也是一头雾水。什么kobject、kset,听着就像天书。但后来我慢慢发现,这东西说白了就是内核用来管理设备的一套“户口本系统”。你想想看,一个系统里插了那么多设备,CPU怎么知道谁是谁?怎么跟它们通信?设备模型就是干这个的。
今天咱们就聊聊这个基础中的基础——kobject、kset和sysfs。我保证,看完这一章,你再看内核里的设备代码,会感觉亲切很多。
1. 为什么需要设备模型?
早期的Linux内核,设备管理其实挺乱的。每个驱动自己管自己的,没有统一规范。后来Linus他们觉得不行,得搞个统一的框架。于是就有了设备模型。
设备模型的核心目标就三个:
- 统一表示:所有设备都用同一种方式描述
- 层次化管理:设备之间有父子关系,比如USB控制器下面挂USB设备
- 用户空间可见:通过sysfs,你在用户态也能看到设备树
我在项目中遇到过一个问题:一个嵌入式板子上,两个驱动都声称控制了同一个GPIO。查了半天,就是因为没有用设备模型来管理资源。从那以后,我写驱动必先搭好设备模型框架。
2. kobject:设备模型的最小单元
kobject,全称是kernel object。它是设备模型里最小的“砖块”。你可以把它理解成每个设备在内核里的“身份证”。
看下它的结构体定义:
struct kobject {
const char *name; // 名字,比如"ttyS0"
struct list_head entry; // 链表节点,用于挂到kset里
struct kobject *parent; // 父对象,形成层次结构
struct kset *kset; // 所属的kset
struct kobj_type *ktype; // 类型,决定了sysfs里的行为
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;
unsigned int uevent_suppress:1;
};
这里我重点说几个字段:
- name:就是你在sysfs里看到的目录名
- parent:指向父对象。比如一个USB键盘,它的parent就是USB控制器
- kref:引用计数。这个很重要,防止对象被提前释放
3. kset:把kobject组织起来
kset,就是kobject的集合。你可以把它理解成一个“文件夹”,里面放了一堆同类型的kobject。
举个例子:
struct kset {
struct list_head list; // 链表头,挂载所有kobject
spinlock_t list_lock;
struct kobject kobj; // kset本身也是一个kobject
const struct kset_uevent_ops *uevent_ops;
};
注意看,kset里面包含了一个kobject成员。这说明什么?说明kset本身也是kobject,也能在sysfs里创建目录。这就是设备模型的精妙之处——递归统一。
我记得有一次调试一个PCIe驱动的热插拔问题。设备插上去后,sysfs里没有出现对应的目录。查到最后,发现是kset的uevent_ops没注册好,导致内核没发uevent事件给用户空间。嗯,这种坑踩过一次就记住了。
4. sysfs:用户空间看内核的窗口
sysfs是一个虚拟文件系统,通常挂载在/sys目录下。它把内核里的kobject层次结构,映射成了文件系统的目录树。
典型的sysfs目录结构:
/sys/
├── block/ # 块设备
├── bus/ # 总线
├── class/ # 设备类
├── dev/ # 设备号
├── devices/ # 所有设备
├── firmware/ # 固件
├── fs/ # 文件系统
├── hypervisor/ # 虚拟机
├── kernel/ # 内核参数
├── module/ # 内核模块
└── power/ # 电源管理
我个人觉得,sysfs最牛的地方在于:你在用户态cat一个文件,就能触发内核里的回调函数。比如:
# 查看CPU信息
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
# 修改LED亮度
echo 255 > /sys/class/leds/led0/brightness
这背后就是kobject的sysfs操作接口在起作用。
5. 设备模型的层次结构
说了这么多,咱们画张图来总结一下。下面这张SVG图,展示了kobject、kset和sysfs之间的关系:
从这张图可以看得很清楚:用户空间通过sysfs访问内核对象,内核对象通过kobject/kset组织成层次结构,最终映射到实际的硬件设备。
6. 实战:创建一个简单的kobject
光说不练假把式。咱们写个简单的内核模块,创建一个kobject:
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static struct kobject *my_kobj;
static int __init my_kobject_init(void)
{
int ret;
// 在/sys/kernel/下创建一个名为"my_kobject"的目录
my_kobj = kobject_create_and_add("my_kobject", kernel_kobj);
if (!my_kobj) {
pr_err("Failed to create kobject\n");
return -ENOMEM;
}
pr_info("kobject created at /sys/kernel/my_kobject\n");
return 0;
}
static void __exit my_kobject_exit(void)
{
kobject_put(my_kobj);
pr_info("kobject removed\n");
}
module_init(my_kobject_init);
module_exit(my_kobject_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple kobject example");
编译加载后,你会在/sys/kernel/下看到my_kobject目录。虽然里面是空的,但这就是设备模型的基础。
7. 避坑指南
我这些年踩过的坑,分享几个给你:
- 引用计数泄漏:每次调用kobject_get(),必须对应一个kobject_put()。我曾经漏了一次,结果那个kobject占用的内存一直释放不了。
- 命名冲突:同一个parent下,kobject的名字必须唯一。如果你创建了两个同名的kobject,第二个会失败。
- sysfs权限:默认创建的sysfs文件是root只读的。如果你想让普通用户也能读写,记得设置合适的权限位。
8. 小结
设备模型这东西,说白了就是内核用来管理设备的“骨架”。kobject是骨头,kset是关节,sysfs是皮肤——用户通过皮肤能看到骨架的样子。
我个人觉得,理解设备模型最好的方法就是多去/sys目录下逛逛。看看每个目录对应什么设备,想想它们之间的父子关系。看得多了,自然就懂了。
下一章咱们会深入platform驱动,看看设备模型怎么跟具体的硬件驱动结合起来。到时候你会发现,今天学的这些基础,全都能用上。
可可精品资料库