Skip to content

第一百四十六章:块设备层

合道期

涉及内核源码:

林小源沿着设备山脉的底层通道前行,脚下的石板路越来越宽阔,空气中弥漫着一股沉闷的机械震动声。通道的尽头是一片巨大的调度广场,五座形态各异的石台矗立在广场中央,每座石台上都刻着不同的符文。

一个身披灰色长袍的老者盘坐在最近的石台旁,手中不断翻转着一堆竹简。那些竹简上写满了 I/O 请求,老者时不时将两片相邻的竹简合为一片,动作利落而精准。

"你是 I/O 调度器?"林小源走上前问。

老者抬起头,浑浊的眼睛里闪过一丝精光:"我是 noop——最简单的调度器。我什么都不做,来什么发什么。"他随手把两片竹简直接丢进了身后的传送阵。

林小源皱眉:"那岂不是毫无优化?"

noop 老者笑了:"你脚下踩的是什么路?"

"石板路。"

"如果这条路是 SSD 的闪存通道——没有磁头,没有寻道,我排不排序有什么区别?"noop 老者拍了拍石台,"排序是给机械盘用的,对闪存来说,直接发送才是最快的。"

林小源正要追问,广场另一端传来一个低沉的声音:"别听他瞎说。"

一个戴着铁面具的壮汉从阴影中走出,每一步都带着沉重的金属回响。他的面具上刻着一个沙漏的图案,沙漏中的沙子正在缓缓流逝。

"我是 deadline。"壮汉的声音像是从地底传来,"机械盘的磁头来回寻道,如果不管顺序,一个写请求可能被无限推迟——这就是饥饿。我给每个请求一个截止时间,到期必须处理。"

林小源感到脚下的石板微微震动,像是有什么东西在地底深处运转。他低头看去,发现石板下面是一条条交错的轨道,每条轨道上都有一个闪烁着红光的计时器。

"如果请求在截止时间前完成了呢?"林小源问。

deadline 壮汉点头:"那最好。但如果快到期了还没处理,我会把它提到队列最前面——这就是防止饥饿的关键。"

林小源琢磨了一下——说到底,调度就是一种优化手段,让磁盘少做无用功。


c
/*
 * I/O 调度器:
 *
 * 1. noop
 *    不调度,直接发送
 *    适合 SSD
 *
 * 2. deadline
 *    保证请求在截止时间前完成
 *    防止饥饿
 *
 * 3. cfq (Completely Fair Queuing)
 *    完全公平队列
 *    为每个进程分配时间片
 *    适合 HDD
 *
 * 4. mq-deadline
 *    多队列版本的 deadline
 *    适合现代 SSD
 *
 * 5. bfq (Budget Fair Queuing)
 *    预算公平队列
 *    适合桌面系统
 *
 * 调度器的作用:
 *   合并: 合并相邻的请求
 *   排序: 按扇区排序减少寻道
 *   优先级: 保证重要请求优先
 */

printf("=== I/O 调度器 — 优化磁盘访问 ===\n\n");

printf("I/O 调度器:\n\n");

printf("1. noop:\n");
printf("   不调度,直接发送\n");
printf("   适合 SSD\n\n");

printf("2. deadline:\n");
printf("   保证截止时间\n");
printf("   防止饥饿\n\n");

printf("3. cfq:\n");
printf("   完全公平队列\n");
printf("   适合 HDD\n\n");

printf("4. mq-deadline:\n");
printf("   多队列 deadline\n");
printf("   适合现代 SSD\n\n");

printf("5. bfq:\n");
printf("   预算公平队列\n");
printf("   适合桌面系统\n\n");

printf("--- 调度器的作用 ---\n");
printf("合并:\n");
printf("  合并相邻的请求\n");
printf("  减少 I/O 次数\n\n");
printf("排序:\n");
printf("  按扇区排序\n");
printf("  减少磁盘寻道\n\n");
printf("优先级:\n");
printf("  保证重要请求优先\n\n");

printf("--- 查看和设置调度器 ---\n");
printf("查看:\n");
printf("  cat /sys/block/sda/queue/scheduler\n\n");
printf("设置:\n");
printf("  echo mq-deadline > /sys/block/sda/queue/scheduler\n\n");

printf("--- SSD vs HDD ---\n");
printf("HDD:\n");
printf("  机械磁盘\n");
printf("  寻道时间长\n");
printf("  需要排序优化\n\n");
printf("SSD:\n");
printf("  闪存\n");
printf("  无寻道时间\n");
printf("  不需要排序\n");

林小源继续向广场深处走去,第三座石台前坐着一个面容和善的中年人。他的面前摆着一个巨大的天平,天平两端各放着一堆竹简,每一堆都标记着不同的进程名。

"我是 cfq。"中年人温和地说,"完全公平队列。每个进程都分到一块时间片,谁也不能独占磁盘。"

林小源观察了一会儿,发现天平始终保持着微妙的平衡。每当一个进程的竹简用完了时间片,中年人就把它移到天平的另一端,换下一个进程上来。

"这不是很慢吗?"林小源问,"频繁切换不会增加开销?"

cfq 中年人叹了口气:"HDD 的磁头移动才是最大的开销。如果不公平分配,一个大文件拷贝可能把所有其他进程的请求饿死。公平,有时候比速度更重要。"

他指了指广场最远处的一座石台——那上面刻着 "bfq" 三个字,旁边站着一个拿着小算盘的年轻人。

"bfq 是我的徒弟,"cfq 说,"他在公平的基础上加了预算机制。每个进程不只分到时间片,还分到一个 I/O 预算。预算用完了才切换,减少了切换次数。桌面系统用他最合适。"

林小源望向 bfq,年轻人正专注地拨弄着算盘珠子,每拨一颗珠子就有一批竹简被整齐地排列好送出去。

他忽然明白,调度器不是万能的,得跟硬件配合才行。

广场的最深处是一座崭新的建筑,墙壁上嵌满了并排的传送阵。每个传送阵前都有一个独立的调度台,台后各坐着一个穿着统一制服的调度员。

"这是多队列区域。"一个穿着银色铠甲的年轻人从建筑中走出,他的胸甲上刻着 "mq-deadline" 的铭文,"我是 mq-deadline——多队列版本的 deadline。"

林小源数了数,面前至少有十六个传送阵,每个都在独立运转。

"NVMe SSD 有多个硬件队列,"mq-deadline 解释道,"如果还用传统的单队列调度器,所有请求都要排队等同一把锁——锁竞争会把多队列的优势全部吃掉。"

他走到一个传送阵前,轻松地将一批竹简送了进去:"每个队列我独立调度,互不干扰。锁?不需要。这就是并行的力量。"

林小源感到脚下的震动变得均匀而轻快——不像之前单队列区域那种忽快忽慢的节奏。每个传送阵都在以自己的频率稳定运转,整个建筑像是一台精密的瑞士钟表。

"那 noop 呢?他不是更适合 SSD 吗?"林小源问。

mq-deadline 摇了摇头:"noop 太简单了——它连截止时间都不管。如果你的 SSD 上有延迟敏感的请求,noop 可能让你等到天荒地老。我在保持并行的同时,还能保证每个请求不会饿死。"

这下他懂了——多队列就是并行的基础,每个队列各干各的,谁也不用等谁。


道藏笔记

内核启示

I/O 调度器的核心思路很简单:既然磁盘寻道慢,那就别让磁头来回跳。noop 什么都不管,直接发给硬件,SSD 用它最合适;deadline 给每个请求设个截止时间,过期必须处理,防止饿死;cfq 按进程分时间片,追求公平;mq-deadline 是 deadline 的多队列版本,适合 NVMe 这种多通道设备;bfq 在公平的基础上加了预算机制,桌面系统用着舒服。调度器干的事归纳起来就三样:合并相邻请求、按扇区排序、保证重要请求优先。选哪个调度器,取决于你的硬件和负载——没有万能方案,只有合适的权衡。


破关试炼

I/O 调度器之试

块设备层中,按截止时间避免请求长期饥饿的 I/O 调度策略叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道