第三章:字符设备驱动-8:Device Registration in scull

In continuation of the previous text第三章:字符设备驱动-7:Char Device Registration, let's GO ahead.

Device Registration in scull

Internally, scull represents each device with a structure of type struct scull_dev. This structure is defined as:

在 scull 驱动内部,每个设备都用 struct scull_dev 类型的结构表示,该结构定义如下:

struct scull_dev {
    struct scull_qset *data; /* Pointer to first quantum set */
    int quantum;             /* the current quantum size */
    int qset;                /* the current array size */
    unsigned long size;      /* amount of data stored here */
    unsigned int access_key; /* used by sculluid and scullpriv */
    struct semaphore sem;    /* mutual exclusion semaphore*/
    struct cdev cdev;        /* Char device structure*/
};

We discuss the various fields in this structure as we come to them, but for now, we call attention to cdev, the struct cdev that interfaces our device to the kernel. This structure must be initialized and added to the system as described above; the scull code that handles this task is:

我们会在后续章节逐步讲解该结构中各个字段的作用,当前需重点关注 cdev 字段 —— 它是 struct cdev 类型的实例,是设备与内核交互的 “桥梁”。该字段必须按照前文介绍的方法初始化并添加到系统中,scull 驱动中负责此任务的代码如下:

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);
    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_fops;
    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

Since the cdev structure is embedded within struct scull_dev, cdev_init must be called to perform the initialization of that structure.

由于 cdev 结构是嵌入在 struct scull_dev 内部的(而非通过 cdev_alloc() 动态分配独立实例),因此必须通过 cdev_init 函数对其进行初始化 —— 该函数会初始化 cdev 的核心字段(如 ops),确保其符合内核的使用要求。

补充说明:

  1. scull_setup_cdev 函数的参数意义

    • dev:指向当前要初始化的 struct scull_dev 实例(即一个 scull 设备);

    • index:设备索引,用于计算设备号(scull_minor + index 得到当前设备的次设备号)。例如,若 scull_minor=0index=2,则次设备号为 2,对应设备文件 /dev/scull2

  2. 冗余代码 dev->cdev.ops = &scull_fops 的作用

    cdev_init 函数已将 scull_fops 赋值给 dev->cdev.ops,此处重复赋值属于 “防御性编程”—— 防止后续代码意外修改 cdev->ops 后,注册到内核的操作集失效。实际开发中可省略该句,但保留可提升代码健壮性。

  3. 嵌入 cdev 结构的优势

    将 cdev 嵌入自定义设备结构(scull_dev),而非使用独立的 cdev 实例,核心优势是便于通过 cdev 反向定位设备实例。例如,在内核提供的某些回调函数中,若仅能获取 struct cdev 指针,可通过 container_of 宏快速找到对应的 scull_dev

    // 已知 cdev 指针,获取对应的 scull_dev 实例
    struct cdev *cdev_ptr = ...;
    struct scull_dev *dev = container_of(cdev_ptr, 
                              struct scull_dev, cdev);

    这种方式避免了维护额外的指针映射表(如 “cdev 指针→设备实例” 的哈希表),简化了代码逻辑。

  4. 错误处理的特点

    该函数仅在 cdev_add 失败时通过 printk 输出错误信息,未执行复杂的回滚操作(如释放设备号)。这是因为 scull_setup_cdev 通常在模块初始化阶段调用,若 cdev_add 失败,后续初始化流程会终止,模块整体加载失败,设备号会在模块的清理函数中统一释放。

The Older Way

If you dig through much driver code in the 2.6 kernel, you may notice that quite a few char drivers do not use the cdev interface that we have just described. What you are seeing is older code that has not yet been upgraded to the 2.6 interface. Since that code works as it is, this upgrade may not happen for a long time. For completeness, we describe the older char device registration interface, but new code should not use it; this mechanism will likely go away in a future kernel.

The classic way to register a char device driver is with:

如果你查阅 2.6 内核中的大量驱动代码,可能会发现不少字符驱动并未使用我们之前介绍的 cdev 接口。这些代码是尚未升级到 2.6 新接口的旧代码,由于它们目前仍能正常工作,可能在很长一段时间内都不会被升级。为保证内容完整性,我们会介绍这种旧的字符设备注册接口,但新代码不应使用它—— 该机制在未来的内核版本中可能会被移除。

字符设备驱动注册的传统方式是使用以下函数:

int register_chrdev(unsigned int major, const char *name,
                    struct file_operations *fops);

Here, major is the major number of interest, name is the name of the driver (it appears in /proc/devices), and fops is the default file_operations structure. A call to register_chrdev registers minor numbers 0–255 for the given major, and sets up a default cdev structure for each. Drivers using this interface must be prepared to handle open calls on all 256 minor numbers (whether they correspond to real devices or not), and they cannot use major or minor numbers greater than 255.
If you use register_chrdev, the proper function to remove your device(s) from the sys-
tem is:

参数说明:

  • major:要注册的主设备号;

  • name:驱动名称(会显示在 /proc/devices 中);

  • fops:默认的 file_operations 结构。

调用 register_chrdev 会为指定的主设备号注册 0-255 号所有次设备号,并为每个次设备号创建默认的 cdev 结构。使用该接口的驱动必须准备好处理这 256 个次设备号的 open 调用(无论这些次设备号是否对应真实设备),且不能使用大于 255 的主设备号或次设备号。

若使用 register_chrdev 注册设备,从系统中移除设备的正确函数是:

int unregister_chrdev(unsigned int major, const char *name);

major and name must be the same as those passed to register_chrdev, or the call will fail.

其中,major 和 name 必须与调用 register_chrdev 时传入的值完全一致,否则该调用会失败。

补充说明:

  1. 旧接口的核心缺陷

    • 次设备号浪费:强制注册 0-255 共 256 个次设备号,即使驱动仅需使用 1-2 个,也会占用整个次设备号段,导致资源浪费;

    • 设备号范围限制:仅支持主设备号和次设备号均不超过 255 的情况,无法满足现代系统中大量设备的需求(新接口的 dev_t 支持 12 位主设备号 + 20 位次设备号);

    • 灵活性差:无法动态关联不同次设备号的 file_operations(新接口可通过 cdev_add 为不同次设备号绑定不同操作集)。

  2. 新旧接口的核心差异对比

    对比维度旧接口(register_chrdev新接口(cdev 系列函数)
    次设备号范围固定 0-255,强制占用全部可自定义(如 0-3),按需分配
    设备号上限主 / 次设备号均 ≤255主设备号 ≤4095,次设备号 ≤1048575
    cdev 管理内核自动创建默认 cdev驱动手动初始化、注册 cdev
    操作集灵活性所有次设备号共享一个 fops不同次设备号可绑定不同 fops
  3. 旧接口的兼容逻辑

    2.6 内核中 register_chrdev 并未真正消失,其内部会模拟新接口的逻辑:

    • 本质是新接口的 “简化封装”,但保留了旧接口的局限性。

    • 将传入的 fops 绑定到该 cdev,再调用 cdev_add 完成注册;

    • 为指定主设备号创建一个 “覆盖 0-255 次设备号” 的 cdev 实例;

  4. 旧接口的残留场景

    ​​​​​​​目前仅在两类旧驱动中可能看到该接口:

    • 无需多设备支持、仅需单个主设备号的极简驱动(如测试用的 “hello world” 驱动)。

    • 早期内核(2.4 及之前)开发的简单驱动(如早期的 nullzero 驱动);


技术交流,欢迎加入社区:GPUers

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值