字符设备、class 和 kobject 之间的关系
【class/device/cdev】在 Linux 设备模型中**字符设备 (cdev)**、**class** 和 **kobject** 分别属于**不同层次的概念**但它们会协同工作共同构建一个完整的、可在用户空间访问的设备。| 概念 | 所属子系统 | 核心作用 | 用户空间可见 ||------|-----------|----------|--------------|| **kobject** | 设备模型基础 | 提供引用计数、父子关系、sysfs 入口 | 在 /sys/ 下表现为一个目录 || **class** | 设备模型高级抽象 | 按功能对设备进行分组如 tty, input | 在 /sys/class/class_name/ 下 || **device** | 设备模型核心 | 代表一个具体的硬件设备内嵌 kobject | 在 /sys/devices/ 以及 /sys/class/... 下作为符号链接 || **cdev** | 字符设备子系统 | 关联设备号与 file_operations提供读写接口 | 不直接可见但通过 /dev 节点间接访问 |---### 关系图解┌─────────────────────────────────────────────────────────────────┐│ Linux 设备模型 │├─────────────────────────────────────────────────────────────────┤│ kobject (基础) ││ ├── 引用计数、sysfs 表示 ││ └── 嵌入于class, device, cdev ... │├─────────────────────────────────────────────────────────────────┤│ class device ││ (例如 my_class) (例如 my_device) ││ │ │ ││ └── 包含多个 device ────────────┘ ││ device 也属于某个 bus、parent 等 │├─────────────────────────────────────────────────────────────────┤│ cdev (字符设备) ││ ├── 本身包含 kobject ││ ├── 通过设备号 (dev_t) 与 device 隐式关联 ││ └── 提供 file_operations (open/read/write) │└─────────────────────────────────────────────────────────────────┘│▼用户空间访问路径/sys/class/my_class/my_device/ (class/device 信息)/dev/my_device (设备节点由 devtmpfs 创建)---### 三者之间的协作关系#### 1. kobject 是所有设备模型对象的根基struct kobject 被嵌入在 class、device、cdev 等结构体中使它们具有以下能力- 引用计数管理生命周期- 在 sysfs 中呈现为目录- 通过 parent 指针建立层次关系cstruct class {struct kobject kobj; // 使得 /sys/class/xxx 出现...};struct device {struct kobject kobj; // 使得 /sys/devices/... 出现struct class *class; // 指向所属 classdev_t devt; // 设备号若有...};struct cdev {struct kobject kobj; // cdev 本身也可以有 sysfs 表示如 /sys/module/.../holdersconst struct file_operations *ops;dev_t dev;unsigned int count;};#### 2. class 是设备的“分组标签”- 一个 class 代表一类设备例如 tty, gpio, input。- 通过 class_create() 创建在 /sys/class/class_name/ 下生成目录。- device 可以通过 device_create(class, ...) 加入某个 class此时 /sys/class/class_name/device_name/ 会创建一个指向真实设备的符号链接。#### 3. device 是硬件实体在软件中的抽象- device 结构体描述了一个具体的硬件或虚拟设备。- 它包含- devt设备号如果是字符设备- class所属类- parent父设备用于构建设备树- kobjsysfs 入口- 当驱动调用 device_create() 时- 在 /sys/devices/... 下创建设备目录。- 如果有 class在 /sys/class/class/device/ 下创建符号链接。- 如果 devt 有效即提供了设备号会触发 devtmpfs 在 /dev/ 下创建对应的设备节点文件。#### 4. cdev 是字符设备驱动与内核的“注册凭证”- cdev 负责将**设备号范围**与 **file_operations** 绑定。- 当应用程序 open(/dev/mydevice) 时- VFS 根据 inode-i_rdev 得到设备号。- 内核通过全局哈希表cdev_map查找该设备号对应的 cdev。- 获得 cdev-ops 并替换 filp-f_op。- **cdev 并不直接关联 class 或 device**但是- 一个常见的做法是先分配设备号 → 注册 cdev → 调用 device_create(..., devt, ...)。- 这样 device 拥有了设备号而 device_create 内部会设置 device-devt devt并调用 devtmpfs_create_node() 创建设备节点。- 因此虽然 cdev 和 device 结构分离但它们通过**共同的设备号**建立起隐式联系。---### 典型驱动初始化流程结合三者cstatic dev_t my_devno;static struct cdev my_cdev;static struct class *my_class;static struct device *my_device;static int __init my_init(void){// 1. 分配设备号alloc_chrdev_region(my_devno, 0, 1, mydev);// 2. 初始化 cdev 并添加字符设备接口cdev_init(my_cdev, my_fops);my_cdev.owner THIS_MODULE;cdev_add(my_cdev, my_devno, 1);// 3. 创建 class/sys/class/myclassmy_class class_create(THIS_MODULE, myclass);// 4. 创建 device/sys/class/myclass/mydevice以及 /dev/mydevicemy_device device_create(my_class, NULL, my_devno, NULL, mydevice);return 0;} ✅ 执行 device_create 时会利用传入的 my_devno 在 devtmpfs 中创建 /dev/mydevice 节点。 ✅ 当用户 open(/dev/mydevice)内核能够根据设备号 my_devno 找到已注册的 my_cdev从而调用 my_fops 中的函数。---### 总结它们的关系是“分工协作部分解耦”| 对象 | 职责 | 与其他对象的联系 ||------|------|------------------|| **kobject** | 设备模型的基础构件提供 sysfs 和引用计数 | 被 class、device、cdev 嵌入 || **class** | 按功能分组设备组织 /sys/class 目录 | 通过 device-class 与 device 关联 || **device** | 代表一个真实或虚拟硬件管理电源、休眠等 | 通过 devt 与 cdev 隐式关联通过 class 参与分类 || **cdev** | 提供字符设备的 file_operations 接口 | 通过设备号dev_t与 device 隐式关联不直接依赖 class | 关键理解**字符设备的接口能力cdev与设备的分类/生命周期管理class/device是正交的**它们通过设备号这座“桥梁”连接。这也是为什么你可以注册一个字符设备cdev_add而不创建 class/device但那样就不会自动在 /dev 下生成节点需要手动 mknod。现代驱动推荐二者结合获得自动节点管理和 sysfs 属性。