Linux 嵌入式基础问答

从中断、并发、设备模型到用户态 / 内核态边界

Posted by Yvain Zhang on June 28, 2022 主题:技术

嵌入式驱动刚接触时,很容易觉得知识点特别散:今天在看中断,明天在看锁,后天又被设备模型和用户态接口绊住。实际做久了会发现,这些问题反复都在几条线上打转:

  • 中断怎么处理
  • 共享资源怎么保护
  • 驱动和设备怎么匹配
  • 用户态怎么和内核态交互

所以这篇不按八股问答铺开,还是按驱动开发里最常碰到的几类问题往下捋。

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. 用户态和内核态为什么不能直接互访

用户态和内核态不只是“权限不同”,还意味着:

  • 地址空间隔离
  • 用户指针未必可靠
  • 访问过程可能触发换页和异常

因此,内核和用户态通信要走受控机制,例如:

  • 系统调用
  • ioctl
  • procfs
  • sysfs
  • mmap
  • netlink

驱动接口只要通向用户态,输入就默认不能直接信,边界和合法性检查不能省。

8. 软中断、tasklet、workqueue 的区别

它们都属于“延后处理”,但上下文不同。

softirq

更底层、更偏性能,适合网络等高频场景。

tasklet

基于 softirq,使用更方便,但能力和限制也继承了 softirq 的特点。

workqueue

运行在进程上下文,可睡眠,适合较重、较复杂的后处理任务。

到底该选哪种,通常就看几件事:

  • 是否需要睡眠
  • 是否追求极低时延
  • 工作量是否较重

9. 驱动开发里并发控制为什么总是绕不开

驱动经常会遇到多种并发来源同时落到一份硬件资源上:

  • 多线程并发调用
  • 中断和进程上下文同时访问
  • 多核同时执行

这也是为什么驱动开发里要长期和这些东西打交道:

  • 原子操作
  • 自旋锁
  • mutex / semaphore
  • 禁中断保护

这不是 Linux 故意把事情搞复杂,而是硬件访问本来就伴随共享、抢占和竞争。

10. 总结

回头看,驱动开发里先抓住的通常不是零散 API,而是下面这几条线:

  • 中断上下文和进程上下文的区别
  • 并发保护怎么选
  • 设备模型如何组织驱动
  • 用户态和内核态边界如何处理

这几条线顺了,后面无论是写字符设备、接 platform 设备,还是排查现场问题,思路都会稳很多。