设备的分配回收与缓冲区管理——I/O 性能的最后拼图 设备的分配回收与缓冲区管理——I/O 性能的最后拼图当你同时按下 CtrlS 保存文件和 CtrlP 打印文档时操作系统面临一个棘手的问题谁来用打印机谁先访问磁盘如果磁盘正在写入打印任务要不要等前面四篇文章分别讨论了 I/O 设备的硬件基础、四种控制方式、I/O 软件层次结构和 SPOOLing 虚拟化技术。但直到现在我们才触及一个最根本的工程问题——多个进程争用同一设备时操作系统如何决策设备分配回答了谁先用的问题缓冲区管理回答了速率不匹配怎么办的问题。两者共同构成 I/O 性能的最后拼图。核心要点设备分配的三个决定因素设备固有属性独占 / 共享 / 虚拟、分配算法FCFS / 优先级 / 短任务优先和安全性策略安全分配破坏请求保持不会死锁但 CPU-I/O 串行不安全分配可并行但可能死锁操作系统用SDT → DCT → COCT → CHCT四层数据结构管理设备-控制器-通道的层级关系只有当设备、控制器、通道三者均分配成功设备分配才算完成原始分配使用物理设备名不透明、不可替换、无法利用同类空闲设备改进方案使用逻辑设备名设备类型由 OS 通过 LUT 完成映射单缓冲处理一块数据的平均用时 Max(C, T) M双缓冲 Max(T, C M)。T 设备输入时间M 缓冲区到工作区传送时间C CPU 处理时间。公式的物理含义系统吞吐由最慢环节瓶颈决定缓冲区管理的两条主线——缓和 CPU-I/O 速度不匹配、减少 CPU 中断频率——构成了设备独立性软件层的核心职责设备分配的三要素——固有属性、分配算法与安全性决定一个 I/O 设备怎么分配的因素有三个设备本身的固有属性是什么类型的设备、操作系统采用的分配算法按什么规则排队以及安全性策略分配后进程是否被阻塞。固有属性独占、共享与虚拟设备按其资源特性分为三类**独占设备Exclusive Device**在一个时段内只能分配给一个进程使用。打印机是最直观的例子——两个进程交替向打印机输出数据打印出来的纸张上两人的内容会交错在一起谁都无法使用。独占设备的分配策略是一个时段一个进程进程用完释放后其他进程才能获得。**共享设备Shared Device**可以同时分配给多个进程在宏观上表现为同时使用在微观上各进程交替访问。磁盘是共享设备的典型——多个进程的读写请求通过磁盘调度算法如 SSTF、SCAN排队宏观上所有进程都在用微观上磁头在依次响应。共享设备的分配策略是大家都能用按调度算法排队。**虚拟设备Virtual Device**是通过 SPOOLing 技术将独占设备改造成的共享设备。打印机接入 SPOOLing 系统后每个进程的输出先写入磁盘上的输出井而非直接送打印机由 SPOOLing 守护进程统一调度打印——每个进程感觉自己独占了一台打印机实际共享的是同一台物理设备 [共识]。关于 SPOOLing 的详细原理见本系列第四篇。分配算法排队规则当多个进程竞争同一设备时操作系统使用以下分配算法之一决定谁先得到设备 先来先服务FCFSFirst Come First Served按请求到达顺序分配。实现简单、公平但不会考虑任务紧急程度。优先级高者优先赋予重要进程如系统进程更高的分配优先级适合实时系统场景。短任务优先优先将设备分配给预计使用时间最短的进程有利于提高设备整体利用率。这些算法与进程调度中的调度算法一脉相承。工程实践中设备分配算法通常与设备队列挂在 DCT 上的等待队列结合实现算法选择取决于设备类型和系统设计目标。安全性分配后就阻塞还是继续运行安全分配方式Safe Allocation的策略是进程申请设备操作系统分配成功后立刻阻塞该进程直到 I/O 操作完成才唤醒。结果是进程在持有设备期间被阻塞无法再去申请新的资源——这恰好破坏了死锁的请求和保持条件因此安全分配方式不会产生死锁 [引用王道 5.2_3][共识]。但代价是每个进程在一个时段内只能使用一个设备CPU 和 I/O 操作必须串行执行——进程算一会儿、等 I/O、再算一会儿、再等 I/O——CPU 和设备之间存在大量空闲等待时间。不安全分配方式Unsafe Allocation的策略相反分配设备后进程不阻塞可以继续执行并发出新的 I/O 请求。好处是进程可以同时使用多个设备磁盘读写 网络收发CPU 和 I/O 可以并行推进系统效率更高。但代价是——进程已经拿着磁盘控制器又去申请打印机而打印机正在被另一个拿着网络设备的进程占用——这就构成了循环等待链可能导致死锁。两者的取舍本质上是安全性与设备利用率之间的 trade-off。从安全分配可以自然过渡到死锁预防策略中的破坏请求与保持条件。静态分配 vs 动态分配这对概念与安全/不安全分配不同但经常在同一道题中出现。静态分配进程在运行前一次性申请所有需要的设备操作系统一次性全部分配如果不能全部分配就等待进程运行结束后一次归还。优点是不会死锁一次性获取所有资源避免拿着旧的申请新的缺点是设备利用率低——设备在进程运行期间被占用但并非时刻都在使用。动态分配进程在运行过程中按需动态申请设备用完即归还。优点是设备利用率高缺点是可能死锁。设备分配管理的四层数据结构——SDT→DCT→COCT→CHCT设备、控制器、通道之间存在严格的层级控制关系——一个通道可以控制多个控制器一个控制器可以控制多个设备。操作系统使用 SDT → DCT → COCT → CHCT 四层数据结构逐层管理这一层级 。SDT系统设备表System Device TableSDT 是设备管理的顶层入口位于系统全局范围内记录系统中全部物理设备。每个设备占用一个表目包含四个关键字段字段含义设备类型Device Type标识设备属于哪一类如 printer、disk、network设备标识符Device ID该设备的唯一编号DCT 指针指向该设备对应的设备控制表DCT——这是向下层查找的入口驱动程序入口该设备对应的设备驱动程序在内核中的地址当用户进程发出 I/O 请求时操作系统首先查阅 SDT找到目标设备后通过 DCT 指针进入下一层。DCT设备控制表Device Control Table每个物理设备拥有一张独立的 DCT存放该设备的所有运行时状态信息设备类型和标识符与 SDT 中对应字段一致用于交叉校验。设备状态字符串或状态位典型取值包括忙碌Busy、“空闲”Idle、“故障”Fault。COCT 指针指向该设备所连接的控制器控制表COCT——这是向下查找控制器的入口。设备等待队列队首指针指向等待该设备的进程队列。分配时如果设备忙碌当前进程被挂入此队列。DCT 是分配逻辑的核心枢纽——操作系统在 DCT 中检查设备是否可用不可用则入队等待。COCT控制器控制表Controller Control Table每个 I/O 控制器拥有一张 COCT管理控制器自身的分配状态 控制器标识符控制器的唯一编号。控制器状态忙碌 / 空闲 / 故障。CHCT 指针指向该控制器所连接通道的通道控制表CHCT。控制器等待队列指针指向等待该控制器的进程队列。一台控制器可以连接多个设备例如一块 SATA 控制器可连接多块硬盘因此多个 DCT 可能指向同一个 COCT。CHCT通道控制表Channel Control Table每个通道拥有一张 CHCT记录通道的分配状态通道标识符通道的唯一编号。通道状态忙碌 / 空闲 / 故障。连接的控制器表首址指向该通道所连接的所有控制器的 COCT 列表。通道等待队列指针指向等待该通道的进程队列。H3: 四张表各自为什么存在——设计动机一个自然的问题为什么需要四层表而不是把设备、控制器、通道的信息合并到一张大表里答案在于层次化资源管理的解耦需求。设备、控制器、通道是三种不同粒度的物理资源——一台通道控制多个控制器一台控制器管理多个设备。如果合并为一张表一个设备的状态变化需要同步更新表中所有关联的控制器的信息耦合度极高。四层分离的设计使得每一层只关注自己这一级的分配——DCT 只管设备是否忙碌、COCT 只管控制器是否可用、CHCT 只管通道是否有空。当设备更换控制器时例如硬盘从 SATA 控制器 1 移到 SATA 控制器 2只需修改 DCT 中的 COCT 指针其他层不受影响。这正是分层架构的经典优势——隔离变化降低耦合。结论分配流程沿 SDT → DCT → COCT → CHCT 逐层查找 [引用王道 5.2_3]① 从 SDT 找到目标设备的 DCT② 检查 DCT 状态忙碌则挂等待队列③ 通过 COCT 指针找到控制器表检查状态④ 通过 CHCT 指针找到通道表检查状态。只有设备 控制器 通道三者均为空闲且均分配成功设备分配才算完成——任何一环忙碌都会导致分配失败进程被挂入对应的等待队列 [共识]。这条结论在 408 选择题中反复出现。题目常给的干扰选项是只要设备空闲就可以分配——忽略控制器和通道的检查导致误选。记忆口诀系统看设备设备看控制器控制器看通道三关全过才行。设备分配步骤——从物理设备名到逻辑设备名的改进原始分配步骤使用物理设备名在早期的操作系统中设备分配使用物理设备名——用户在编程时必须指定具体的设备编号如打印机 #3或磁盘控制器 #2。原始分配流程分四步用户进程提供物理设备名→ 操作系统查 SDT找到对应设备的表目。查 DCT设备空闲则分配忙碌则将进程挂入设备等待队列。查 COCT控制器空闲则分配忙碌则挂入控制器等待队列。查 CHCT通道空闲则分配忙碌则挂入通道等待队列。只有四步全部通过设备分配才算成功。这种方案有三个严重缺陷第一底层物理细节对用户不透明——程序员需要知道系统中每个设备的物理编号。第二程序绑定死了一个特定设备——如果把打印机从打印机 #3换到打印机 #1程序必须修改源码重新编译。第三同类型的其他设备即使空闲也无法使用——打印机 #3被占用用户必须等即便打印机 #1正闲着。改进方案逻辑设备名 LUT改进方案的核心思路是用户只需提供逻辑设备名即设备类型由操作系统在系统范围内查找该类型下任意一个空闲设备。流程演变为用户提供逻辑设备名如打印机而非打印机 #3。操作系统在 SDT 中遍历该类型设备找到第一个状态为空闲的。在**逻辑设备表LUTLogical Unit Table**中新增一个映射项逻辑设备名 → 物理设备 ID。此后所有对该逻辑设备的 I/O 操作操作系统通过 LUT 自动转换为对对应物理设备的操作。LUT 的两种管理方式——每个用户一张表 vs 整个系统一张表——属于设备独立性软件的范畴详见本系列第三篇关于 I/O 软件层次结构的讨论。这一改进带来的好处是结构性的 [共识]用户程序不再绑定特定物理设备设备故障后系统可透明替换到同类备用设备多个同类型设备之间实现了自然的负载均衡OS 自动选择空闲的那台。Linux 中的/dev/sda、/dev/printer等设备文件正是逻辑设备名思想的现代实践——用户看到的是统一命名规则下的抽象设备而非SCSI 控制器 #0 上的硬盘 #0这种物理标识。缓冲区管理的动机——为什么需要缓冲区缓冲区Buffer是一个存储区域可由内存实现主流方式也可由硬件寄存器实现成本高、容量小仅用于 TLB 等对速度要求极高的场景。缓冲区管理的两大核心作用第一缓和 CPU 与 I/O 设备之间的速度不匹配矛盾。CPU 处理一个数据块可能只需微秒级而磁盘传输同一块数据需要毫秒级——两者之间有 3 个数量级的速度差。如果没有缓冲区CPU 每次只能等设备慢慢传完大量的计算能力被浪费。缓冲区充当了速率适配器的角色——设备慢慢把数据送进缓冲区CPU 快速从缓冲区取走处理两者在自己的节奏上运转。第二减少对 CPU 的中断频率。字符设备如键盘每产生一个字符就需要一次中断来通知 CPU 处理。如果有缓冲区例如 128 字节的键盘缓冲设备可以将多个字符攒够再触发一次中断——中断次数立刻降为原来的 1/128。这个效果在低速设备上尤其显著——中断处理本身的上下文切换开销往往比处理一个字符的开销大得多 [经验]。缓冲区管理属于 I/O 软件层次结构中设备独立性软件层的职责 [引用王道 5.2_1]。也就是说不管底层是什么设备键盘还是磁盘使用缓冲区的策略和逻辑由设备独立性软件统一管理设备驱动程序只负责与具体硬件的交互。这种层次划分使得缓冲区机制对所有 I/O 设备通用——你不需要为键盘写一套缓冲逻辑、再为磁盘写另一套。关于硬件缓冲的补充TLBTranslation Lookaside Buffer快表本质上就是页表查找的硬件缓冲区——CPU 先查 TLB快在芯片内未命中再查内存中的页表慢。TLB 的设计动机弥补速度差异、减少访存次数与本节讨论的内存缓冲区完全一致只是实现方式从软件变成了硬件寄存器。详见基本分页储存管理中关于 TLB 的讨论。单缓冲与双缓冲的性能建模——计算公式的严格推导这是 408 考研 I/O 管理章节最高频的计算考点。以下推导不是让你背公式而是帮你建立一种思维习惯——拿到处理时间问题先画时序图再找稳定周期结论自然浮现。变量定义与初始状态约定符号约定全文统一TTransfer设备将一块数据输入到缓冲区所需的时间设备输入时间。MMove将数据从缓冲区传送到用户工作区所需的时间。CComputeCPU 处理一块数据所需的时间。两个关键初始状态约定计算时必须先确认单缓冲初始时工作区已有一块数据已满缓冲区为空。处理开始时设备立即向缓冲区输入下一块同时 CPU 处理工作区中的数据。双缓冲初始时工作区为空Buffer1 已满有一块待处理数据Buffer2 为空。处理开始时CPU 从 Buffer1 取走数据到工作区耗时 M然后处理耗时 C同时设备向 Buffer2 输入数据耗时 T。单缓冲推导Max(C, T) M场景一T C设备比 CPU 慢。处理第一块数据的流程 [经验]工作区已有数据CPU 立即处理耗时 C。同时设备向缓冲区输入下一块数据耗时 T。CPU 先处理完因为 T C但缓冲区的数据还没输完——CPU 必须等待T - C 这段时间 CPU 空闲。T 时刻设备输入完成。数据从缓冲区传送到工作区耗时 M。M 时刻工作区就绪CPU 开始处理第二块耗时 C。同时设备开始输入第三块耗时 T。这是一个以 T M 为周期的循环过程每块数据的总处理时间 T M。CPU 始终在等设备——设备是瓶颈。场景二T CCPU 比设备慢。工作区已有数据CPU 处理耗时 C。同时设备输入耗时 T。T 时刻设备输入完成。但 CPU 还没处理完因为 T C缓冲区数据无法传送到工作区——设备必须等待C - T 这段时间设备空闲。C 时刻CPU 处理完成。数据从缓冲区传送到工作区耗时 M。C M 时刻CPU 开始处理第二块耗时 C。同时设备开始输入第三块耗时 T。稳定周期 C M。设备始终在等 CPU——CPU 是瓶颈。两种情况统一为一个公式处理每块数据的平均用时 Max(C, T) M。推导揭示了一个关键洞察M 是无论如何都要付出的搬运税——不管是 T C 还是 T C数据从缓冲区搬运到工作区的 M 时间永远存在不受瓶颈影响。这也回答了 FAQ 中的经典问题“M 为什么总是要加”双缓冲推导Max(T, C M)双缓冲的核心优势在于输入和处理可以并行在两个独立缓冲区上进行。初始状态工作区为空Buffer1 满Buffer2 空。场景一T C M设备输入慢于传送 处理的总和。CPU 从 Buffer1 取出数据到工作区M然后处理C。同时设备向 Buffer2 输入数据T。CPU 处理完时C M 时刻设备还在向 Buffer2 输入因为 T C M——CPU 必须等待。T 时刻设备输入完成。数据从 Buffer2 送工作区MCPU 处理C。同时设备开始向 Buffer1 输入。由于 I/O 始终赶不上 CPU 的步伐CPU 总是要等设备。稳定周期由设备控制 T。场景二T C M设备输入快于传送 处理的总和或两者相等。CPU 从 Buffer1 取数据M处理C。同时设备向 Buffer2 输入T。T 时刻设备输入完成——但 CPU 还在处理中Buffer1 还在被占用。设备可以提前向 Buffer1 输入只要 Buffer1 的旧数据已被送走且处理正在进行或者等待 Buffer 释放后立即开始。C M 时刻CPU 处理完成立即从 Buffer2 取下一块M处理C。设备早已就绪Buffer 中数据可用。设备输入不再成为瓶颈。稳定周期由 CPU传送决定 C M。统一公式处理每块数据的平均用时 Max(T, C M)。核心对比与物理含义条件单缓冲用时双缓冲用时谁更快T C MT MT双缓冲快 MC TC MC M相同C M T CT MC M取决于 T 与 CM 的关系物理含义总结 [经验]单缓冲用时 瓶颈Max(C, T) 搬运税M。双缓冲用时 Max(T, CM)——双缓冲把搬运合入了 CPU 一侧的捆绑时间中如果设备足够快T CM搬运开销被并行掩盖如果设备太慢T CM设备本身成为唯一瓶颈。双缓冲消除了单缓冲中CPU 空等设备或设备空等 CPU的刚性串行约束将 M 纳入了可被并行覆盖的范围。H3: 考研答题技巧——从初始状态到稳定周期408 考试中缓冲计算题的三种常见陷阱 陷阱一忽略初始状态。单缓冲初始有工作区满缓冲区空双缓冲初始有一个缓冲满另一个空工作区空。如果题目给出了不同的初始状态——按题目给定状态画第一块的处理时序第二块开始进入稳定周期用稳定周期公式计算后续所有块的时间。陷阱二混淆 T、M、C 的取值方向。T 是设备 → 缓冲区的时间M 是缓冲区 → 工作区的时间。两个方向不同千万不要把 T 当成缓冲区到 CPU 的总时间。考试中遇到描述模糊的情况根据上下文判断——如果题目说设备传输时间那是 T如果题目说从缓冲区读取时间那是 M 或 C 的一部分。陷阱三直接套公式不看条件。公式 Max(C, T) M 和 Max(T, C M) 只适用于处理一块数据的平均时间这个提问方向。如果题目问的是处理 N 块数据的总时间——第一块的时间往往与后续不同初始状态差异需要单独计算第一块再对剩余 N-1 块使用公式。循环缓冲与缓冲池——进阶概念单缓冲和双缓冲是 I/O 缓冲的基本形态。当并发度进一步提高时两种进阶方案进入考虑范围。循环缓冲Circular Buffer将多个缓冲区组织成环形队列。生产者设备向队尾写入数据消费者CPU从队头取出数据指针循环移动。典型应用场景是音频/视频流处理——数据连续到达CPU 连续消费环形队列避免了缓冲区满时新数据覆盖旧数据的问题。Linux 内核的kfifo就是循环缓冲的一个经典实现。缓冲池Buffer Pool是操作系统统一管理的缓冲区集合包含三种工作缓冲区类型 空缓冲Empty Buffer尚未被使用的缓冲区。输入缓冲Input Buffer装有设备输入数据的缓冲区。输出缓冲Output Buffer装有待输出到设备的数据的缓冲区。缓冲池通过四种标准操作来管理数据流收容输入设备数据装入空缓冲、提取输入CPU 从输入缓冲取数据、收容输出CPU 将输出数据装入空缓冲、提取输出从输出缓冲取数据送设备。这四种操作构成了生产者-消费者模型的缓冲池版本。循环缓冲关注的是缓冲区之间的组织方式缓冲池关注的是缓冲区的分类管理和统一分配。两者不是互斥概念——大型操作系统的缓冲区管理往往同时使用循环缓冲的组织方式 缓冲池的分类管理。Linux 内核中的 Buffer Cache 和 Page Cache 正是在此基础上的工程实现它们将内存作为磁盘的缓冲池缓存最近读写的磁盘块使得大部分 I/O 请求无需真正访问物理磁盘。FAQQ1: SDT/DCT/COCT/CHCT 四张表的关系怎么快速记住有什么口诀四张表的层级关系可以这样记忆SDT 是系统级的总目录列了所有设备DCT 是每个设备的档案记录该设备的状态和它连在哪个控制器上COCT 是每个控制器的档案记录该控制器的状态和它连在哪个通道上CHCT 是每个通道的档案记录该通道的状态和它连了哪些控制器。口诀系统查设备设备查控制器控制器查通道——四层递进三层都要空闲。数据结构上的串联逻辑是 SDT→DCT→COCT→CHCT 的单向指针链没有反向指针 。Q2: 安全分配方式不会死锁的原理是什么是怎么破坏请求和保持条件的死锁的请求和保持条件是指进程已经持有了某些资源又去申请新资源申请失败被阻塞却不肯释放已持有的资源。安全分配方式在分配设备后立即阻塞该进程——进程被阻塞后无法再发出任何新的 I/O 请求包括申请其他资源的请求自然不可能同时持有旧资源 申请新资源。这等价于死锁预防策略中的一次性分配思路——区别在于安全分配是一次分配一个分配后立刻阻塞同样阻止了拿着旧的申请新的这个行为模式 。Q3: 单缓冲 Max(C,T)M 公式中的 M 为什么总是要加有没有 M0 的情况M 代表数据从缓冲区搬运到工作区的传送时间。在单缓冲模型中无论 CPU 快还是设备快每次处理一块数据都必须执行一次 M——因为 CPU 不能直接操作缓冲区中的数据必须先把数据搬到自己的工作区才能开始处理。这是单缓冲架构的固有约束 [引用王道 5.2_4]。理论上如果缓冲区直接映射为工作区即 CPU 直接在缓冲区中处理数据M 可以视为 0——考试中按 M 始终存在处理。Q4: 如果 T 远大于 CM双缓冲和单缓冲的性能比是多少选哪个更划算当 T CM设备速度极慢是绝对瓶颈时单缓冲用时 ≈ T M双缓冲用时 ≈ T性能比约为 (TM) / T 1 M/T。也就是说双缓冲省掉的就是那个 M 时间——如果 M 相对 T 很小M/T ≈ 0.01双缓冲带来的提升约 1%几乎无差别如果 M 与 T 相当M/T ≈ 0.5双缓冲能提升约 33%。但这里有一个工程上的微妙之处当 T 远大于 CM 时系统瓶颈在设备上换用双缓冲只是让 CPU 少等一个 M 而已——真正需要优化的是设备的传输速度换更快的设备、增大传输块大小而不是缓冲数量。缓冲方案解决的是速率匹配不是绝对速度。Q5: 循环缓冲和缓冲池是同一回事吗408 考试中需要掌握到什么程度不是同一回事。循环缓冲关注缓冲区的组织方式多个缓冲区围成环形队列指针循环移动缓冲池关注缓冲区的分类管理空缓冲 / 输入缓冲 / 输出缓冲三种类型 四种标准操作。408 考试对这两个概念的要求是理解基本思想和适用场景能做概念辨析不会像单/双缓冲那样出计算题。回顾整个第五章 I/O 管理系列从最底层的 I/O 设备分类与控制器硬件第一篇到四种 I/O 控制方式第二篇到五层 I/O 软件层次结构第三篇到 SPOOLing 虚拟化技术第四篇再到本文的设备分配四层数据结构与缓冲区性能建模第五篇——这条纵向主线覆盖了数据如何从外部设备进入内存、又如何从内存回到外部设备的完整旅程。对于考研备考建议将设备分配的 SDT→DCT→COCT→CHCT 四表关系画成一张图、将单缓冲和双缓冲的计算公式整理成一张对比表。延伸阅读本系列第一篇[I/O 设备分类与 I/O 控制器]内存管理相关[基本分页储存管理——从页表到地址变换机构TLB 作为硬件缓冲区进程管理相关[死锁深度解析——从四个必要条件到银行家算法]安全分配与死锁预防策略的对应关系Linux Kernel Documentation: Buffer and Cache —— 内核中缓冲区和页缓存的实际实现