第二讲:Linux内核存储子系统全景

存储子系统,说白了就是内核里负责数据搬运的那一套体系。从应用程序的read/write,到数据真正落到硬盘上,中间要经过好几层。我刚开始接触这块时,也觉得层数太多,绕得头晕。但后来发现,每一层都有它存在的道理。

2.1 块设备层:存储的通用接口

块设备层是所有存储设备的抽象层。它把硬盘、SSD、U盘这些东西,统一成「块设备」的概念。每个块设备都有一个主设备号和次设备号,比如/dev/sda、/dev/nvme0n1。

我个人习惯把块设备层想象成一个邮局。应用程序把数据包(bio结构体)交给邮局,邮局负责分拣、排队、最终投递。至于邮递员是骑自行车还是开卡车,那是下层的事。

核心数据结构:

  • block_device:代表一个块设备实例
  • gendisk:通用磁盘描述,包含分区信息
  • request_queue:请求队列,I/O调度器挂在这里
  • bio:块I/O操作的基本单元

我在项目中遇到过一个问题:某块NVMe SSD在极端负载下,块设备层的bio合并策略失效,导致大量小IO直接穿透到驱动层。嗯,这里要注意,bio合并是块设备层的重要优化手段,合并失败会直接影响性能。

2.2 I/O调度层:电梯算法与排队策略

I/O调度层,说白了就是决定「先处理哪个请求」的算法层。传统机械硬盘时代,调度算法至关重要,因为磁头寻道是最大的性能瓶颈。但到了NVMe时代,情况变了。

你想想看,NVMe SSD的延迟是微秒级的,随机IO和顺序IO几乎没有性能差异。那传统的CFQ(完全公平队列)调度器,反而成了累赘。我个人建议NVMe设备直接使用none调度器,或者mq-deadline。

调度器 适用场景 特点
none NVMe、高性能SSD 不做任何调度,直接下发,延迟最低
mq-deadline 混合存储(HDD+SSD) 保证请求不饿死,有截止时间机制
bfq 桌面系统、多媒体 强调公平性,适合交互式场景
kyber 延迟敏感场景 基于延迟反馈的动态调度

避坑指南:我曾经在部署NVMe全闪存阵列时,默认使用了CFQ调度器,结果4K随机读写性能掉了30%。后来换成none调度器,性能直接拉满。所以,NVMe设备请务必检查I/O调度器配置。

2.3 SCSI/ATA子系统:传统存储的基石

SCSI和ATA是两种经典的存储协议。SCSI用于服务器和企业级存储,ATA(包括PATA和SATA)用于消费级市场。Linux内核中,SCSI子系统是一个非常庞大的框架,它不仅管理SCSI硬盘,还管理光驱、磁带机、甚至一些USB存储设备。

SCSI子系统分为三层:

  1. 上层(Upper Layer):提供统一的磁盘、磁带等设备接口
  2. 中间层(Mid Layer):处理命令分发、错误恢复、队列管理
  3. 底层(Lower Layer):与具体硬件交互的HBA驱动

ATA子系统相对简单一些,主要通过libata库实现。我记得早期Linux内核中,ATA和SCSI是两套独立的代码,后来通过libata把ATA设备模拟成SCSI设备,统一管理。这个设计思路很巧妙——你想想看,上层应用根本不需要知道底层是SATA还是SAS。

注意:NVMe驱动并不走SCSI子系统。NVMe有自己的原生驱动路径,直接通过PCIe与NVMe控制器通信。这一点和SATA SSD完全不同,SATA SSD最终还是要经过SCSI中间层。

2.4 NVMe驱动的位置:原生PCIe路径

好了,终于说到NVMe了。NVMe驱动在内核存储子系统中的位置,可以用一张图来概括:

Linux内核存储子系统架构 应用层(VFS、系统调用) 块设备层(Block Layer) I/O调度层(I/O Scheduler) SCSI/ATA子系统 (SAS/SATA硬盘、光驱等) NVMe Native Driver (直接PCIe路径,无中间层) 传统存储硬件(SATA/SAS) NVMe SSD(PCIe接口) NVMe驱动 独立路径

从这张图可以看得很清楚:NVMe驱动和SCSI/ATA子系统是并行的两条路径。NVMe驱动直接挂在I/O调度层下面,不走SCSI中间层。这意味着:

  • 路径更短:从应用层到NVMe硬件,只经过块设备层和I/O调度层
  • 延迟更低:省去了SCSI命令转换、错误恢复等开销
  • 队列更深:NVMe原生支持多队列,每个队列可以深度达64K

核心要点:NVMe驱动在内核中的位置,决定了它的性能优势。它绕过了SCSI这个「中间商」,直接和硬件对话。这也是为什么NVMe SSD的延迟能做到10微秒以下,而SATA SSD还在100微秒级别徘徊。

2.5 实际开发中的注意事项

说了这么多理论,来点实际的。如果你要开发NVMe驱动,或者适配NVMe设备到Linux内核,有几点我建议你记住:

  1. 不要修改块设备层的通用代码——除非你非常清楚自己在做什么。块设备层经过了几十年的打磨,稳定性极高。
  2. I/O调度器选none——NVMe设备不需要调度,任何调度都是额外的开销。
  3. 注意中断亲和性——NVMe的多队列特性,需要把中断绑定到不同的CPU核心上,否则性能上不去。
  4. 调试时多用blktrace——这个工具可以追踪IO从块设备层到驱动层的完整路径,定位性能瓶颈非常有用。

个人经验:我曾经调试过一个NVMe驱动,发现IO延迟忽高忽低。用blktrace一查,发现是块设备层的bio合并策略和NVMe的队列深度不匹配。调整了队列深度参数后,延迟曲线变得非常平滑。嗯,这种问题光看代码是看不出来的,必须靠工具。

存储子系统是一个庞大的体系,但NVMe在其中扮演的角色非常清晰——它就是那个「不走寻常路」的加速器。理解了它的位置,你就理解了为什么NVMe能成为下一代存储的标准。


公众号:蓝海资料掘金营,微信deep3321