【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` 指针建立层次关系

```c
struct class {
    struct kobject kobj;      // 使得 /sys/class/xxx 出现
    ...
};

struct device {
    struct kobject kobj;      // 使得 /sys/devices/... 出现
    struct class *class;      // 指向所属 class
    dev_t devt;               // 设备号(若有)
    ...
};

struct cdev {
    struct kobject kobj;      // cdev 本身也可以有 sysfs 表示(如 /sys/module/.../holders)
    const 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`(父设备,用于构建设备树)
  - `kobj`(sysfs 入口)
- 当驱动调用 `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` 结构分离,但它们通过**共同的设备号**建立起隐式联系。

---

### 🧩 典型驱动初始化流程(结合三者)

```c
static 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/myclass)
    my_class = class_create(THIS_MODULE, "myclass");

    // 4. 创建 device(/sys/class/myclass/mydevice,以及 /dev/mydevice)
    my_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 属性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐