为什么从字符设备开始
字符设备驱动是 Linux 驱动开发里最适合作为起点的一类模型。它足够简单,但又能把驱动开发中最关键的几件事串起来:
- 驱动模块的加载与卸载
- 设备号的管理
file_operations的设计- 用户态与内核态之间的数据交互
如果这条链路理解清楚,后面继续学 platform 驱动、设备树匹配、中断和总线模型会顺很多。
先理解应用层和驱动层的关系
在 Linux 中,应用程序最终是通过操作 /dev 下的设备节点与驱动交互的。对于一个字符设备,最典型的调用链就是:
openreadwriteclose
应用层看到的是文件接口,内核侧真正实现的是 struct file_operations 中对应的函数。
这也是字符设备驱动最值得练习的地方:你写的不是一个“抽象概念”,而是一组明确可映射到用户接口的内核函数。

file_operations 该怎么看
第一次看到 struct file_operations 往往会觉得很长,但实际开发不需要一次理解全部成员。对一个最小可运行字符设备来说,先抓住四个核心函数就够了:
openreleasereadwrite
等到需要支持阻塞/非阻塞、ioctl、poll 或 mmap 时,再逐步扩展。
这种“先做最小闭环,再扩展能力”的方式,在驱动学习里非常重要。
模块加载和卸载做了什么
Linux 驱动常见有两种形态:
- 编译进内核
- 编译为可加载模块
.ko
对于学习和调试来说,模块形式更方便,因为你可以反复:
insmod/modprobe- 查看日志
rmmod- 修改后重新编译加载
对应入口通常就是:
module_init(xxx_init);
module_exit(xxx_exit);
可以把它理解成模块生命周期的起点和终点。
字符设备注册的两层含义
字符设备“注册”这件事,至少包含两层语义:
- 告诉内核:这是一个字符设备驱动
- 申请并绑定设备号,建立驱动与设备节点之间的映射关系
学习初期常见写法是直接使用 register_chrdev。它上手快,能迅速跑通流程;但从长期来看,也要理解 alloc_chrdev_region 这类更标准的设备号管理方式。
inode 和 file 在 open 里为什么重要
这是很多初学驱动时容易忽略,但又非常关键的点。
在 open 阶段,inode 主要可以帮助我们:
- 获取主次设备号
- 找到当前字符设备对应的
cdev - 进一步反推到自定义设备结构体
然后再把这个设备结构体保存到 filp->private_data 中,后续 read / write 就能直接复用。
这一步本质上是在做“设备上下文传递”。如果理解了这个思路,很多更复杂的驱动框架都会更容易读懂。
这次实战做了一个什么驱动
我写的是一个最小字符设备驱动:
- 在内核中分配一个缓冲区
- 允许用户态向缓冲区写入数据
- 允许用户态从缓冲区读取数据
这个例子虽然简单,但它完整覆盖了:
- 驱动注册
- 设备读写
- 用户态/内核态数据拷贝
- 上板验证
最关键的两个数据拷贝函数
用户态和内核态不能直接互相访问对方内存,因此字符设备读写一定会频繁碰到:
copy_to_usercopy_from_user
这是字符设备驱动最基本、也最容易因为粗心出问题的部分。除了记住函数本身,更要养成几个习惯:
- 明确缓冲区长度
- 检查返回值
- 不要默认用户传入的参数一定合法
在 RK3568 上板测试时我关注什么
驱动编译成 .ko 后,我会把它放到目标板对应的模块目录下,再通过 modprobe 加载,然后检查以下几件事:
- 模块是否成功加载
- 设备节点是否创建成功
- 写入操作是否真的进入内核缓冲区
- 读取结果是否符合预期
- 卸载后节点与驱动状态是否一致

一个比较容易让初学者困惑的点是:驱动卸载了,为什么 /dev 下的节点还在?
原因很简单,因为如果这个节点是通过 mknod 手动创建的,它本身就是文件系统里的一个特殊文件,不会随着模块卸载自动消失。除非你显式删除,或者在驱动里补齐自动创建设备类与销毁逻辑。
上板验证结果
下面这几张图分别对应:创建设备节点、向缓冲区写入数据、从缓冲区读取数据。



这份最小例子的价值在哪里
它最大的价值不是“可以读写一段字符串”,而是帮你建立一套驱动开发的骨架认知:
- 先有模块入口和出口
- 再有字符设备注册
- 再补齐
file_operations - 最后通过用户态程序验证整个调用链
这套思路以后完全可以迁移到:
- GPIO 设备控制
- LED 驱动
- 按键输入
- 更复杂的平台设备驱动
小结
如果把字符设备驱动理解成 Linux 驱动开发的第一块拼图,那么这块拼图的核心并不是某个 API,而是三件事:
- 明白用户态如何进入内核态
- 明白设备号和驱动如何关联
- 明白设备上下文如何在
open/read/write之间传递
一旦这三件事理顺,后续学习内核驱动就会从“记笔记”逐渐变成“能自己搭框架”。