4. 设备Class核心原理
在Linux设备模型中,Class(类)是一种从“功能视角”对设备进行归类的抽象机制。与Bus按物理连接方式划分设备、Device按具体硬件实例描述设备不同,Class回答的是“这个设备能提供什么功能、应该暴露给用户什么接口”。例如,所有网卡无论挂在PCI、USB还是虚拟总线上,都会归入net类;所有输入设备都会归入input类。正是Class机制,让用户空间能够以统一路径/sys/class/<class_name>/<device_name>访问设备,而不必关心底层总线拓扑。
4.1 class结构体:功能分类的载体
内核用struct class描述一个设备类,其核心字段如下:
struct class {
const char *name; /* 类名,如 "net"、"input" */
struct module *owner; /* 所属模块,用于引用计数 */
const struct attribute_group **groups; /* 类级默认属性组 */
const struct attribute_group **dev_groups; /* 类下所有设备共享的属性组 */
struct kobject *dev_kobj; /* 指向 /sys/dev 下的 kobject */
int (*dev_uevent)(const struct device *dev,
struct kobj_uevent_env *env); /* 设备uevent回调 */
void (*class_release)(struct class *class); /* 类释放回调 */
void (*dev_release)(struct device *dev); /* 设备释放回调 */
int (*shutdown_pre)(struct device *dev); /* 关机前回调 */
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(const struct device *dev);
...
};
其中name决定/sys/class/下的目录名;dev_groups是Class最实用的字段之一,它让同一类下所有设备自动获得一组属性文件,无需每个驱动重复创建;dev_uevent则允许类在设备热插拔时向用户空间追加环境变量,是udev规则的重要输入来源。
4.2 class_register与class_unregister:注册与注销流程
Class的注册通过class_register()完成,注销通过class_unregister()完成。典型用法如下:
static struct class my_class = {
.name = "mydev",
.dev_groups = my_dev_groups,
};
static int __init my_init(void)
{
int ret = class_register(&my_class);
if (ret)
return ret;
return 0;
}
static void __exit my_exit(void)
{
class_unregister(&my_class);
}
注册过程会在/sys/class/下创建以name命名的目录,并在/sys/class/的kobject层级中建立对应节点。注销时,内核会先移除该类下所有设备节点,再删除类目录,并等待引用计数归零。需要特别注意的是:class_unregister不会自动释放class结构体内存,若class是动态分配的,还需在class_release回调中释放。
class_create的等价逻辑,但class_create是宏封装,返回struct class *;而class_register接收调用者提供的结构体指针。二者不可混用释放方式。
4.3 class_attribute:类级属性的定义与导出
Class属性用于向用户空间暴露类级别的配置或状态。定义方式与设备属性类似,使用CLASS_ATTR_RW、CLASS_ATTR_RO等宏:
static ssize_t mode_show(struct class *cls,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", current_mode);
}
static ssize_t mode_store(struct class *cls,
struct class_attribute *attr,
const char *buf, size_t count)
{
strncpy(current_mode, buf, sizeof(current_mode) - 1);
return count;
}
static CLASS_ATTR_RW(mode);
static struct attribute *my_class_attrs[] = {
&class_attr_mode.attr,
NULL,
};
ATTRIBUTE_GROUPS(my_class_attrs);
将my_class_attrs赋给class.groups后,/sys/class/mydev/mode即可读写。类属性作用于整个类,而dev_groups作用于类下每个设备,二者层级不同,使用时需明确区分。
4.4 设备与Class的关联方式
设备与Class的关联通常由驱动在probe阶段完成,核心API是device_create()或device_register()配合device_add()。最常见的是:
struct device *dev;
dev = device_create(&my_class, parent, devt,
drvdata, "mydev%d", index);
if (IS_ERR(dev))
return PTR_ERR(dev);
device_create会完成三件事:创建struct device、将其加入Class、在/sys/class/mydev/下生成以设备名命名的符号链接,指向/sys/devices/...中的真实设备节点。这样用户空间既可以通过Class路径访问,也可以通过设备树路径访问。
4.5 典型场景与误区复盘
以字符设备驱动为例,常见做法是:注册cdev后,用class_create创建类,再用device_create为每个次设备号创建设备节点。这样/dev/mydev0由udev根据/sys/class/mydev/mydev0的uevent自动生成,驱动无需手动调用mknod。
| 操作 | API | sysfs效果 | 常见误区 |
|---|---|---|---|
| 注册类 | class_register() |
创建 /sys/class/<name>/ |
忘记检查返回值,导致后续device_create失败 |
| 创建设备 | device_create() |
创建符号链接与uevent | dev_t冲突或parent为空导致路径异常 |
| 添加类属性 | CLASS_ATTR_RW() |
生成 /sys/class/<name>/xxx |
与dev_groups混淆,属性挂错层级 |
| 注销类 | class_unregister() |
删除类目录 | 先注销类再销毁设备,导致空指针 |
device_destroy()销毁类下所有设备,再class_unregister()注销类,最后释放动态分配的class内存。若顺序颠倒,内核会在移除类时遍历已失效的设备链表,引发oops。
理解Class的核心在于区分三个层次:class结构体是功能分类的元数据,class_register/unregister管理类的生命周期,class_attribute与dev_groups决定用户空间能看到什么。设备通过device_create挂入Class后,sysfs中便形成“类目录—符号链接—真实设备”的三段式视图,这正是Linux设备模型对用户空间保持统一接口的关键设计。