字符设备注册
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 接口,以减少资源管理的工作量和错误风险。