嵌入式驱动刚接触时,很容易觉得知识点特别散:今天在看中断,明天在看锁,后天又被设备模型和用户态接口绊住。实际做久了会发现,这些问题反复都在几条线上打转:
- 中断怎么处理
- 共享资源怎么保护
- 驱动和设备怎么匹配
- 用户态怎么和内核态交互
所以这篇不按八股问答铺开,还是按驱动开发里最常碰到的几类问题往下捋。
1. 设备文件为什么会出现
Linux 把很多硬件访问都抽象成文件接口。字符设备节点创建方式常见有三类:
- 手动
mknod - 用户态设备管理工具自动创建,如
udev/mdev - 早期机制如
devfs
设备节点本身只是最终露给用户态看的入口。前面真正要做的是把设备号、设备类和一组文件操作先注册好,不然 /dev 下就算有名字也没有意义。
2. 中断处理为什么强调快进快出
中断上下文最忌讳把重活都放在上半部。原因很简单:
- 中断处理时间过长会拖慢系统响应
- 很多操作在中断上下文里不能睡眠
- 多核和高频中断场景下容易放大竞争
更常见的做法是:
- 上半部快速确认来源、保存必要状态
- 下半部、tasklet、softirq 或 workqueue 再处理耗时逻辑
看 ISR 写得好不好,通常不是看它“功能多不多”,而是看它有没有把必须马上做的事情和可以往后放的事情分开。
3. 自旋锁、信号量、mutex 到底怎么选
最常见的区分方式是看上下文能不能睡眠。
自旋锁
- 不能睡眠
- 适合短临界区
- 常见于中断上下文或高频共享数据保护
信号量 / mutex
- 可以睡眠
- 更适合进程上下文
- 适合可能阻塞的资源访问
所以中断里一般先想自旋锁,信号量和 mutex 往往是进程上下文里的选择。
4. 什么是原子操作
原子操作强调的是“不可分割”。
它常用于计数器、标志位、状态切换等简单共享变量场景,避免为了极小的状态保护动用更重的锁。
但要注意,原子操作不是万能互斥。
它更适合简单变量更新,不适合需要保护一整段复杂临界区的场景。
5. 为什么寄存器访问前常要 ioremap
驱动拿到的很多硬件资源,本质上是物理地址。内核不能随手把物理地址当虚拟地址直接访问,所以要先通过 ioremap 建立映射,再用合适的 I/O 接口访问。
这里其实是在碰一个很基本的事实:
- 设备资源在物理地址空间里
- 驱动运行在内核虚拟地址环境里
- 中间需要映射桥梁
6. platform 总线解决了什么
并不是所有设备都挂在 PCI、USB、I2C、SPI 这类显式总线上。对 SoC 内部很多集成设备来说,Linux 通过 platform 设备和 platform 驱动来统一管理。
它的价值主要在于:
- 统一设备 / 驱动匹配模型
- 统一资源描述方式
- 方便中断、时钟、寄存器、DMA 等平台资源管理
做 SoC 内部设备时,先把 platform 这一套想明白,通常比死背某几个 API 更有用。
7. 用户态和内核态为什么不能直接互访
用户态和内核态不只是“权限不同”,还意味着:
- 地址空间隔离
- 用户指针未必可靠
- 访问过程可能触发换页和异常
因此,内核和用户态通信要走受控机制,例如:
- 系统调用
ioctlprocfssysfsmmapnetlink
驱动接口只要通向用户态,输入就默认不能直接信,边界和合法性检查不能省。
8. 软中断、tasklet、workqueue 的区别
它们都属于“延后处理”,但上下文不同。
softirq
更底层、更偏性能,适合网络等高频场景。
tasklet
基于 softirq,使用更方便,但能力和限制也继承了 softirq 的特点。
workqueue
运行在进程上下文,可睡眠,适合较重、较复杂的后处理任务。
到底该选哪种,通常就看几件事:
- 是否需要睡眠
- 是否追求极低时延
- 工作量是否较重
9. 驱动开发里并发控制为什么总是绕不开
驱动经常会遇到多种并发来源同时落到一份硬件资源上:
- 多线程并发调用
- 中断和进程上下文同时访问
- 多核同时执行
这也是为什么驱动开发里要长期和这些东西打交道:
- 原子操作
- 自旋锁
- mutex / semaphore
- 禁中断保护
这不是 Linux 故意把事情搞复杂,而是硬件访问本来就伴随共享、抢占和竞争。
10. 总结
回头看,驱动开发里先抓住的通常不是零散 API,而是下面这几条线:
- 中断上下文和进程上下文的区别
- 并发保护怎么选
- 设备模型如何组织驱动
- 用户态和内核态边界如何处理
这几条线顺了,后面无论是写字符设备、接 platform 设备,还是排查现场问题,思路都会稳很多。