字符设备注册

register_chrdev 和 cdev_init + cdev_add 是 Linux 内核中用于注册字符设备的两种不同方法。它们在 使用场景 和 实现方式 上有显著区别。

register_chrdev

特点

  • 一次性注册:
    • 通过一个函数调用完成字符设备的注册。
    • 自动分配主设备号和创建设备节点(如果使用 devfs 或 udev)。
  • 简单易用:
    • 适合简单的字符设备驱动,代码量少,开发速度快。
  • 静态分配:
    • 通常用于静态分配主设备号,无法灵活管理多个次设备号。
  • 旧版接口:
    • 是 Linux 内核早期提供的接口,逐渐被 cdev_init + cdev_add 取代。

使用场景

  • 适用于简单的字符设备驱动。
  • 当设备只需要一个主设备号,且不需要管理多个次设备号时。
  • 适合快速原型开发或学习阶段的驱动程序。

cdev_init + cdev_add

特点

  • 分步注册:
    • cdev_init 初始化字符设备结构体。
    • cdev_add 将字符设备注册到内核中。
  • 灵活管理:
    • 支持动态分配主设备号和次设备号。
    • 可以管理多个次设备号(如多个设备实例)。
  • 现代接口:
    • 是 Linux 内核推荐的字符设备注册方式,功能更强大,灵活性更高。
  • 与设备模型集成:
    • 可以与内核的设备模型(如 sysfs、udev)集成,支持设备节点的动态创建和管理。

使用场景

  • 适用于复杂的字符设备驱动。
  • 当设备需要动态分配主设备号或管理多个次设备号时。
  • 当需要与内核的设备模型集成(如通过 device_create 创建设备节点)时。
  • 适合生产环境中的驱动程序开发。

主要区别

特性 REGISTER_CHRDEV CDEV_INIT + CDEV_ADD
注册方式 一次性注册 分步注册
设备号管理 静态分配主设备号 动态分配主设备号和次设备号
次设备号支持 不支持管理多个次设备号 支持管理多个次设备号
设备模型集成 不支持内核设备模型(如 sysfs、udev) 支持内核设备模型
灵活性和功能 功能简单,灵活性较低 功能强大,灵活性高
适用场景 简单设备驱动,快速原型开发 复杂设备驱动,生产环境开发
内核版本支持 旧版接口,逐渐被淘汰 现代接口,推荐使用

总结

  • register_chrdev:
    • 简单易用,适合快速开发简单的字符设备驱动。
    • 功能有限,不支持动态设备号管理和内核设备模型集成。
    • 逐渐被淘汰,不建议在新代码中使用。
  • cdev_init + cdev_add:
    • 灵活强大,适合开发复杂的字符设备驱动。
    • 支持动态设备号管理、多个次设备号和内核设备模型集成。
    • 是现代 Linux 内核推荐的字符设备注册方式。

在实际开发中,建议优先使用 cdev_init + cdev_add,因为它提供了更强大的功能和更高的灵活性,能够更好地满足现代设备驱动的需求。

创建设备节点

旧方法:mknod, 新方法:device_create。

mknod

函数原型

int mknod(const char *pathname, mode_t mode, dev_t dev);

使用场景

用户空间手动创建设备节点

mknod /dev/my_device c 240 0

缺点

  • 手动管理:需要手动创建设备节点,增加了使用复杂度。
  • 权限问题:需要手动设置权限,可能导致权限不足或安全问题。
  • 缺乏动态性:设备节点无法动态创建或删除,无法适应设备的热插拔。

device_create

函数原型

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

使用场景

  • 在内核驱动中自动创建设备节点。
    device_create(my_class, NULL, dev, NULL, "my_device");

优点

  • 自动管理:设备节点由内核自动创建,无需手动干预。
  • 权限控制:设备节点的权限由内核自动设置,符合安全规范。
  • 动态支持:支持设备的热插拔,设备节点可以动态创建或删除。
  • 集成设备模型:与内核的设备模型(如 sysfs、udev)集成,提供更强大的功能。

为什么要使用新函数?

  • 自动化和动态化 新方法通过内核自动创建设备节点,支持设备的热插拔和动态管理,减少了手动操作的复杂性。
  • 权限和安全性 新方法由内核自动设置设备节点的权限,避免了手动设置权限可能带来的安全问题。
  • 集成设备模型 新方法与内核的设备模型(如 sysfs、udev)紧密集成,提供了更强大的设备管理功能(如属性文件、事件通知等)。
  • 一致性和可维护性 新方法符合现代 Linux 内核的设计理念,代码更加一致和可维护。

总结

  • 旧方法:mknod 用于在用户空间手动创建设备节点,缺乏自动化和动态支持。
  • 新方法:device_create 用于在内核中自动创建设备节点,支持动态管理、权限控制和设备模型集成。
  • 推荐使用新方法:新方法符合现代 Linux 内核的设计理念,提供了更强大的功能和更好的可维护性。

class_create 和 device_create

class_create 和 device_create 是 Linux 内核中用于 设备模型管理 的函数,它们分别用于 创建设备类 和 创建设备节点。

class_create

作用

  • 创建一个设备类(struct class),用于管理一组具有相同类型的设备。
  • 设备类在 /sys/class/ 目录下创建一个目录,用于存放设备的属性和状态信息。

使用场景

  • 当你需要为一组设备(如多个字符设备)创建一个共同的类别时,使用 class_create。
  • 例如,为所有 I2C 设备创建一个 i2c-dev 类,为所有 GPIO 设备创建一个 gpio 类。

示例

struct class *my_class;
my_class = class_create(THIS_MODULE, "my_device_class");

device_create

作用

  • 在 /dev 目录下创建设备节点,并在 /sys/class/ 目录下创建设备的属性文件。
  • 设备节点是用户空间与内核设备交互的接口。

使用场景

  • 当你需要在 /dev 目录下创建设备节点时,使用 device_create。
  • 例如,在字符设备驱动中,为每个设备创建设备节点(如 /dev/my_device)。

示例

device_create(my_class, NULL, dev, NULL, "my_device");

典型的使用流程

my_class = class_create(THIS_MODULE, CLASS_NAME);
device_create(my_class, NULL, dev, NULL, DEVICE_NAME);

总结

class_create:

  • 创建设备类,用于管理一组具有相同类型的设备。
  • 使用场景:为多个设备创建一个共同的类别。

    device_create:

  • 创建设备节点,用于用户空间与内核设备交互。
  • 使用场景:在 /dev 目录下创建设备节点。

cdev_add 和 device_add 关系

在 Linux 内核中,cdev_add 和 device_add 是两个不同的函数,分别用于 字符设备注册 和 设备模型注册。如果只使用 cdev_add 而没有使用 device_add,会有以下缺点和问题:

缺少设备模型支持

device_add 是 Linux 设备模型的一部分,用于将设备注册到内核的设备树中。如果只使用 cdev_add,设备不会被添加到内核的设备树中,导致以下问题:

无法通过 sysfs 管理设备

  • sysfs 是内核提供的文件系统,用于管理设备和驱动的属性。
  • 如果设备没有通过 device_add 注册,sysfs 中不会出现该设备的目录,用户无法通过 sysfs 查看或修改设备属性。

无法自动加载驱动

  • 内核的设备模型支持自动加载驱动(通过 MODALIAS 机制)。
  • 如果设备没有通过 device_add 注册,内核无法自动加载与该设备匹配的驱动。

无法使用 udev 管理设备

  • udev 是用户空间的设备管理工具,依赖于内核的设备模型。
  • 如果设备没有通过 device_add 注册,udev 无法管理该设备(如创建设备节点、设置权限等)。

缺少设备节点

device_create 是 device_add 的一部分,用于在 /dev 目录下创建设备节点。如果只使用 cdev_add,设备节点不会被自动创建,导致以下问题:

需要手动创建设备节点

  • 必须手动使用 mknod 命令创建设备节点,增加了使用复杂度。
    mknod /dev/my_device c 240 0

设备节点权限问题

  • 手动创建设备节点时,需要手动设置权限,可能导致权限不足或安全问题。
    chmod 666 /dev/my_device

缺少设备的热插拔支持

device_add 支持设备的热插拔功能。如果只使用 cdev_add,设备无法支持热插拔,导致以下问题:

无法动态加载和卸载设备

  • 设备无法在运行时动态加载或卸载。
  • 例如,无法通过 echo 命令将设备从内核中移除。

无法与用户空间交互

  • 设备的热插拔事件无法通知用户空间(如通过 udev)。

缺少设备的状态管理

device_add 会将设备的状态(如 probe、remove 等)记录到内核中。如果只使用 cdev_add,设备的状态无法被内核管理,导致以下问题:

无法跟踪设备状态

  • 内核无法知道设备是否已被初始化或移除。
  • 例如,无法通过 sysfs 查看设备的状态。

    无法处理设备的依赖关系

  • 内核无法处理设备与其他设备或资源的依赖关系。
  • 例如,无法确保设备在依赖资源可用后再初始化。

总结

  • 如果只使用 cdev_add,设备无法被注册到内核的设备树中,导致无法通过 sysfs 管理设备、无法自动加载驱动、无法使用 udev 管理设备等问题。
  • 为了充分利用内核的设备模型和功能,建议同时使用 cdev_add 和 device_add。

gpio, gpiod

gpiod 和 gpio 是 Linux 内核中用于管理 GPIO(通用输入输出)的两种不同接口。它们的主要区别在于 实现方式 和 使用场景。以下是它们的详细对比

gpio

特点

  • 旧版接口:
    • 是 Linux 内核早期提供的 GPIO 管理接口。
    • 基于 sysfs 文件系统,通过 /sys/class/gpio 目录访问 GPIO。
  • 用户空间接口:
    • 主要通过文件操作(如 open、read、write)控制 GPIO。
    • 例如,通过 echo 命令导出 GPIO 或设置 GPIO 方向。
  • 简单易用:
    • 适合简单的 GPIO 操作,无需编写内核代码。

使用场景

  • 在用户空间快速测试 GPIO 功能。
  • 简单的嵌入式系统或原型开发。
  • 不需要复杂 GPIO 管理的场景。

gpiod 接口

特点

  • 新版接口:
    • 是 Linux 内核推荐的 GPIO 管理接口。
    • 基于 libgpiod 库,提供更强大和灵活的功能。
  • 内核空间和用户空间接口:
    • 支持在内核空间和用户空间操作 GPIO。
    • 提供丰富的 API,支持复杂的 GPIO 管理(如中断、多 GPIO 操作)。
  • 设备树支持:
    • 与设备树(Device Tree)紧密集成,支持动态配置 GPIO。
  • 性能优化:
    • 比 gpio 接口更高效,适合高性能和实时性要求高的场景。

使用场景

  • 复杂的嵌入式系统或生产环境。
  • 需要高性能和实时性支持的场景。
  • 需要与设备树集成的场景。
  • 需要复杂 GPIO 管理(如中断、多 GPIO 操作)的场景。

主要区别

特性 GPIO 接口 GPIOD 接口
接口类型 旧版接口,基于 sysfs 新版接口,基于 libgpiod
使用场景 用户空间,简单 GPIO 操作 内核空间和用户空间,复杂 GPIO 操作
性能 较低 较高
功能 功能有限 功能强大(支持中断、多 GPIO 操作等)
设备树支持 不支持 支持
推荐使用 逐渐被淘汰 推荐使用

总结

  • gpio 接口:
    • 是旧版接口,基于 sysfs,适合简单的 GPIO 操作。
    • 逐渐被淘汰,不建议在新代码中使用。
  • gpiod 接口:
    • 是新版接口,基于 libgpiod,功能强大,性能优越。
    • 支持复杂的 GPIO 管理和设备树集成,推荐在新项目中使用。

在实际开发中,建议优先使用 gpiod 接口,因为它提供了更强大的功能和更好的性能,能够满足现代嵌入式系统的需求。

devm 和 旧版方式

devm(Device Resource Management)是 Linux 内核中用于 自动管理设备资源 的一套接口。它通过将资源的生命周期与设备绑定,确保在设备卸载时自动释放资源,从而减少资源泄漏的风险。devm 接口是 新版接口,并没有所谓的“旧版接口”,但它与传统的资源管理方式(手动管理)有显著区别。

传统资源管理方式(手动管理)

特点

  • 手动分配和释放资源:
    • 开发者需要显式调用资源分配函数(如 kmalloc、request_irq 等)。
    • 在设备卸载时,需要显式调用资源释放函数(如 kfree、free_irq 等)。
  • 容易出错:
    • 如果忘记释放资源,会导致资源泄漏。
    • 如果多次释放资源,会导致内核崩溃。

示例

static int my_probe(struct platform_device *pdev)
{
    void *buffer;
    int irq;

    // 分配内存
    buffer = kmalloc(1024, GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    // 注册中断
    irq = request_irq(IRQ_NUM, my_handler, 0, "my_device", NULL);
    if (irq < 0) {
        kfree(buffer);
        return irq;
    }

    // 在设备卸载时,需要手动释放资源
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    // 释放中断
    free_irq(IRQ_NUM, NULL);

    // 释放内存
    kfree(buffer);

    return 0;
}

devm 接口(自动管理资源)

特点

  • 自动释放资源:
    • 资源与设备绑定,当设备卸载时,内核自动释放资源。
    • 开发者无需显式调用资源释放函数。
  • 减少错误:
    • 避免资源泄漏和重复释放的问题。
    • 简化驱动代码,提高开发效率。

示例

static int my_probe(struct platform_device *pdev)
{
    void *buffer;
    int irq;

    // 分配内存(自动释放)
    buffer = devm_kzalloc(&pdev->dev, 1024, GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    // 注册中断(自动释放)
    irq = devm_request_irq(&pdev->dev, IRQ_NUM, my_handler, 0, "my_device", NULL);
    if (irq < 0)
        return irq;

    // 无需手动释放资源
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    // 无需手动释放资源
    return 0;
}

主要区别

特性 传统方式(手动管理) DEVM 接口(自动管理)
资源释放 需要手动释放资源 内核自动释放资源
错误风险 容易导致资源泄漏或重复释放 减少资源泄漏和重复释放的风险
代码复杂度 代码复杂度较高,需要显式管理资源 代码简化,资源管理与设备绑定
适用场景 适用于所有场景,但需要开发者小心管理资源 推荐在新代码中使用,减少开发负担

devm 接口的常见函数

devm 接口提供了一系列资源管理函数,以下是常见的函数:

函数 说明
devm_kzalloc 分配内存,设备卸载时自动释放。
devm_kmalloc 分配内存,设备卸载时自动释放。
devm_request_irq 注册中断,设备卸载时自动释放。
devm_ioremap 映射 I/O 内存,设备卸载时自动释放。
devm_gpiod_get 获取 GPIO 描述符,设备卸载时自动释放。
devm_clk_get 获取时钟资源,设备卸载时自动释放。
devm_regulator_get 获取电源调节器,设备卸载时自动释放。

总结

  • 传统方式:
    • 需要手动分配和释放资源,容易出错,代码复杂度高。
  • devm 接口:
    • 资源与设备绑定,内核自动释放资源,减少错误,简化代码。
  • 推荐使用 devm 接口:
    • 在新代码中优先使用 devm 接口,以减少资源管理的工作量和错误风险。

发表评论