当前位置: 首页 > news >正文

驱动开发硬核特训 · 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 驱动学习者。


相关文章:

  • 欧拉计划 Project Euler54(扑克手牌)题解
  • MySQL运算符
  • stack和queue的学习
  • 【实证分析】ESG发展对企业新质生产力影响的研究—来自中国A股上市企业的经验
  • ROS第十二梯:ros-noetic和Anaconda联合使用
  • Python常用的第三方模块之【pymysql库】操作数据库
  • HarmonyOS 5.0应用开发——MVVM模式的应用
  • CF2103F Maximize Nor
  • AI 人工智能模型:从理论到实践的深度解析⚡YQW · Studio ⚡【Deepseek】【Chat GPT】
  • 深度学习中的黑暗角落:梯度消失与梯度爆炸问题解析
  • springboot+vue 支付宝支付(沙箱方式,测试环境使用)
  • VUE Element-ui Message 消息提示组件自定义封装
  • 如何在编译命令中添加灰度标识
  • cnas认证注意事项,cnas认证审核有效期?cnas认证难吗?
  • 思科路由器密码绕过+重置
  • uniapp小程序使用echarts
  • 湖南大学-操作系统实验四
  • python——模块、包、操作文件
  • 如何选择 Flask 和 Spring Boot
  • 【数据结构入门训练DAY-21】信息学奥赛一本通T1334-围圈报数
  • 人民日报:上海“模速空间”何以汇聚超百家大模型企业
  • 美国经济萎缩意味着什么?关税政策如何反噬经济?
  • 据报特斯拉寻找新CEO,马斯克财报会议上表态:把更多时间投入特斯拉
  • 人民日报钟声:国际社会应共同维护科学溯源的正确方向
  • 马上评|什么才是地方文旅宣传的正确姿势
  • 国台办:台商台企有信心与国家一起打赢这场关税战