深度解析操作系统I/O系统:DMA、中断、SPOOLing与设备独立性

发布时间:2026/10/23 6:53:47
深度解析操作系统I/O系统:DMA、中断、SPOOLing与设备独立性 很多学《计算机操作系统》的朋友到了第六章输入输出系统第一次感觉到“这玩意儿到底在干嘛”。前面几章讲进程、调度、内存好歹能写出几行代码模拟一下到了输入输出这里教材翻来覆去讲控制器、通道、缓冲区、SPOOLing感觉全是概念离代码越来越远。其实恰恰相反输入输出系统才是你每天写代码时打交道最多的部分。你每敲一个字符程序每次 read 一个文件数据库每次落盘背后全是这一章讲的机制在运转。这一章学好了你对“程序是怎么跟硬件打交道的”会有真正的体感而不只是会背几个名词。这篇文章我按自己讲课和实际排查问题的经验把这章重新拆一遍。不讲废话只说人话把概念落到场景里能跑的实验给操作步骤能踩的坑提前标出来。1. 章节定位I/O 系统在操作系统里的角色1.1 I/O 系统到底解决什么问题先想一个问题你写了一个fopen(data.txt,r)到底发生了什么你的程序在用户态文件在磁盘上磁盘由硬件控制器管理数据要经过总线、控制器、内核缓冲区最后才能拷到你程序的内存里。操作系统的 I/O 子系统就是管这一整条链路的。它要解决的核心问题有三个第一屏蔽硬件差异。磁盘、键盘、网卡、显示器每种设备的寄存器、时序、数据格式都不一样。不可能让每个应用程序员直接去操作这些寄存器那写出来的代码就没法跨设备、跨平台了。操作系统在中间提供统一接口让程序员看到的就是read、write、open这几个系统调用。第二提高 CPU 和设备的工作效率。CPU 的速度和 I/O 设备的速度差好几个数量级。机械硬盘一次寻道好几毫秒CPU 一个时钟才零点几纳秒。如果 CPU 干等设备那就是巨大的浪费。所以出现了中断、DMA、通道这些机制核心思路就一句话让 CPU 启动 I/O 之后赶紧去干别的等设备完事了再来通知它。第三让多个程序安全、高效地共享设备。打印机不能两个进程同时往里塞数据磁盘不能两个进程在同一个扇区乱写。I/O 子系统还要负责分配、排队、隔离让共享不出乱子。这三个问题其实就是整个第六章的骨架。你后面学的每一种技术都能对号入座。1.2 本章知识地图我习惯把这一章分成四块硬件基础设备分类、设备控制器、I/O 端口、DMA、通道。控制方式演进程序直接控制、中断、DMA、通道四种方式一层层升级。软件层次用户层软件、设备独立性层、设备驱动层、中断处理层。核心机制缓冲区管理、SPOOLing、设备分配。前两块是“硬件怎么看”后两块是“软件怎么管”。考试重点和实际应用重点都在后两块但前两块如果不懂后两块就是空中楼阁。我自己教学生的时候经常强调一句话不要只背“DMA 是直接存储器访问”这个定义你要能回答出来“DMA 和中断驱动的本质区别是什么”。答案是是否经过 CPU 中转数据。中断驱动虽然解决了“CPU 等待”的问题但数据还是要 CPU 从控制器寄存器搬到内存DMA 则是控制器直接通过总线把数据写到内存CPU 只在开始和结束时介入。这才是本质差异。后面每一节我都按照“是什么、为什么、怎么用、注意什么”这个节奏来讲尽量让读者看完就能形成自己的逻辑链。2. 硬件基础搞懂设备、控制器、端口和 DMA2.1 设备的两种分类方式设备分类可以按信息交换的单位分也可以按共享属性分考试里两个都考实际工程里两个都有用。按交换单位分就是块设备和字符设备。块设备以“块”为单位读写比如磁盘典型块大小是 512 字节或 4KB。块设备的特点是可以随机访问寻址某个块直接读写不需要顺序来。所以块设备通常支持 mounted 文件系统可以当存储用。字符设备以“字符”为单位读写比如键盘、鼠标、串口。数据是一个字节一个字节来的通常只能顺序访问不能 lseek 跳到某个位置。块设备和字符设备的区别你在 Linux 里能用ls -l /dev直接看到开头的字母b就是块设备c就是字符设备。按共享属性分就是独占设备和共享设备。打印机是典型的独占设备一个进程在用别的进程就得等着。磁盘是可共享设备多个进程可以同时并发访问当然底层要靠文件系统来管理并发冲突。还有一种叫虚拟设备就是用 SPOOLing 技术把独占设备改造成“看起来能共享”的设备后面专门讲。我建议你把这两种分类方式分开记因为它们回答的是不同的问题按交换单位分类解决的是“驱动怎么跟设备打交道”按共享属性分类解决的是“设备分配策略怎么定”。2.2 设备控制器CPU 和设备的“翻译官”设备控制器本质是一块硬件电路板插在总线上一头接 CPU/内存一头接设备。控制器里面有几个关键组成部分数据寄存器/数据缓冲区CPU 和设备之间的数据中转站。比如串口控制器里的接收缓冲寄存器设备来一个字节先放在这CPU 来读。控制寄存器CPU 通过往这里写命令告诉设备要做什么比如“启动读”“磁头寻道”。状态寄存器CPU 从这里读设备当前状态比如“忙”“就绪”“出错”。地址译码逻辑CPU 访问控制器时要知道往哪个地址写是数据寄存器、哪个地址是控制寄存器。这里有一个关键问题CPU 怎么访问这些寄存器有两种方案。第一种叫独立编址端口映射 I/O就是给控制器的每个寄存器分配一个独立的 I/O 端口地址用专门的 IN/OUT 指令访问。x86 就是这么干的in、out指令访问的地址空间和内存地址空间是分开的。第二种叫内存映射 I/O就是把控制器的寄存器映射到内存地址空间里CPU 用普通的mov指令就能读写。ARM 架构常用这种方式你写嵌入式驱动时操作*(volatile uint32_t *)0x40021000这种地址就是在操作寄存器。两种方式各有取舍。独立编址需要专用指令但不会占用内存地址空间内存映射不需要专用指令但要注意不能用缓存所以要 volatile否则 CPU 可能读不到设备刚更新的状态。实际工程中很多系统是两种混用的x86 的显卡、网卡等高速设备通常走内存映射老式的串口、并口走独立编址。2.3 中断和 DMA两个“不让 CPU 傻等”的硬件帮手中断这部分操作系统课程在进程那一章就讲过但第六章里要换个角度看中断是 I/O 设备和 CPU 通信的主要手段。设备完成一个操作后通过中断控制器向 CPU 发一个中断请求CPU 响应后进入中断处理程序完成后续工作。中断有硬中断和软中断之分硬中断是设备发出的软中断是程序主动触发的比如int 0x80系统调用。在 Linux 里中断处理还分上半部和下半部上半部处理紧急的、需要关中断的部分下半部处理耗时的、可以延后的部分。这是现代操作系统为了“中断处理时间尽量短”做的设计考试如果考 Linux 的 I/O 实现会涉及这一块。DMA直接存储器存取是另外一个重要的硬件。没有 DMA 的时候磁盘读一个扇区到内存CPU 要先把数据读到自己寄存器里再写到内存里一个扇区 512 字节就要搬运 512 次。有了 DMA 控制器CPU 只要告诉 DMA“把磁盘地址 X 的数据搬到内存地址 Y长度 512 字节”DMA 控制器就自己通过总线搬运完了搬运完成后发一个中断通知 CPU。CPU 从搬运工变成了包工头这是质的飞跃。DMA 控制器需要配置的寄存器有源地址寄存器、目的地址寄存器、传输长度计数器、控制寄存器方向、突发模式等。有的 DMA 控制器还支持链表描述符可以做散列收集就是把内存中不连续的好几个缓冲区拼成一个连续的 I/O 操作Linux 里sgscatter-gather就是干这个的。使用 DMA 要注意缓存一致性问题。CPU 有高速缓存而 DMA 是直接读写内存的如果缓存里的数据和内存里的数据不一致就会出现错误。这也是为什么现代 DMA 引擎都有 cache coherency 处理或者驱动里用dma_map_single这样的 API 来保证一致性。3. 四种 I/O 控制方式从 CPU 苦力到通道管家这四种方式是本章的核心逻辑线必须串起来理解不能孤立着背。3.1 程序直接控制方式最原始CPU 全程苦等流程很简单CPU 不断读取设备状态寄存器看到“就绪”就搬运一个单位的数据循环往复。比如要从磁盘读一个字节CPU 发出读命令然后不停轮询状态寄存器。设备准备好一个字节后CPU 读数据寄存器存到内存然后继续发下一条命令。这个方式的致命问题就是忙等。CPU 全程被占住设备慢CPU 就空转。对于现代操作系统来说这是不可接受的。但它有一个优点实现最简单适合对性能没要求、或者单片机上的简单场景。我读大学的时候用单片机做过一个串口收发实验就是这种轮询方式。代码大概是while ((UART0-FR (1 4)) ! 0); // 等待发送FIFO为空 UART0-DR ch; // 发送一个字符在裸机环境里这么写没问题但如果你在 Linux 内核驱动里用轮询等设备会被骂死的。工程师圈子里把这种忙等叫 busy-wait是性能杀手。3.2 中断驱动方式设备完事再叫我中断驱动的思路是CPU 发出 I/O 命令后就回去执行其他任务当设备完成操作时向 CPU 发出中断请求CPU 在中断处理程序里接管后续的数据处理。这里有一个关键区别很多人搞混程序直接控制和中断驱动I/O 单位是一个字或一个字节。也就是说中断驱动的数据搬运仍然经过 CPU。比如键盘来一个字符中断处理程序去读数据寄存器把字节放到缓冲区然后返回。每来一个字符就触发一次中断。所以中断驱动解决的问题是“CPU 不用干等”但每传输一个数据单位都要一次中断中断处理本身要保护现场、跳转、恢复现场是有开销的。对于键盘这种低速设备无所谓但要传文件这种大量数据中断次数就太多了CPU 要被频繁打扰。不过中断驱动比轮询先进的地方是确确实实把“等待时间”拿回来了设备在工作的时候CPU 可以去跑别的进程。3.3 DMA 方式数据搬运不再经过 CPUDMA 方式下数据传送的基本单位是块。CPU 只需初始化 DMA 控制器配置源地址、目的地址、长度然后就去做别的事情。DMA 控制器自己完成从设备到内存的数据传输传输完成才发中断通知 CPU。举个例子磁盘读一个 4KB 的块。程序直接控制方式下CPU 读 4096 个字节每读一个字节判断一次状态。中断驱动方式下每个字节触发一次中断如果算上中断的保存/恢复开销比轮询也好不了太多。DMA 方式则是CPU 配置好 DMA然后该干嘛干嘛DMA 控制器通过总线突发模式一下子把整个 4KB 考到内存最后发一个中断。CPU 总共也就被麻烦两次启动时一次、完成时一次。DMA 和应用层读文件的关系在后文第 6 节“从应用请求到硬件执行”里会有完整串联这里先记住结论DMA 是块设备高性能 I/O 的基石。DMA 的一个经典坑是 cache coherence 问题前面提过不再展开但我要提醒的是在面试和考试里如果被问“DMA 有什么缺点”除了 cache 一致性还可以说DMA 需要占用总线在 DMA 传输期间 CPU 访问总线会被仲裁虽然影响不大但在高并发大带宽场景下DMA 也是对系统资源的一种占用。3.4 通道方式有一个专门管 I/O 的小 CPU通道方式可以理解成把 DMA 进一步升级了。DMA 还需要 CPU 去配置各种寄存器通道则是把 I/O 操作的指令集做成一个专门的处理器CPU 只需要发起一个“通道程序”通道自己解析指令完成一组 I/O 操作然后中断报告结果。通道有自己的指令系统指令包括读、写、反读、控制、转移等。通道程序就是由这些指令组成的存放在内存里。CPU 执行专门的外部指令如“启动 I/O”来启动通道通道再去控制设备控制器。通道的诞生背景是大中型计算机要连接大量外设如果每个设备都靠 CPU 来管理CPU 忙不过来。通道相当于一个 I/O 专用处理器可以把 CPU 从繁琐的 I/O 管理中彻底解放出来。通道按独立程度分三种字节多路通道以字节为单位共享通道给多个低速设备数据选择通道一次只为一个设备服务适合高速块设备数组多路通道以块为单位在设备之间切换兼具两者优点。考研操作系统里常考这三个的适用场景理解它们的原则就能推出来按数据单位和服务方式是“轮流”还是“独占”去区分。3.5 四种控制方式的对比速查表为了好记我把四种方式的对比整理成一个表。面试前、考试前瞄一眼能帮你快速找回主线。控制方式数据单位是否经过 CPU 搬运CPU 工作量适用场景程序直接控制字/字节是最大全程忙等简单的嵌入式裸机中断驱动字/字节是较大每次传输都中断低速字符设备DMA块否控制器直接搬小启动和结束时介入高速块设备通道块/组否通道处理器管理最小仅发启动指令大型计算机海量外设一条主线串下来就是CPU 从亲自干活变成等人叫变成只管两头变成彻底甩手。这个演化逻辑记清楚比背十条定义都管用。4. I/O 软件层次设备独立性的实现路径4.1 四层结构每层干好自己的活I/O 软件在逻辑上分为四层从上到下依次是用户层软件就是你的应用程序里的 read、write、printf 这些库函数和系统调用。这层负责把用户的 I/O 请求转成内核能认的调用提供友好接口。设备独立性软件设备无关层这一层做的事情很多包括给上层提供统一接口设备命名与映射把设备名映射到设备驱动保护设备为进程分配设备缓冲管理差错报告还有 SPOOLing 的实现也在这里。设备驱动程序每个设备类型对应一个驱动负责把上层的逻辑请求翻译成具体的硬件操作。比如把“读块号 100”翻译成“向磁盘控制器发送寻道命令、读扇区命令配置 DMA”。中断处理程序设备完成操作后发中断中断处理程序负责读取设备状态、把数据从缓冲区取出、唤醒等待的进程等。这四层的设计哲学就是分层。每一层只跟相邻层打交道上层不用关心设备的物理细节下层不用关心应用的具体意图。4.2 设备独立性为什么换设备不用改应用设备独立性也叫设备无关性是指应用程序不直接使用物理设备名而是使用逻辑设备名比如“打印机”操作系统在运行时才把逻辑设备映射到物理设备。这样带来的好处非常明显如果换了一台打印机只要新打印机的驱动注册到系统里应用程序完全不用改继续用逻辑设备名请求就行。在 Linux 里这种设计体现在设备文件上。/dev/sda、/dev/ttyS0这些设备文件就是设备独立性的体现用户程序通过文件接口访问设备具体驱动由内核根据主次设备号查找。你写程序用open(/dev/ttyS0, O_RDWR)内核根据这个设备文件的设备号找到对应的串口驱动然后调用它的操作函数。我自己写驱动模块的时候对“设备独立性”的体感特别深内核里file_operations结构体就是驱动层暴露给上层的统一接口。上层只需要会open、read、write、ioctl至于底层是 SPI、I2C 还是 PCIe完全屏蔽。面试问“设备无关性如何实现”你答出这层意思就够了。4.3 设备驱动与中断处理驱动干活中断善后设备驱动层和中断处理层的分工是驱动负责“发起”中断处理负责“收尾”。驱动做的事情包括校验上层传下来的参数把逻辑请求转成设备命令写设备控制器的寄存器配置 DMA然后返回阻塞或非阻塞。中断处理做的事情包括保存现场读取设备状态寄存器确认成功还是失败如果成功把数据从控制器缓冲区搬到内核缓冲区解除阻塞、唤醒等待的进程恢复现场返回。这里有一个关键设计问题驱动通常运行在进程上下文可以睡眠中断处理运行在中断上下文不能睡眠。所以在 Linux 里驱动里可以调用wait_event、sleep这类函数但中断处理程序里绝对不能。在写驱动时如果中断处理里需要做大量工作就把工作推到下半部tasklet 或 workqueue。很多教材会把驱动和中断处理都归到“内核层”但你要意识到它们在上下文、可调用函数、运行环境上有本质区别这个在 5.3 的设备分配里还要用到分配设备时可能要等待设备空闲这时进程要睡眠。5. 核心机制缓冲、SPOOLing 与设备分配这一节是实操里最常碰到的内容。文件读写为什么会卡为什么打印机共享不会乱码多进程同时写磁盘文件系统怎么处理都是这些机制在起作用。5.1 缓冲区管理CPU 和设备的速度差怎么填缓冲区就是一块内存区域用于暂存数据让速度不匹配的双方能“异步”工作。为什么必须有缓冲我总结三个原因第一匹配 CPU 和设备的速度差。CPU 纳秒级磁盘毫秒级中间没缓冲CPU 就得等设备或者频繁被打断。第二减少对 CPU 的中断频率。比如网卡每来一个包就中断一次如果每次中断只处理一个字节CPU 就被淹没了。有了缓冲可以攒一批数据或者用 DMA 直接批量搬走。第三减少数据拷贝次数、提高效率。这个要分清缓冲本身是拷贝但合理设计可以减少“应用-内核-设备”之间不必要的来回拷贝。缓冲区设计里有几个常见模式单缓冲和双缓冲设备先填缓冲区ACPU 同时处理缓冲区B交替使用。双缓冲可以让生产和消费并行。环形缓冲一组固定大小的缓冲区组成环写指针和生产、读指针和消费。网卡驱动、串口接收都常用这种结构。Linux 里struct ring_buffer就是这么干的。缓冲池把多个缓冲区统一管理起来按需分配用完后回收避免频繁申请释放内存。教材里还会讲“双缓冲为什么能把处理时间和传输时间重叠”。我举一个具体的场景假设设备传输一块数据时间 TCPU 处理这块数据时间 C如果单缓冲总时间就是 TC等设备传完再处理双缓冲的话如果 C T设备一直在传CPU 每块数据只要等一段不超过 T 的时间平均时间接近 max(T, C)。这就是教材里那个计算题的来源理解了重叠逻辑公式不用背。我自己调试网络抓包工具的时候环形缓冲的知识帮了大忙。理解tail、head、drops这几个计数器才能看懂netstat -i的丢包统计。如果你做高性能 I/O这一块是必修。5.2 SPOOLing把独占设备变成“虚拟共享设备”SPOOLing 的全称是 Simultaneous Peripheral Operation On-Line也就是假脱机操作。它解决的问题是打印机是独占设备但一个系统里可能有几十个进程都要打印怎么办最朴素的办法是排队一个进程打印完下一个进程再上。但打印是慢速I/O后面进程阻塞等待系统效率太低。SPOOLing 的思路是在磁盘上划出两块区域输入井和输出井同时在内存里维护输入/输出缓冲区。以打印为例进程要打印时不是直接把数据扔给打印机而是先把数据写到磁盘上的输出井中这个过程很快磁盘速度比打印机快好几个数量级。然后由 SPOOLing 系统后台进程按照队列顺序慢慢地把输出井中的数据送往打印机。这样对进程来说它感觉自己“独占”了一台打印机瞬间就完成了打印请求不用等真正的打印过程。打印机从“独占设备”变成了“看起来可以并发共享的虚拟设备”这就是虚拟设备的含义。SPOOLing 技术的应用远不止打印机。在 Linux 里最典型的就是打印系统的 CUPS 队列。你调用lp命令提交打印任务实际上是先写到队列里CUPS 的后台守护进程再逐个打印。而lpr、lpq这些命令就是 SPOOLing 队列的用户接口。我再举一个不算打印机但本质相同的例子邮件服务器。你发一封邮件SMTP 客户端把邮件投递到本地的邮件队列目录里后台进程再慢慢尝试发送。客户端不用等服务器真的把邮件送达到对方经历的就是“写入队列”这个过程这是 SPOOLing 思想在网络服务中的应用。理解 SPOOLing 的要点就是三个字缓磁盘缓冲、队列排队调度、异步后台执行。5.3 设备分配怎么分才不打架设备分配涉及两个问题什么时候分配设备用什么数据结构记录设备的状态教材上会讲设备分配表、控制器分配表、通道分配表考试喜欢考“分配顺序”先分配设备再分配控制器再分配通道。这张分配顺序的逻辑是数据从设备上来要经过控制器、总线/通道才能到内存哪一环没分配到链路都不通。这跟内存管理分配页表、段表一样整个资源链都得配齐。我在实际读驱动代码时对这个过程一般不怎么关注因为现代操作系统主要用设备文件 驱动模型来管理动态分配和释放都自动化了。但教材里的知识点能帮你理解“为什么系统有时会报告‘设备忙’”因为某个链路资源被占用了。设备分配的安全性策略也是常考点。安全分配方式是在设备分配时一次性占有所有需要的设备然后在 I/O 过程中不再申请新设备不安全分配方式是边用边申请可能出现死锁两个进程各自占了一个设备还要对方占的设备。这其实是资源分配的一般性原则跟读者进程同步那一章的死锁知识是通的。对了还有一个概念容易混淆设备分配中的“逻辑设备名”。教材上说用户用逻辑设备名请求设备系统把它映射到物理设备名。映射过程就是查逻辑设备表表中记录了逻辑设备名对应的物理设备名和驱动程序入口地址。这就是设备独立性的落地——上层用逻辑名底层转到物理设备。5.4 与设备独立性相关的完整链条把 4.2 和 5.3 合起来看设备独立性的完整链条就是用户程序 → 逻辑设备名 → 逻辑设备表 → 物理设备名 → 设备驱动程序入口 → 设备控制器 → 物理设备这一条链路就是第 4 节“I/O 软件层次”和第 5 节“设备分配”的桥梁。考试如果考综合题叫你画出“一次打印请求从应用到打印机的完整流程”本质上就是让你把这条链路用 SPOOLing 串起来。我来画一条完整链路文字版应用程序调用printfC 库把它封装成write系统调用。内核根据文件描述符找到对应的文件对象这个 fd 是/dev/lp0打印机设备文件。文件对象指向设备独立层的打印逻辑设备调用设备驱动里的write函数。驱动把这个打印任务写入 SPOOLing 输出井磁盘上的队列。应用返回打印任务排队等待。CUPS 后台进程从输出井取出任务调用真正的打印机驱动。打印机驱动通过设备控制器把数据送往打印机的数据缓冲区。打印机控制器控制打印头/激光单元完成物理打印。注意第 4 步是关键应用在写入输出井后就返回了后续打印是异步的。这就是为什么你用lp提交任务命令行立刻返回但打印机要过一会儿才动。6. 一次完整串联从 read 系统调用到磁盘硬件这一节我把前面所有知识串成一个完整的例子。场景是一个进程执行read(fd, buf, 4096)从磁盘文件读 4KB 数据。我把全过程分成八个阶段你看看每一步对应第六章的哪个知识点用户态调用read触发系统调用陷入内核。这一层属于用户层软件和系统调用接口。内核虚拟文件系统层根据 fd找到对应文件的内核态表示struct file获取文件当前的偏移量。根据文件所在文件系统定位到磁盘上的逻辑块号然后调用块设备驱动层的接口。块设备驱动根据逻辑块号计算柱面、磁头、扇区如果是机械盘或者直接换算成 NAND 的物理页地址如果是 SSD。这里用到设备驱动层。驱动判断是否可以直接用 DMA 读如果可以就初始化 DMA 控制器配置源地址设备控制器里的数据缓冲区、目的地址内存页、长度然后启动。DMA 控制器控制设备执行数据传送完成后发中断。这个中断的处理程序把 DMA 完成状态记录下来唤醒等待这个 I/O 的进程。注意数据到了这里还只是在内核的页缓存page cache里不在用户态的buf里。内核把数据从页缓存拷贝到用户空间的buf返回给用户态。如果文件在页缓存里已经有数据之前读过第 4~6 步可以省掉很多这就是页面缓存的作用。系统调用返回用户程序继续执行。这个完整流程几乎把前面所有章节的知识都串起来了系统调用、内核态用户态切换、页缓存、块设备层、设备驱动、中断、DMA。学操作系统要想形成整体观一定要自己走一遍这样的“贯穿流程”。我在 Linux 里用一个简单的strace命令就能看到真实的过程。比如strace -f -e traceread,openat cat /etc/hostname你会看到 openat、read 这些系统调用。如果想深入观察 I/O 在内核里的行为可以用blktraceblktrace -d /dev/sda -o - | blkparse -i -这个能看到每次 I/O 请求从进程到块设备的完整轨迹。我实习的时候靠这个排查过虚拟机磁盘性能问题它能清楚地显示请求在哪一层排队、在哪一层合并。7. 常见问题与易错点考试和实战中的那些坑7.1 概念辨析题最容易混淆的几个点第一个坑DMA 和中断驱动很多人觉得 DMA 也用中断啊两者区别到底是什么。区别在于数据传送主体。中断驱动是 CPU 在中断处理程序里逐字搬运DMA 是控制器直接搬运中断只是“搬家完成”的通知。第二个坑SPOOLing 是不是就是缓冲区不是。缓冲区在内存SPOOLing 是在磁盘上的缓冲井 后台进程调度。缓冲区解决速度匹配SPOOLing 解决独占设备共享二者解决的问题不同。第三个坑块设备和字符设备是不是像文件和目录那样互斥不是。有的设备既支持块读写也支持字符模式访问比如/dev/sda是块设备但/dev/sda1如果直接读块也行而/dev/ttyS0是字符设备。分类标准是访问方式不是设备类型本身。第四个坑通道和 DMA 的关系。DMA 是通道的一种工作方式吗不能简单这么说。通道拥有自己的指令系统能执行通道程序管理多个设备DMA 只是单一的数据搬运动作不能“编程”。通道比 DMA 更高一层通道程序里可以包含多次 DMA 操作。这四个坑考试时几乎年年有人掉进去。我当助教改卷子的时候见过太多次“反正都用了中断所以认为 DMA 和中断驱动一样”的答案只能说抓住“数据经不经过 CPU”这个判据所有问题迎刃而解。7.2 计算题中断次数、DMA 时间、缓冲重合时间这一章的计算题主要集中在两块中断/ DMA 传输时间和缓冲区空闲/重合计算。先说中断次数。比如一个波特率 9600 的串口每秒传 9600 个字符每个字符 8 位。如果每收到一个字符触发一次中断那每秒中断 9600 次。CPU 每次中断处理假设需要 50 微秒那 CPU 在中断上就要花 480 毫秒接近一半 CPU 时间。这就是为什么高速设备必须用 DMA中断开销根本扛不住。DMA 时间的计算假设 DMA 传输 4KB 数据总线时钟 100MHz每次突发传输 32 位4 字节那么需要 4096 / 4 1024 个总线周期每个周期 10 纳秒总时间约 10.24 微秒。这个时间远小于 CPU 中断准备 数据搬运的时间。所以高性能存储设备全部走 DMA。缓冲重合时间的计算题目通常给一个设备传输时间 TCPU 处理时间 C问单缓冲和双缓冲的块处理时间。我直接给经验公式单缓冲每块总耗时 max(T, C) 很小的调度开销。要注意如果 C TCPU 会等待设备如果 C T设备会等待 CPU所以两项取大。双缓冲每块总耗时 max(T, C)。因为两块交替流水线重叠理论上吞吐量只受最慢一环限制。这个公式看起来很简单但好多同学会忘掉“取大”这一步直接 TC那就错了。你再回头想想我前面说的“双缓冲把时间重叠”其实就用这个公式理解最直白。7.3 实际工程中的排查思路实战里I/O 相关问题的排查比书本上的概念题复杂很多但思路是相通的。我把自己踩过的坑整理成几个排查步骤第一看是不是中断风暴。用cat /proc/interrupts看某个设备的中断计数是不是在疯狂增长。如果中断次数每秒上千万次那可能某些老设备还在用中断驱动方式搬运大数据严重拖累 CPU。这时候优化方向就是启用 DMA 或调整中断合并。第二看是不是缓冲配得不对。用vmstat看bi、bo、wa三个字段。wa偏高说明 CPU 在等待 I/O很可能是缓冲区不足或设备太慢。我之前调一个 Redis 持久化性能wa一直 30% 以上后来发现是内核的dirty_ratio设置不合理导致大量回写加缓冲参数之后就降下来了。第三看是不是设备分配排队太长。Linux 里的 block layer 有 I/O 调度算法比如mq-deadline、kyber、bfq。如果发现 I/O 延迟很大但设备本身的吞吐率还有余量可能是调度器没有按应用需求来切换调度器有时立竿见影。这个过程需要在「设备队列深度、批大小、目标延迟」之间做取舍没有放之四海而皆准的配置。第四看是不是文件系统层导致的额外 I/O。iostat -x里有avgqu-sz、await、svctm等指标。如果await远大于svctm说明请求在队列里等的时间长可能是设备饱和或调度器排队。如果两者接近说明设备本身处理慢可能是磁盘坏了或者硬件性能不够。这一套排查思路其实对应的是第六章各个软件层次在真实系统中的表现。教材上的每一个概念在proc、sysfs、iostat、blktrace这些工具里都有落脚点。我觉得学这一章最好的方式就是一边读教材一边在 Linux 上敲命令验证而不是只盯着教科书上的概念图看。8. 学习与复习建议怎么把这一章真正吃透如果你是在校学生正在准备考研或者期末考试我给三点建议第一先把四种 I/O 控制方式的演进逻辑自己讲一遍。不要拿着书念合上书从“CPU 忙等”讲到“中断”讲到“DMA”讲到“通道”。能讲出来说明逻辑链通了。第二做几道缓冲重合计算的题。这类题是固定套路公式搞清楚之后很好拿分。但注意审题看清楚是单缓冲还是双缓冲是“传输处理”还是“读入读出”。第三画一次完整的打印请求流程图跟同学互问。SPOOLing 是本章最常考的案例也是实际系统用的最多的技术值得多花时间。如果你是在职工程师读这一章是为了补操作系统基础我更推荐一种方法结合 Linux 源码看。重点看这五个文件block/blk-core.c块设备 I/O 请求的入口和核心流程。drivers/char/mem.c字符设备/dev/mem、/dev/null的简单实现非常适合入门。drivers/block/virtio_blk.c一个真实的块驱动看它怎么用 DMA 和中断。kernel/irq/manage.c中断管理核心看 request_irq 和中断线程化。fs/buffer.c缓冲区管理和页缓存交互。不要一开始就啃最复杂的驱动从简单驱动入手。我自己是这样一路走过来发现教材里的概念在源码里都能对上。比如virtio_blk里virtqueue就是教材“环形缓冲”的工业实现。最后一句话送给读者输入输出系统这章最忌讳的就是“只见树木不见森林”。如果你发现自己背了很多名词但连“一次 read 系统调用数据到底是怎么到磁盘的”都说不清楚那说明还停留在碎片化记忆阶段。逼自己把那条链路完整串一遍才是真的学会了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询