驱动开发硬核特训 · Day 18:深入理解字符设备驱动与子系统的协作机制(以 i.MX8MP 为例)


日期:2025年04月23日
回顾:2025年04月22日(Day 17:Linux 中的子系统概念与注册机制)
本日主题:字符设备驱动 × 子系统协作机制剖析
学习目标:理解字符设备的注册原理,掌握其与子系统间的接口关系与工作流,结合实际代码实现。


一、前言:设备模型之后,我们为什么学习字符设备驱动?

在前面的内容中,我们系统掌握了设备模型的构成和原理,包括 busdevicedriver 之间的匹配与生命周期关系。这一模型为 Linux 驱动提供了统一的管理方式。但这只是驱动“框架”的一环,驱动真正提供“功能”的部分往往以“字符设备”的形式呈现。

尤其是在嵌入式平台如 NXP i.MX8MP EVK 上,GPIO、I2C、PWM、摄像头等模块的访问大多通过 /dev/ 下的字符设备进行,而这些字符设备的背后通常归属于某个子系统(例如:input、sound、misc、video、tty 等)。因此,今天我们要解决的是:

  • 字符设备到底是什么?
  • 它如何注册?如何与用户态通信?
  • 它和设备模型有何关系?
  • 子系统在其中扮演了什么角色?

二、字符设备驱动基础知识回顾

2.1 什么是字符设备?

字符设备(Character Device)是一种一次读取/写入一个字符流的设备接口,相对于块设备(如磁盘)而言,它没有缓冲区机制、没有对齐限制。常见的字符设备包括:

  • 串口 /dev/ttyS*
  • GPIO /dev/gpiochip*
  • I2C /dev/i2c-*
  • 自定义控制器设备(如 /dev/mypwm

它们通常通过 file_operations 结构体实现系统调用的处理,如 readwriteioctl 等。

2.2 注册字符设备的方法(核心 API)

字符设备的注册过程大致分为以下几步:

  1. 申请设备号

    alloc_chrdev_region(&devt, 0, 1, "demo_char");
    
  2. 初始化 cdev 并添加到系统

    cdev_init(&cdev, &fops);
    cdev_add(&cdev, devt, 1);
    
  3. 创建设备节点

    class_create(); 
    device_create();
    
  4. 注销字符设备(卸载时)

    device_destroy();
    class_destroy();
    unregister_chrdev_region();
    

三、子系统:字符设备驱动背后的组织者

在 Linux 中,并非所有字符设备都以“裸 API”的方式注册,大量的字符设备实际上是依附于子系统框架进行注册的。
在这里插入图片描述

3.1 子系统为何存在?

子系统(subsystem)是为了解决如下问题而出现的:

  • 提供统一的设备类与行为抽象(如 input, tty, misc)
  • 简化字符设备注册流程(抽象 class、cdev、major/minor)
  • 提供统一的 ioctl、poll、open 行为(如 video4linux 的 V4L2)

举例:

子系统设备名注册方式
input/dev/input/event*input_register_device()
misc/dev/misc/*misc_register()
tty/dev/ttyS*tty_register_driver()
video/dev/video*video_register_device()

这些子系统往往会帮你封装掉字符设备注册流程,你只需关注“实现业务逻辑”。

3.2 子系统与字符设备的关系图

用户空间          内核空间
----------        -----------------------------------
 /dev/video0 --->  V4L2子系统 ----> 注册字符设备
                  ↑
                  │
                  platform_device / i2c_device / ...

四、实战:以 i.MX8MP 上的 LED 控制器为例讲解字符设备注册

4.1 示例背景

我们以 gpio-leds 中的一个 LED 为例,演示如何构造一个字符设备用于控制其亮灭。

设备树片段(位于 imx8mp-evk.dts):

gpio-leds {
    compatible = "gpio-leds";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_led>;

    status {
        label = "yellow:status";
        gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
        default-state = "on";
    };
};

4.2 简单字符设备驱动代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define GPIO_NUM 80  // gpio3_16 = 32 * 2 + 16 = 80
static dev_t devt;
static struct cdev cdev;
static struct class *led_class;

static ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    char kbuf[4];
    if (copy_from_user(kbuf, buf, len))
        return -EFAULT;
    gpio_set_value(GPIO_NUM, kbuf[0] == '1' ? 1 : 0);
    return len;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
};

static int __init led_dev_init(void)
{
    gpio_request(GPIO_NUM, "led_gpio");
    gpio_direction_output(GPIO_NUM, 1);

    alloc_chrdev_region(&devt, 0, 1, "led_char");
    cdev_init(&cdev, &led_fops);
    cdev_add(&cdev, devt, 1);

    led_class = class_create(THIS_MODULE, "led_class");
    device_create(led_class, NULL, devt, NULL, "ledchar");

    pr_info("ledchar device init done\n");
    return 0;
}

static void __exit led_dev_exit(void)
{
    gpio_set_value(GPIO_NUM, 0);
    gpio_free(GPIO_NUM);

    device_destroy(led_class, devt);
    class_destroy(led_class);
    cdev_del(&cdev);
    unregister_chrdev_region(devt, 1);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

编译为模块,加载后可直接:

echo 1 > /dev/ledchar   # 点亮
echo 0 > /dev/ledchar   # 熄灭

五、为什么不能只靠设备模型或子系统?

设备模型的本质是“管理对象关系”,子系统的作用是“组织类行为”,但真正实现具体功能的,还得靠字符设备驱动这一“落地接口”。

我们可以这样理解它们三者的关系:

模块角色
设备模型提供注册匹配机制(device/driver)
子系统封装字符设备注册流程,组织行为一致性
字符设备驱动实现具体功能,操作底层硬件

六、总结

今日你学习了:

  • 字符设备的基本概念与注册流程
  • 子系统对字符设备的封装与简化作用
  • 实际示例中字符设备如何与 GPIO 结合
  • 三大模块(设备模型、子系统、字符设备驱动)间的工作边界与协作逻辑

字符设备是 Linux 驱动开发中最贴近用户层的部分,掌握这一点将为后续如 input 设备、V4L2 摄像头、tty 驱动打下基础。


七、问答环节(回顾与思考)

  1. cdev_add()device_create() 有什么区别?
  2. misc_register 和标准字符设备注册方法有什么异同?
  3. 为什么有的驱动不需要显式注册字符设备?
  4. 子系统能否单独工作,不借助设备模型?

视频教程请关注 B 站:“嵌入式 Jerry”

明日预告(Day 19):misc_register 机制与子系统封装详解 —— 带你剖析为什么字符设备能“一行搞定”。

如果你觉得今天的训练内容对你有帮助,欢迎留言交流,也欢迎转发给其他 Linux 驱动学习者。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值