文章

Linux 内核学习笔记:设备管理

Linux 内核学习笔记:设备管理

这一篇记录设备管理的几个基础问题:设备控制器、I/O 控制方式、设备驱动、通用块层、存储系统 I/O 分层,以及“键盘敲入 A 字母”这条路径。Linux 落点放在 MMIO/PIO、DMA、IRQ、driver model、字符设备、块设备、block layer、workqueue。

问题索引

主题本篇记录
设备控制器控制寄存器、数据寄存器、状态寄存器
I/O 控制方式轮询、中断、DMA、MMIO/PIO
设备驱动程序向上提供统一接口,向下控制具体硬件
通用块层块设备抽象、请求队列、I/O 调度
存储系统 I/O 分层文件系统、page cache、块层、驱动、设备
键盘输入路径键盘控制器、中断、驱动、输入子系统、终端

1. 设备控制器:CPU 不直接操作复杂设备

外设种类很多,键盘、鼠标、磁盘、网卡、显示器的操作方式都不同。CPU 通常通过设备控制器和设备通信。控制器理解设备协议,并向 CPU 暴露寄存器或内存映射区域。

寄存器作用
data register传输数据
command register接收 CPU 发出的命令
status register暴露设备是否忙、是否完成、是否出错
control register设置设备工作模式

设备大体可以分成两类。

类型特点例子
字符设备字节流或字符流,通常不能随机寻址键盘、串口、终端
块设备固定大小块,可随机访问HDD、SSD、U 盘

Linux 落点:用户态常通过设备文件访问设备,例如 /dev/sda、/dev/null、/dev/tty。设备文件把 VFS 接口接到驱动的 file operations 上。字符设备和块设备在内核里的对象、路径和调度方式不同。

2. MMIO、PIO、中断、DMA

CPU 和设备控制器通信常见两类地址空间接口。

方式记录
PIO使用专门 I/O 指令访问设备端口
MMIO把设备寄存器映射到内存地址空间,像访问内存一样读写寄存器

设备完成工作后需要通知 CPU。轮询会反复读状态寄存器,简单但浪费 CPU;中断让设备在事件发生时打断 CPU,进入内核中断处理路径。

DMA, Direct Memory Access,让设备或 DMA 控制器直接在设备和内存之间搬运数据。CPU 负责配置传输方向、地址、长度和权限,数据搬运由 DMA 完成,传输完成后再通过中断通知 CPU。

机制解决的问题代价
轮询实现简单,适合极短等待或特殊路径消耗 CPU
中断事件发生时通知 CPU高频事件会带来中断风暴
DMA大块数据搬运不占用 CPU 拷贝循环需要映射、同步、缓存一致性处理

Linux 落点:高性能设备路径经常组合 DMA、中断、软中断、NAPI、workqueue。硬中断路径要短,能延后的工作放到 softirq、threaded IRQ 或 workqueue。

3. 设备驱动:内核里的硬件适配层

设备驱动向上接入内核通用框架,向下操作设备控制器。驱动负责初始化设备、注册中断处理函数、分配 DMA 缓冲区、暴露设备文件或网络接口、处理错误和热插拔等。

驱动动作Linux 入口
设备发现和匹配bus、device、driver、modalias
注册字符设备cdev、file_operations
注册块设备gendisk、request queue、blk-mq
注册网卡net_device、NAPI
注册中断处理request_irq()
延后处理softirq、tasklet、workqueue、threaded IRQ

Linux driver model 的核心对象可以先记 struct device、struct device_driver、bus、class、sysfs。/sys 暴露了设备层次、驱动绑定、属性和电源状态等信息。

观察入口:

命令 / 路径记录
ls /sys/bus总线类型
ls /sys/class设备类别
lsmod已加载模块
modinfo <module>模块信息
dmesg -T驱动初始化、设备错误、中断日志
udevadm infoudev 看到的设备属性

4. 中断上下文和 deferred work

中断处理函数运行在特殊上下文中。它响应硬件事件,但路径要短,不能随便执行可能睡眠的操作。常见做法是硬中断里确认事件、读取少量状态、屏蔽或确认设备中断,再把后续工作交给下半部机制。

机制记录
hard IRQ硬中断处理,响应设备或定时器事件
softirq软中断,网络收发等高频路径常用
tasklet基于 softirq 的旧式延后执行机制
workqueue在内核线程上下文执行,可以睡眠
threaded IRQ把中断处理线程化,适合需要较长处理的驱动

能否睡眠是关键边界。

上下文睡眠能力
普通进程上下文通常可以睡眠
workqueue可以睡眠
hard IRQ不能睡眠
softirq不能随便睡眠
持有 spinlock不能睡眠

5. 通用块层:文件系统和磁盘驱动之间

块设备路径需要处理请求排队、合并、调度、分发和完成。Linux 的通用块层把上层文件系统或 direct I/O 的请求转成块设备请求,再交给具体驱动和设备。

层次记录
VFS / 文件系统产生文件读写请求
page cache / writeback缓存文件页和脏页写回
block layerbio、request queue、I/O scheduler、blk-mq
driver设备特定命令、DMA、完成中断
hardwareSSD/HDD/NVMe 等

blk-mq 是现代 Linux 块层的多队列框架,用来适配多核 CPU 和现代存储设备。I/O scheduler 的作用包括合并请求、控制延迟、公平性和设备队列。

观察入口:

命令 / 路径记录
lsblk -o NAME,TYPE,SIZE,ROTA,SCHED块设备和调度器
cat /sys/block/<dev>/queue/scheduler当前 I/O scheduler
iostat -x 1设备利用率和延迟
pidstat -d 1进程 I/O
cat /proc/diskstats块设备统计

6. 一次键盘输入路径

键盘敲入一个字符,路径可以压缩成硬件事件进入内核,再被用户态读取。

阶段记录
键盘控制器产生扫描码,放入控制器缓冲区
中断控制器向 CPU 发送 IRQ
中断处理内核保存当前上下文,进入键盘驱动中断处理
驱动读取扫描码,交给输入子系统
input / tty转成按键事件或终端输入
用户态shell、编辑器或图形系统读取输入

这条路径连接几个核心概念:设备控制器通过中断通知 CPU;驱动处理设备协议;输入子系统做统一抽象;终端或图形栈把事件交给应用。

7. 设备文件和 ioctl

Linux 里很多设备通过文件接口暴露。普通读写走 read() / write(),设备特有控制走 ioctl()。

接口记录
open()打开设备文件
read() / write()读写设备数据流
poll() / epoll()等待设备可读写
mmap()把设备或 DMA 缓冲区映射给用户态,依驱动支持
ioctl()设备特定控制命令

ioctl() 是设备管理里常见的“逃生口”:统一文件接口表达不了的设备属性、模式切换、命令提交,经常通过 ioctl 进入驱动。

压缩表

概念基础概念 / 八股Linux 落点
设备控制器CPU 通过控制器操作设备MMIO/PIO、寄存器
中断设备通知 CPUIRQ、request_irq()、/proc/interrupts
DMA设备和内存直接搬运数据DMA mapping、IOMMU、cache sync
驱动屏蔽设备差异driver model、module、sysfs
字符设备字节流接口cdev、file_operations
块设备固定块随机访问block layer、blk-mq、request queue
deferred work延后处理耗时工作softirq、workqueue、threaded IRQ

检查点与参考回答

问题参考回答
设备控制器是什么?设备控制器是 CPU 和具体外设之间的硬件控制组件,向 CPU 暴露寄存器或内存映射区域,并负责理解设备协议。
MMIO 和 PIO 怎么区分?PIO 使用专门 I/O 指令访问端口;MMIO 把设备寄存器映射到内存地址空间,用普通内存访问指令读写。
DMA 解决什么问题?DMA 让设备和内存之间的大块数据搬运不需要 CPU 逐字节拷贝,CPU 只负责配置传输和处理完成事件。
中断上下文为什么不能随便睡眠?中断上下文不代表普通进程执行,路径要求短,且没有正常可阻塞的进程上下文;耗时工作通常交给 softirq、threaded IRQ 或 workqueue。
字符设备和块设备有什么区别?字符设备以字节流方式访问,通常不可随机寻址;块设备按固定大小块访问,支持随机读写,路径会经过通用块层。
通用块层做什么?通用块层位于文件系统和块设备驱动之间,管理 bio/request、队列、合并、调度和分发,向上屏蔽不同块设备差异。
ioctl() 为什么存在?通用 read/write/poll 接口无法表达所有设备特定控制命令,ioctl() 用于设备模式配置、属性获取、命令提交等驱动私有操作。

本篇参考

  • Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces
  • Linux Kernel Documentation: Driver API
  • Linux Kernel Documentation: Core API
  • Linux Kernel Documentation: Block
  • Michael Kerrisk, The Linux Programming Interface
  • Robert Love, Linux Kernel Development
本文由作者按照 CC BY 4.0 进行授权