• 中国精品科技期刊
  • CCF推荐A类中文期刊
  • 计算领域高质量科技期刊T1类
高级检索

前  言

舒继武, 王意洁

舒继武, 王意洁. 前  言[J]. 计算机研究与发展, 2025, 62(3): 543-544. DOI: 10.7544/issn1000-1239.20253
引用本文: 舒继武, 王意洁. 前  言[J]. 计算机研究与发展, 2025, 62(3): 543-544. DOI: 10.7544/issn1000-1239.20253
舒继武, 王意洁. 前  言[J]. 计算机研究与发展, 2025, 62(3): 543-544. CSTR: 32373.14.issn1000-1239.20253
引用本文: 舒继武, 王意洁. 前  言[J]. 计算机研究与发展, 2025, 62(3): 543-544. CSTR: 32373.14.issn1000-1239.20253

前  言

  • NAND SSD[1]、持久性内存(persistent memory, PM)[2]等新兴存储介质的性能已经远超针对慢速I/O设备所设计的传统I/O栈能处理的范围. 新型存储设备的低延迟(数百ns至100 μs[3])特性要求重新设计和优化I/O路径,以充分发挥其性能优势. 已有相关工作[4-6]针对NVMe SSD软件栈进行了优化. 同时,持续发展的SAS协议[7],如24G SAS(当前的主流版本是12G SAS)以及预计2026年前后完成制定的24G+ SAS,将为SAS子系统带来更高的传输速率以及更多的新特性,为优化SAS存储协议栈提供了新的机遇与挑战.

    然而设计并实现这样的存储协议栈存在着非常大的挑战. 具体而言,首先,由于大多数性能测试工具位于用户空间,在测试过程中,其不可避免地会引入存储I/O栈中的诸多开销. 例如用户空间与内核空间的切换开销、中断上下文的切换开销等,从而导致针对低延迟I/O设备的性能测试不够准确(尤其是延迟),进而无法准确评估传统I/O栈对设备带来的性能损耗. 此外,为了避免不同空间的切换损耗以及内核空间的时间开销,旁路内核实现完全用户空间的I/O栈是一个重要的方向,但是用户空间管理SAS存储设备需要考虑各种问题:1) 现有的机制可以在用户空间直接接管PCIe设备,但是SAS设备处于SAS控制器(PCIe设备)的下一级总线,而目前用户空间缺乏对这类设备的控制与访存管理;2) 内核空间对外设的访问有着严格的权限来保证安全,用户空间必须在数据安全可靠的前提下优化存储设备的读写性能;3) 当前外设对CPU的中断必须由操作系统处理,所以为了完全屏蔽内核的影响,必须能够在用户空间感知外设的中断.

    为了解决上述问题,本文首先提出了硬件性能测试工具HwPerfIO,能够消除大部分软件开销的影响以测得更加准确的硬件性能. HwPerfIO运行在内核空间,能够根据用户输入的模型直接在内核空间构造请求,并且采用CPU轮询的形式去除了用户空间与内核空间的切换开销及中断上下文切换所引入的影响. HwPerfIO直接调用外设驱动的I/O接口,能够绕过冗长的内核I/O栈,最大程度地发掘硬件性能并最小限度地引入软件开销. 其次,本文分析了HwPerfIO的测试结果,并与典型性能测试工具fio[8]的结果进行对比,分析内核软件栈对性能测试的影响. 最后,本文基于SPDK设计并实现了用户空间的SCSI存储软件栈,通过在用户空间实现高效的数据通路降低存储设备的响应时间. 在该实现中提出了用户空间SCSI设备管理框架,能够高效地管理设备与驱动,并提出了SCSI底层驱动接口规范以兼容众多的SCSI存储设备;利用输入输出内存管理单元(input output memory management unit, IOMMU)[9]实现了用户空间安全的DMA访问,在安全隔离的情况下保证了数据的高效访问;采用轮询的方式直接在用户空间感知I/O的完成.

    本文的主要贡献有3个方面:

    1)提出了硬件性能测试工具HwPerfIO,能够更准确地测试低延迟I/O设备的性能,并结合测试结果分析了内核I/O栈对性能测试带来的影响.

    2)设计并实现了基于SPDK的用户空间SCSI存储I/O栈,减少了应用程序与存储设备I/O路径上复杂的软件开销,充分发掘了低延迟存储设备的性能优势.

    3)通过实验分析,基于用户空间的SCSI存储I/O栈相比内核能够提供更快的访问路径,在队列深度为1时,读写延迟分别降低23.8%和30.1%.

    在本节中,主要介绍新型存储介质的特征以及存储协议SAS的发展现状,并分析传统I/O栈在低延迟设备上面临的挑战,以及新型存储设备与协议在I/O虚拟化中的应用.

    在现代计算机存储系统中,由于不同存储介质的成本与读写性能的差异,存储器体系结构遵循金字塔[3]原则. 如图1所示,从顶部的CPU寄存器和高速缓存到底部的传统机械硬盘(HDD)以及远端存储等,存储设备容量逐级递增,访问效率逐级递减,成本也相应降低. 这种层次结构能够在兼顾成本与容量的同时,保证存储系统的可用性. 可以看出,持久性内存及NAND SSD等新兴存储介质的出现填补了动态随机存储器(dynamic random access memory, DRAM)与HDD之间巨大的性能鸿沟.

    图  1  存储金字塔[3]
    Figure  1.  Storage pyramid [3]

    得益于NAND Flash技术的成熟,高性能SSD已经得到了广泛的应用. SSD起初通过引入闪存转换层(flash translation layer, FTL)[10]以兼容串行ATA(serial advanced technology attachment, SATA)[11]接口及相应的高级主机控制器接口(advanced host control interface, AHCI)[12]协议迅速进入了消费领域. 但SATA协议的带宽上限只有600 MBps[12],无法发挥出SSD的内在性能. 为了解决SSD性能受协议限制的问题,NVMe协议[13]标准被提出,这是一种高性能低延迟的存储协议,通过PCIe总线直接与CPU通信,消除了SATA协议的性能瓶颈,提供了更高的带宽与更低的延迟.

    SAS协议是另一种支持SSD的重要存储协议,由SCSI协议发展而来,适用于各种存储介质,包括磁带、HDD以及SSD等. 近20年来,SAS一直是企业级存储的首选,根据国际数据中心(IDC)的估计,超过70%的企业级存储都在使用SAS[14],SAS作为底层存储协议被广泛应用于FC-SAN[15],IP-SAN[16]等存储系统中. 从性能角度来看,NVMe SSD在带宽、延迟以及每秒进行的读写操作数(input output operations per second, IOPS)等方面都具有一定的领先优势. 但数据中心的构建需要综合考虑成本、性能、可扩展性等诸多因素才能够找到最佳的解决方案.

    而SAS协议的制定充分考虑了数据中心的需求,在提供高可靠性与高可扩展性的同时,实现了成本与性能的平衡. 在使用扩展器链接的情况下,SAS协议可以扩展至64000个设备[17],能够提供巨大容量的存储池,基于SAS协议的SSD容量可达数十TB,远高于同时期的NVMe SSD. 且NVMe SSD通过PCIe直接连接到主机CPU,很难在服务器机箱外进行扩展,在该方面SAS具有明显的优势.

    此外,NVMe性能优于SAS的原因之一是相对于SCSI命令集,NVMe命令集删除了很多关于管理与纠错的功能,而SAS拥有一套非常强大的命令集合,功能包括但不限于高级错误检测与纠正、热拔插处理、错误处理及数据保护等. 对于银行、证券等依赖存储实现超高可靠性的场景而言,SAS的可靠性优势至关重要. 最后,数据中心在技术选型时需要考虑预算与成本,得益于价格优势,SAS能够有效改进数据中心的总体拥有成本(total cost of ownership, TCO). 因此,SAS 在未来几年内将继续成为数据中心的基础组件. 此外,SAS标准仍在不断修订与演进中. 目前SAS的主流版本是2013年发布的SAS-3,即12G SAS. 最新标准是2017年开始制定的SAS-4,即24G SAS,其实际传输速率为22.5 Gbps. 24G SAS之后,SAS预计将推出24G+ SAS,预计将于2026年后制定完毕,其详细发展趋势路线图详见图2[7].

    图  2  SAS技术路线[7]
    Figure  2.  SAS technical route [7]

    传统内核I/O栈是针对慢速I/O设备所设计的,DRAM与HDD之间存在巨大的性能差异. 参考图1[3]可知,相较于慢速I/O设备10 ms级别的延迟,I/O流程中的软件开销几乎可以忽略不计. 因此针对慢速I/O设备所设计的内核I/O栈工作良好. 但是随着新兴存储介质的高速发展,这些设备具有微秒甚至纳秒级的低延迟,其性能已经远超传统I/O栈所能处理的范围.

    以Linux内核为例,在传统I/O栈中,用户数据的访存需要经过用户空间的标准库,通过I/O系统调用从用户空间进入内核空间. 一个I/O请求在内核空间中将依次经过虚拟文件系统(virtual file system, VFS)、硬盘文件系统,以及通用块层和I/O调度层等. 然后通过控制器驱动将I/O请求提交到对应控制器,最后由控制器完成真正的数据传输工作,将主机侧的数据传输到存储设备,或将存储设备中的数据读取到主机. 由于DRMA与HDD之间的性能差异巨大,传统I/O栈中的文件系统大都利用DRAM构造缓冲区高速缓存来提高读写访问的性能,并通过异步冲刷缓冲区高速缓存到磁盘. 这种方式能够在保证正确性的前提下对应用程序隐藏HDD等慢速存储设备的高I/O延迟,并提高CPU利用率. 否则大量的CPU周期将用于等待慢速I/O设备的响应,造成CPU资源的浪费. 通用块层与I/O调度层则通过I/O合并及I/O调度等机制,充分考虑传统磁盘顺序读写性能高、随机读写性能低的特性,最大限度地发掘机械硬盘的性能. 一个I/O请求所经历的每个阶段都会引入不同的时间开销,图3显示了这些阶段的时间开销分布. 用户空间发起的I/O请求首先经由系统调用进入内核空间,用户空间与内核空间的切换开销在0.5~2 μs[18],某些情况下会更长. 读写请求在整个I/O栈中会经历5~15 μs[19]的软件延迟,每一层的处理都会增加额外的开销. 例如在访问DRAM时,内存管理器必须检查是否发生缺页,若发生缺页,需要触发缺页中断执行调页. 文件系统则必须检查当前进程是否拥有目标文件的访问权限,同时维护快照以及日志等机制来确保崩溃一致性. 对于扩展文件系统,这些机制会引入40%~200%的开销,具体取决于日志的使用情况. 对于B树文件系统(B tree file system, Btrfs),开销可能增加高达5倍[20]. 如果发起直接I/O请求,Linux内核必须检查I/O请求是否按照目标设备的块粒度对齐,这些检查全部由CPU完成. 当I/O请求被提交到控制器后,后续的延迟由存储设备决定,HDD的延迟约为10 ms,SSD的延迟范围在10~100 μs. 当存储设备完成I/O之后,向主机发起中断,此过程涉及中断上下文的切换与中断处理程序的调度,通常会引入若干微秒的时间开销[21]. 最后中断处理程序沿着调用路径返回到用户程序,最终应用程序感知到一次I/O的完成. 因此,新型存储介质的低延迟特性要求重新设计或优化I/O路径,减少传统I/O栈的性能开销,以提升系统的整体效率,满足高性能应用的需求.

    图  3  Linux内核I/O栈延迟分布
    Figure  3.  Linux kernel I/O stack latency distribution

    I/O虚拟化是虚拟化的重要组成部分,主要为运行在虚拟机中的应用程序提供I/O服务. I/O操作的最后一步通常是写外设对应的门铃寄存器,该操作是一条特权指令,因此需要相应的机制来实现I/O虚拟化. 在SPDK出现之前主要包括4种类型:第1种是纯软件设备模拟(全虚拟化). 虚拟机感知到的I/O设备是由宿主机通过软件模拟呈现的,客户机发起的I/O操作会被宿主机捕获,宿主机通过模拟软件将I/O请求转发到真实的物理设备中. 这是最简单的I/O虚拟化技术,虽然兼容性好、使用方便,但是由于I/O路径过于冗长,所以性能相对较差. 第2种是I/O虚拟化技术,被称为半虚拟化. 这种方式需要在客户机中部署virtio[22]前端驱动,在QEMU[23]中实现后端驱动,I/O请求写外设门铃寄存器的操作均在virtio驱动中实现,能够有效减少客户机与宿主机指令切换的次数. 相对于纯软件的模拟,有效降低了I/O延迟,性能相对较好. 第3种是虚拟化技术,被称为设备直通. 它利用CPU的虚拟化能力,所以又被称为硬件辅助虚拟化,尽管能够获得与真机接近的性能,但是设备只能被唯一的客户机独占. 第4种是基于第3种类型进一步提出的SR-IOV[24]技术,能够完美解决设备不能在虚拟机之间共享的问题,但是需要特定外设的支持.

    在上述4种类型的I/O虚拟化中,virtio已经完全并入Linux内核主线,由于其他虚拟化方案相对苛刻的硬件要求以及virtio在性能上的一定优势,virtio已经作为主流的I/O虚拟化方案被广泛部署. virtio后端用于处理客户机的I/O请求,位于QEMU进程之外的实现方案被称为vhost[25],I/O处理模块被置于Linux内核的方案被称为vhost-kernel. 随着SPDK等用户空间方案的提出,虚拟化I/O的处理模式发生了改变,虚拟机本身位于用户空间,SPDK的I/O处理模块也位于用户空间,与vhost-kernel对应的vhost-user明显是更优的解决方案. vhost-user利用SPDK在用户空间提供的I/O处理能力,使虚拟机能够更快地访问后端存储设备.

    目前市场上已有几款支持24G SAS的SSD,包括铠侠PM6与PM7系列[26]以及三星PM1653[27]等. 支持24G SAS的存储控制器主要包括博通96系列HBA/RAID卡[28],以及Microchip旗下的Adaptec Smart RAID 3200[29]等. 以PM7系列在双端口模式下的官方测试结果为例,128 KB顺序读性能最高可达4200 MBps,128 KB顺序写性能最高为4100 MBps. 而4 KB随机读性能达到了惊人的720 KIOPS,4 KB随机写性能最高则为175 KIOPS. 通过使用fio对博通9670 RAID控制器连接铠侠KPM71RUG3T84 24G SAS SSD的初步实验测试可知,其单次I/O写延迟约为18.43 μs,读延迟约为42.3 μs. 此外,由于RAID控制器普遍采用DRAM缓存,Arrakis[20]提出其所测试RAID控制器的平均写延迟约为25 μs,在最极端的情况下,软件栈延迟可能高达17 μs甚至更高. 在一次完整的I/O过程中,软件栈开销的占比将超过50%. 因此,针对慢速I/O设备所设计的传统内核软件栈会限制SAS SSD的性能,无法满足日益增长的高速I/O设备的性能需求.

    为了解决该问题,学界与工业界相继提出了众多I/O栈优化方案. 以TH-iSSD[30],ParaBit[31]等为代表的工作尝试在存储设备中执行计算逻辑,将I/O路径中的部分工作卸载到更高效的硬件设备中,极大地减轻了I/O栈的负担;文献[3233]则通过优化闪存文件系统以提高I/O路径的整体效率,NVStore[34]通过自适应元数据管理[35]降低了云存储环境中NVMe SSD 的未对齐写,降低了无效写入,提高了系统带宽;另一部分研究工作[5-6,36-40]认为冗长的内核I/O路径所引入的时间开销是限制低延迟I/O设备性能的主要因素. 这些研究工作旁路部分或全部内核,在用户空间向应用程序提供直接访问存储设备的能力或在整个I/O路径上减少内核的参与. 下文将着重介绍旁路内核的相关工作.

    用户空间文件系统(filesystem in user space, FUSE)[41]允许用户在无需编译内核的情况下,创建自定义的文件系统,最小化I/O路径中来自内核本身的干扰. FUSE主要包括2部分,内核的FUSE模块以及用户空间的libfuse库. 在Linux内核中,所有文件请求都会通过系统调用进入虚拟文件系统VFS,VFS作为统一的接口承接了上层的全部文件请求. 随后VFS根据打开文件句柄调用具体文件系统实现的对应文件操作. 假如该文件位于Btrfs,则VFS将继续调用Btrfs实现的文件操作. 如果该文件是用户空间文件系统FUSE下的文件对象,那么FUSE所提供的内核模块就能够将该请求转发到位于用户空间的文件系统,处于用户空间的libfuse库负责接收转发自内核空间的I/O请求并实现文件系统的公共部分,而具体文件系统的实现全部位于用户空间. 尽管FUSE探索出了一条全新的I/O路径,但是在用户空间实现文件系统必然会引入额外的内核空间/用户空间切换开销,并不适用于元数据密集型的应用场景,而是广泛应用于加密场景或作为分布式文件系统的客户端使用等. 为了尽量降低FUSE引入的额外开销,研究者们提出了若干针对FUSE的优化方案.

    如ExtFUSE[36]利用eBPF优化FUSE性能. AVFS[37]使用LD_PRELOAD重定向libc的POSIX API接口,能够在不进行用户内核空间切换的情况下直接调用用户空间文件系统的接口. Direct-FUSE[38]的所有部分均位于用户空间中,应用程序可以直接使用预定义的统一文件系统接口来与不同的用户空间文件系统进行交互,结果显示Direct-FUSE的平均性能要比某些原生FUSE文件系统高出11.9%. XFUSE[39]主要针对高性能存储设备上的文件系统,允许多个守护进程并行处理不同的I/O请求,充分利用多CPU的性能优势,根据I/O请求自适应守护进程的等待周期,其端到端的延迟可以控制在4 μs以内.

    用户空间驱动则在应用程序与存储设备之间直接构造了一条高速数据通路,完全旁路了内核. 其中最典型的工作即Intel公司提出的SPDK[4],SPDK是一套高性能存储开发工具包,用于加速NVMe SSD的应用软件加速库. SPDK将设备驱动全部实现在用户空间,避免了用户空间与内核空间切换的时间开销以及冗长的内核I/O路径. 此外,SPDK使用轮询代替中断,极大地降低了I/O延迟与性能抖动. 最后,SPDK在I/O路径上避免采用任何锁机制进行同步,降低延迟的同时提高了吞吐. NVMeDirect[5]是一个用户空间I/O框架,旨在最大化NVMe SSD的性能,同时适应多样化的应用需求. 与 SPDK不同,NVMeDirect可以与传统内核I/O栈共存,基于内核的应用程序与基于NVMeDirect的应用程序可以同时使用NVMe SSD的不同分区. 实验结果表明,较于内核,NVMeDirect框架会带来约30%的性能提升. uNVMe[6]是三星电子公司开发的用户空间NVMe驱动程序,其工作模式包括客户端-服务器模型以及客户端-库模型. 前者使用独立的进程管理设备,支持多客户端的应用同时访问设备;后者将应用与驱动合并以提高性能但限制每个应用独占一个NVMe设备. 实验结果显示,使用uNVMe设备驱动程序,随机读性能相较于内核能够提高2~3倍,随机写性能的提升则相对有限.

    为了减少I/O路径上的开销并限制内核的参与,I/O虚拟化也是另一个重要的技术手段. Simon等人[20]设计并实现了一个全新的操作系统Arrakis,其特点在于从数据路径中剔除了I/O操作中近乎所有需要内核参与的步骤,并为应用程序提供与传统I/O栈相同的安全模型. Arrakis将内核划分为数据面与控制面,应用程序可以在用户空间直接访问底层硬件抽象出的虚拟I/O设备实例,数据平面的操作与数据访问相关,而控制面只负责虚拟I/O设备实例的创建与管理等. 由于数据面与控制面的分离,进而将内核从绝大多数的数据流中分离. 此外,Caulfield等人[40]针对NVMe SSD虚拟化所提出的Moneta-D是一种全新的存储软硬件架构. 该架构能够消除I/O路径中软件开销的两大来源,即进入内核与文件系统时的权限检查. 主要技术创新在于为每个进程提供私有的虚拟化接口并将文件系统保护检查移至硬件中. 未经修改的应用程序在使用Moneta后,在延迟、带宽和数据库吞吐量方面均有显著的提升. 其中,带宽提高2~8倍,延迟降低约60%.

    为了更加准确地测试硬件I/O性能,本文设计了完全位于内核空间的HwPerfIO,在I/O执行统计过程中完全无需考虑用户空间与内核空间的切换开销. 并且HwPerfIO直接与控制器驱动交互,最大限度地减少了整个I/O路径上的其他软件开销,所以HwPerfIO能够测得最贴近硬件真实情况的性能.

    主流的硬件性能测试工具包括fio[8],vdbench[42]等,这些工具均位于用户空间,参考图4可知,在用户空间构造I/O请求后通过系统调用接口将I/O发送到对应的设备,这种测试方法无法避免地引入了内核软件栈的时间开销. 而为了评估系统软件栈的开销,许多研究通常通过模拟将所有读写请求都落入内存来进行测量. 例如,Arrakis[20]通过在一个密集的10000次迭代循环中依次执行小写和fsync操作以确保每一次的写操作都能够落入存储设备而非缓冲区高速缓存中. 尽管这种方法在一定程度上能够统计出I/O过程的平均CPU开销,但也具有一定的局限性. 通过DRAM模拟磁盘的方式需要向文件系统提供块I/O的提交方法,DRAM模拟盘所提供的实现就是简单的内存拷贝操作. 而通用块层提供的块I/O提交方法则涵盖了I/O排队、I/O调度以及I/O合并等机制. 因此该方法也就无法准确刻画通用块层中相应的软件开销,所以主要用于分析文件系统带来的CPU开销. 为了实现硬件设备性能的准确测量,本文设计并实现了一种硬件性能测试方法,如图4所示. 首先,为了解决用户空间内核空间切换以及内核本身引入的时间开销问题,本文提出了基于内核空间的测试工具,将HwPerfIO实现在内核空间(3.1节). 其次,为了减少中断上下文的切换开销,HwPerfIO采用轮询机制达到了有效降低I/O路径上延迟开销的目的,能够准确地测试出硬件本身的初始性能(3.2节).

    图  4  HwPerfIO与fio对比
    Figure  4.  HwPerfIO vs fio

    通常来说,设备驱动位于内核I/O栈的最底层,直接与硬件设备通信,是连接上层I/O请求与底层硬件的关键组件,负责维护并管理请求描述符并发起直接内存访问(direct memory access, DMA)[43]. 请求描述符位于连续的一致性缓存DMA内存[43]中,且请求描述符的个数等于外设的队列深度(外设能够同时处理的命令数). 请求描述符包含完成一次I/O的必要信息:1)DMA控制信息以及对应传输协议的命令信息. 如果外设是SCSI控制器,那么请求描述符包含命令描述符块(command descriptor block,CDB)[44],即SCSI命令;如果外设是NVMe SSD,那么请求描述符包含NVMe命令. 2)数据负载信息. 请求描述符需要描述主机内存的负载信息,即分散聚合(scatter-gather, SG)[43]DMA链表. SG链表由若干SG项构成,SG项描述了不连续物理内存的地址与长度.

    为了防止内核I/O栈对性能测试的干扰,HwPerfIO以内核模块的形式直接实现在内核空间,且不经过任何I/O栈直接与外设驱动通信,其核心工作就是构造请求描述符. HwPerfIO允许用户指定不同的I/O模式,典型I/O模式包括用于测试吞吐率的4 KB小I/O随机读写以及用于测试带宽的256 KB大I/O顺序读写. HwPerfIO在内核空间直接根据用户输入的I/O模型构造外设需要的全部请求描述符,该构造过程仅发生在HwPerfIO的初始化过程中,在后续的性能测试中能够节省请求描述符的准备时间. 当所有的准备工作就绪之后,CPU只需要通过内存映射I/O(memory map I/O, MMIO)[24]将请求描述符地址写入外设提供的门铃寄存器中就可以发起DMA. 写门铃寄存器的操作本质是一条CPU mov指令,当该指令返回后,CPU就可以继续发起下一次DMA. HwPerfIO在循环中不断地发起DMA,直到硬件提交队列中没有空间容纳更多的I/O请求,每当有完成命令释放提交队列资源时,HwPerfIO继续发起DMA以保证硬件提交队列始终处于忙碌状态. 硬件的提交队列深度代表了外设能同时处理的命令数,反映了硬件执行I/O的能力,当外设的提交队列始终充满待处理I/O请求时,硬件性能达到最佳.

    当CPU发起DMA后,后续的数据传输工作将全部由DMA控制器完成. 当数据传输完成后,外设主动通过DMA将响应描述符传输到主机内存,随后中断CPU,CPU通过中断服务例程读取响应描述符中的完成信息,向I/O栈上报I/O成功或失败的完成状态. HwPerfIO采用轮询的方式检查I/O完成,能够在第一时间感知从而避免中断上下文的切换等开销. 而且HwPerfIO采用执行I/O任务之外的CPU核心来轮询I/O的完成并完成性能统计,极大程度地发挥了硬件的性能并最小限度地引入软件开销.

    HwPerfIO能够直观地展现出在指定I/O模式下的带宽、吞吐率及平均延迟. 在4 KB随机写的同等测试条件下,HwPerfIO与fio测试所得单次I/O的平均延迟分别为13.76 μs与18.72 μs. 其中13.76 μs接近硬件的访问时间,其差值即为内核I/O栈所引入的软件开销,约为5 μs. 所以HwPerfIO的确能够测得更加真实准确的硬件性能. 详细的分析与论证详见5.1节.

    结合第3节HwPerfIO的测试结果,在特定测试条件下,软件栈的开销占整个I/O路径延迟的26.7%,所以构建基于用户空间的SCSI I/O栈具有重要意义. 为了充分释放SAS SSD的性能潜力,本文设计并实现了用户空间的SCSI系统软件栈,弥补了当前用户空间存储软件架构缺乏对SCSI子系统支持的不足.

    SCSI子系统是内核的重要组成部分,通过多层次的抽象设计,提供了统一的接口处理不同类型的SCSI设备. 如图5所示,Linux SCSI子系统通常被分为上中下3层:1)上层是具体的SCSI设备驱动(如机械硬盘),Linux内核中的实现为sd.c;2)中间层是SCSI总线驱动,提供了一组通用接口与服务,管理着SCSI命令的整个生命周期,该部分通常由操作系统开发者维护;3)下层是具体的SCSI控制器驱动,驱动程序直接与硬件通信,负责将中间层传递的SCSI命令转换为特定设备所需要的命令,通常由各自外设厂商自行开发维护.

    图  5  Linux内核SCSI子系统分层架构
    Figure  5.  Linux kernel SCSI subsystem layered architecture

    SCSI 三层驱动模型进行了完善的功能划分,定义了高效通用的接口. 如果在用户空间实现SCSI软件栈模型,首先需要在用户空间建立有效的SCSI设备管理框架;其次需要保证DMA数据传输的安全性;最后,用户空间SCSI存储子系统应向应用提供便捷的编程接口. 当前的用户空间存储研究中,SPDK提供了相对完善的基础设施,在SPDK的基础上实现用户空间SCSI软件栈能够简化实现并丰富用户空间SCSI的使用场景. 原生SPDK主要包含3部分:1)最靠近硬件的是硬件驱动,如NVMe驱动等,这是SPDK的基础组件;2)包含块设备抽象的存储服务层,主要目的是将底层的不同存储设备抽象为统一形式,在此基础上提供了诸如压缩去冗、逻辑卷管理、服务质量等存储服务;3)存储协议层主要包含vhost-scsi Target等一系列存储协议,主要目的是丰富应用场景,为网络对端或虚拟机中的启动器提供存储服务. 因此,本文基于SPDK设计并实现了用户空间的SCSI驱动框架.

    本文提出的用户空间SCSI子系统设备管理框架实现在存储服务层,详见4.1节所述. 该框架主要对应Linux内核SCSI子系统的上层及中层,目的是兼容不同厂商的SCSI控制器,提供统一的规范与接口,并管理所有的SCSI存储设备,以及提供SCSI块设备的抽象. 同时该框架对应于SPDK中的NVMe块设备模块,其中用户空间SCSI块设备是本文提出的一种全新设备类型,代表SCSI存储设备的抽象,类似于NVMe块设备,均可作为SPDK原生块设备具体的后端存储设备. 在硬件驱动部分,本文实现了一个用户空间驱动的实例,即mpt3sas[45],主要对应SCSI子系统的下层,同时对应于NVMe SSD设备驱动,均位于硬件驱动层,用于在用户空间直接驱动外设,详见4.2节. 图6描述了用户空间SCSI子系统的两大功能模块与原生SPDK的关联关系.

    图  6  基于SPDK的用户空间SCSI存储I/O栈
    Figure  6.  User space SCSI storage I/O stack based on SPDK

    用户空间SCSI设备管理框架主要实现了4部分功能:SCSI块设备模块的实现;RPC管理接口的实现;驱动设备管理功能的实现;SCSI底层驱动接口与规范的实现.

    1)SCSI块设备模块的实现. 该模块主要用于接收上层的通用I/O命令,这是SPDK规定的自定义块设备必须实现的部分. SPDK原生块设备为所有后端设备提供了统一的API,上层应用可以通过统一的I/O命令下发到目标块设备. 主要接口包括命令入队、命令重置以及超时重传等,并支持通过远程系统调用(remote procedure call, RPC)来配置块设备. 其中最重要的是I/O处理,即命令入队函数,它负责通用I/O命令与自定义块设备命令的转换. 在本例中,I/O处理函数需要将来自上层的通用命令转换为SCSI协议的标准命令,如果该块设备后端的设备是NVMe SSD,那么NVMe块设备模块负责通用I/O命令到NVMe命令的转换.

    2)RPC管理接口的实现. 该模块主要用于配置块设备,RPC是SPDK的一种重要通信机制,允许用户以及应用程序对SPDK进行远程配置与控制. 主要功能包括但不限于设备的创建与销毁、设备状态的查询等. SPDK的RPC基于JSON-RPC 2.0实现,所有的请求与响应均以JSON编码,有效降低了复杂存储系统的管理难度.

    3)驱动设备管理功能的实现. 用户空间无法沿用内核空间的设备驱动管理方式. 所以本文设计实现了一种全新的驱动设备管理办法,在设备管理框架中实现了2个全局链表,分别用于链接设备与驱动. 所有需要初始化的设备及驱动在SPDK初始化阶段利用编译器指令__attribute__(constructor)先于主函数完成构造. 所有SCSI后端存储设备依赖于底层驱动的设备上报机制,每当底层驱动上报一个物理设备时,SCSI中间层会将其注册到全局设备链表中,每一个设备的成功注册都会遍历全局驱动链表,如果设备与驱动能够完成匹配,则进入驱动初始化阶段,直到初始化过程结束. 此时用户或应用程序就可以通过RPC接口来管理设备. 以SCSI块设备的创建为例,用户编写如图7所示的JSON示例便可基于底层驱动上报的物理设备sda创建一个名为 custom的400 GB的SCSI块设备. SCSI块设备与具体的物理SCSI设备之间存在用户定义的映射关系,该机制允许用户便捷地定制自己对硬件设备的需求,允许多用户共享同一个存储设备.

    图  7  创建SCSI块设备的JSON命令
    Figure  7.  JSON command to create SCSI block device

    4)SCSI底层驱动接口与规范的实现. 本文在用户空间实现了SCSI子系统存储框架,由于硬件设备驱动大都由硬件制造厂商开发维护,为了能够兼容不同厂商的存储产品,本文在SCSI设备管理框架与用户空间SCSI驱动之间实现了一套接口规范,遵从该规范实现的用户空间SCSI底层驱动均可无缝接入该系统. 该接口规范主要明确了用户空间SCSI底层驱动必须实现的方法,主要包括但不限于:

    ① SCSI命令入队. 该方法用于接收SCSI设备管理框架所规定的命令格式并转换为存储厂商自定义的命令格式以适配不同厂商的SCSI存储控制器.

    ② SCSI总线扫描. 底层驱动必须实现总线扫描方法,该方法用于主动发现SCSI控制器连接的SCSI存储设备,被发现的存储设备会被上报给SCSI设备管理框架.

    ③ SCSI控制器重置. 该方法用于尝试恢复故障的控制器.

    ④ SCSI设备重置. 该方法用于尝试恢复故障的设备.

    SPDK硬件驱动部分负责直接与硬件交互,该部分实现了博通mpt3sas[45]驱动的用户空间化,核心逻辑主要包括3部分:1)存储控制器基址寄存器(base address register, BAR)空间的映射;2)DMA空间的申请;3)轮询机制的实现.

    1)BAR空间的映射.PCIe配置空间与BAR空间是PCIe设备向操作系统暴露的编程接口,这2段空间均需要映射到系统物理地址空间(system physical address, SPA),即主机的CPU地址空间中,其中配置空间的映射在基本输入输出系统(basic input/output system, BIOS)启动阶段完成,BAR空间的映射在驱动初始化过程中完成. 完成映射后,CPU可以使用与内存访问相同的方式直接访问PCIe设备的寄存器接口,相比于端口I/O极大地提高了编程的便携性与统一性. 如果驱动位于内核空间,内核通过remap机制建立内核页表,构造虚拟地址与PCIe寄存器地址的映射关系,如果将驱动实现在用户空间,则必须利用用户空间驱动框架VFIO(virtual function I/O)[46]将PCIe的配置空间与BAR空间映射到用户空间,即建立PCIe寄存器物理地址与虚拟地址的用户空间页表. 在Intel平台VFIO充分利用Vt-d所提供的DMA重映射以及中断重映射技术,在保证直通设备DMA安全性的同时可以达到接近物理设备的I/O性能,用户空间进程可以直接使用VFIO驱动访问硬件,从而可以在用户空间完成设备驱动框架的构建.

    2)DMA空间的申请.DMA是数据传输的主要方式. 在内核空间中,驱动程序通过内核实现的安全接口申请固定的、缓存一致性的DMA内存,该内存用于存放固定的信息(如请求描述符、响应描述符等),该内存空间在驱动的整个生命周期中均有效,所以这种DMA映射方式被称为一致性DMA映射. 与之相对应的是流式DMA映射,DMA需要显式地冲刷或失效缓存来保证数据负载DMA缓存的一致性,其具体实现方式取决于不同类型的CPU架构. 在用户空间中,SPDK同样实现了类似的DMA分配函数来保证DMA的缓存一致性. SPDK通过Linux内核的大页机制保证了物理地址与虚拟地址的固定绑定关系,用户空间所有的DMA操作都必须依赖该函数,这样申请出的DMA内存空间才是安全可靠的.

    3)轮询机制的实现. 外设通知内核I/O完成的主要方式是中断,中断控制器接收到外设发送的中断信号后中断CPU,进而在中断上下文执行对应的中断处理函数. 中断上下文切换以及中断处理函数调度所带来的CPU开销在新型高速存储设备上无法忽略,所以SPDK采用事件通知的机制轮询I/O操作的完成,轮询器可以有效降低系统调用以及上下文切换带来的CPU开销,提升I/O处理效率.

    本文的测试均基于Dell PowerEdge R750服务器进行,相关配置信息详见表1.

    表  1  实验环境
    Table  1.  Experiment Environment
    软硬件 配置
    CPU Intel至强金牌CPU 6348 56核心
    内存/GB 256
    操作系统 Ubuntu22.04 5.15.0-72-generic
    HBA 9500-8e HBA [47]
    SPDK版本 v23.01
    驱动 mpt3sas
    SAS SSD 铠侠 24 G SAS SSD KPM71RUG3T84 单端口模式
    测试工具 fio-3.35 filebench-1.5[48] HwPerfIO
    下载: 导出CSV 
    | 显示表格

    首先使用HwPerfIO测试铠侠SAS SSD的单盘基础性能,包括带宽、延迟及吞吐率,并将结果与fio在相同测试条件下进行比较. 测试参数见表2,实验结果如图8所示.

    表  2  基础性能测试参数
    Table  2.  Basic Performance Test Parameters
    参数类型带宽测试对应
    参数配置
    延迟测试对应
    参数配置
    吞吐率测试对应
    参数配置
    队列深度111
    工作任务数1611
    I/O负载类型顺序读写随机读写随机读写
    单次I/O大小/KB12844
    运行时间/s303030
    fio ioenginelibaiolibaiolibaio
    下载: 导出CSV 
    | 显示表格
    图  8  fio与HwPerfIO I/O测试对比
    Figure  8.  Comparison of fio and HwPerfIO I/O test

    1)可以观察到HwPerfIO与fio测试所得的带宽没有明显差异. 这是因为带宽测试的I/O模型并非延迟敏感型负载,单次I/O延迟主要取决于块大小,且与数据块大小正相关,如128 KB的读写延迟在200 μs左右,内核软件栈所引入的微秒量级时间开销对整体性能测试并不会产生明显影响. 由于单次I/O传输数据块大,硬件大部分时间都在进行有效的数据传输工作,相对小的队列深度就可以保证硬件持续不断地工作,所以队列深度对带宽测试的影响相对较低. 并且考虑到大多数情况下单个CPU无法满足硬件的能力上限,因此带宽的测试结果主要取决于工作线程数,即测试所用的CPU核心数. 当到达硬件带宽上限后,继续增加CPU核心不会显著提升性能. 相反,当工作线程的数量超过系统CPU核心数量后,由于任务之间的相互竞争,性能可能会有所下降,这种现象在计算能力较弱的CPU上表现更加明显,所以工作线程的数量不能超过CPU总数. 选用表2所描述的测试参数即可测得铠侠SAS SSD在单端口模式下的带宽上限.因此,在带宽测试方面,HwPerfIO并不会比fio得到更加准确的结果,同时也说明fio和HwPerfIO都能够提供可靠的性能评估.

    2)延迟是存储设备需要考虑的重要性能指标之一,单次I/O延迟能够最直观地表现出I/O栈的额外时间开销,对于4 KB的随机读操作,HwPerfIO与fio测得的延迟分别是13.76 μs与18.72 μs;对于4 KB的随机写操作,延迟分别为37.49 μs与49.25 μs. 由于一定深度的硬件队列普遍存在于主流存储设备中,对于刚入队的I/O请求而言,如果此时队列处于繁忙状态,那么这个I/O请求将持续排队直到被硬件处理. 尽管会得到相对较高的吞吐率,但是平均延迟也会增大,无法反映出硬件处理单个I/O请求的实际时间,所以本文在测试平均延迟时始终保持队列深度为1.

    3)在fio与HwPerfIO的测试结果中,随机读的吞吐率分别为20.1 KIOPS与26.6 KIOPS,随机写的吞吐率分别为50.5 KIOPS与72.2 KIOPS,在4 KB的随机读写测试中,HwPerfIO测得的吞吐率分别比fio高出约24.4%与30.5%,即内核I/O栈为性能测试带来了一定的不准确性. 吞吐率主要考察硬件处理密集型随机读写小I/O的能力,属于延迟敏感型负载. 不同于带宽负载,小I/O负载时,硬件大部分时间都在处理小I/O之间的切换,而并非数据传输. 为了测试出硬件的极限性能,随机读写的小I/O必须充分利用硬件所提供的异步提交队列,当队列中待处理请求充足时,额外的CPU资源并不会带来正向的吞吐率收益. 但是当硬件性能到达极限后,单个I/O平均延迟增大,且流水线会稀释每一个I/O请求在I/O路径上的延迟,无法准确反映出软件栈对吞吐率的影响. 所以测试参数同表2中所述,选择队列深度为1,工作任务数为1,单次I/O大小为4 KB,这样能够反映出软件栈对吞吐率的最大影响. 单次I/O延迟对整体吞吐率的影响非常显著,内核软件栈所引入的微秒量级时间开销会对测试结果产生明显的影响,HwPerfIO能够测试出硬件更加准确的吞吐率,这也从侧面印证了Linux内核I/O栈中软件开销对性能测试的影响.

    为了评估SAS SSD在不同软件栈下的性能表现,我们使用SPDK提供的内置工具bdevperf[49]和主流的内核I/O栈性能测试工具fio分别对用户空间SCSI与Linux内核管理的SAS SSD进行了性能测试与分析. fio是由Jens Axboe(Linux块层的维护者)主导开发的开源I/O测试工具,支持多种不同类型的I/O引擎,能够提供丰富的I/O性能指标,其作为Linux下典型存储性能测试工具得到了广泛的认可与应用,所以选择fio作为传统Linux内核I/O栈的性能测试工具. 此处不使用HwPerfIO代替fio测试Linux内核管理的SAS SSD的原因是:本节的主要目的是评估用户空间SCSI I/O栈与内核空间I/O栈的性能差异,如果使用HwPerfIO代替fio进行测试,将无法测试出内核I/O栈对存储设备的性能影响,所以无法采用HwPerfIO代替fio进行测试.

    而基于SPDK构建的用户空间SCSI软件栈与传统Linux内核软件栈并不兼容,所以无法直接使用fio进行性能测试. 而bdevperf是SPDK中提供的性能评估工具. bdevperf提供了与fio类似的命令行格式与配置方式,支持用户自定义测试参数,包括但不限于CPU核心数、块大小、I/O队列深度等,并能够支持一系列的基准操作,包括顺序读写、随机读写以及各种混合I/O模式. HwPerfIO与用户空间SAS SSD性能测试工具bdevperf的主要区别在于其目的不同. HwPerfIO的主要目的是消除软件栈中可能引入的开销以测试硬件本身的性能. 而基于用户空间驱动所构建的SAS SSD性能测试工具(如bdevperf)实际是在测试用户空间软件栈与SAS SSD的整体性能.

    图9给出了在工作任务数量固定为1且测试时间固定的前提下,分别测试Linux内核I/O栈与用户空间SCSI I/O栈下4 KB随机读写性能随队列深度变化的情况,其他相关测试参数详见表3.

    图  9  不同队列深度下的吞吐率
    Figure  9.  IOPS under different number of queue depth
    表  3  fio与bdevperf测试配置
    Table  3.  fio and bdevperf Test Configuration
    参数类型带宽测试对应参数配置吞吐率测试对应参数配置
    队列深度11~64
    工作任务数1~161
    块大小/KB1284
    执行时间/s300300
    I/O模型随机/顺序读写随机/顺序读写
    fio ioenginelibaiolibaio
    下载: 导出CSV 
    | 显示表格

    随着队列深度的增大,吞吐率也随之增大,直至到达硬件上限. bdevperf的测试结果反映本文所提出的用户空间SCSI I/O栈,fio的测试结果对应于传统Linux内核I/O栈. 可以看出,在队列深度较小时,基于用户空间的I/O栈相比于内核空间I/O栈在延迟敏感型的小I/O处理上表现更佳,读写IOPS分别提升约为24.4%和30.1%. 这主要归因于减少了内核空间与用户空间的切换开销,优化了I/O路径,使得系统响应速度更快. 当队列深度增大后,命令提交队列的流水线开始起主要作用,软件栈所占的开销被流水线所隐藏,硬件的极限吞吐率差异相对较小. 图10展示了在队列深度保持为1的情况下,Linux内核I/O栈与用户空间SCSI I/O栈下128 KB顺序读写随工作任务数变化的性能变化情况. 在顺序读写测试中,用户空间SCSI I/O栈与Linux内核I/O栈的带宽表现几乎一致,即对于非延迟敏感型操作,单次I/O的延迟影响较小,用户空间SCSI I/O栈主要优化目标为延迟敏感型的小I/O负载.

    图  10  不同工作任务数下的带宽
    Figure  10.  Bandwidth under different number of tasks

    尽管vhost-user提升了I/O性能,但SCSI始终处于内核空间,无法利用vhost-user来提升其性能. 本文提出的用户空间SCSI I/O栈则填补了这一空缺,在虚拟机与SCSI后端存储设备之间提供了一条高速I/O通路. 图11描述了客户机如何利用vhost-user访问用户空间SCSI所管理的存储设备. 虚拟机中生成的I/O请求通过客户机virtio驱动进入到virtqueues队列,SPDK持续轮询该队列的I/O请求,如果有新的I/O请求产生,则转发到SCSI块设备模块,并通过实现在用户空间的SCSI驱动将I/O请求发往具体的存储设备,该过程全部在用户空间完成,有效降低了Linux内核所带来的各种延迟开销.

    图  11  基于用户空间SCSI I/O栈的I/O虚拟化
    Figure  11.  I/O virtualization based on user space SCSI I/O stack

    为了测试基于用户空间SCSI 存储框架的I/O虚拟化性能,本文对比了2种方案:virtio + Linux内核SCSI与virtio + 用户空间SCSI的文件系统性能. 具体来说,存储设备分别通过Linux内核与vhost-user的方式被客户机识别后,在该虚拟I/O设备上建立第4代扩展文件系统(fourth extended filesystem, EXT4),并进行了filebench自定义随机读写、网页服务器负载webserver以及文件服务器负载fileserver的测试.

    本文使用filebench工具构造了随机读写负载,测试过程仅执行3个简单操作:1)打开文件;2)读取整个16 KB文件/写入整个16 KB文件;3)关闭文件.

    图12展示了2种方案在自定义随机读写、网页服务器负载以及文件服务器负载下的性能表现. 对比结果显示,virtio + 用户空间SCSI方案相对于Linux内核SCSI方案在性能上有一定的优势,具体结果如下. Virtio + 用户空间SCSI在随机读写操作与文件服务器负载中均呈现出了与Linux内核SCSI方案相当的性能. 并且在自定义随机读操作以及网页服务器负载中表现出了一定的优势,带宽与单位时间内的操作数提升幅度基本一致,分别为3.24%与3.12%. 同时,自定义随机读操作以及网页服务器负载的单个操作平均延迟降幅均约为3.12%. 这表明用户空间SCSI在性能不降低的前提下,能够应用于大部分的使用场景,为SCSI存储设备提供更加丰富的I/O虚拟化方案.

    图  12  用户空间SCSI I/O虚拟化性能
    Figure  12.  User space SCSI I/O virtualization performance

    本文首先提出了存储设备性能测试工具HwPerfIO,它能够有效避免I/O栈对测试结果的影响,准确地反映存储设备的硬件性能. HwPerfIO对SAS SSD的测试结果表明,在SCSI协议栈中,由软件所引入的时间开销已不可忽略. 进而本文提出了一种基于SPDK的用户空间SCSI存储I/O栈,通过将SCSI子系统用户空间化,实现用户空间对 SCSI设备的管理与控制,在应用程序与SAS SSD高速设备之间提供了一条高速I/O通路. 实验结果表明,对于延迟敏感的小I/O随机负载,用户空间SCSI存储I/O栈为存储系统带来了一定的性能提升,能够进一步发挥出 SAS SSD硬件的性能潜力.

    尽管CPU轮询I/O能够降低存储系统对应用程序的响应时间,但是轮询始终会占用CPU资源,Intel的Sapphire Rapid[50]CPU架构支持直接在用户空间接收外设中断,借助Sapphire Rapid构建完全用户空间的存储软件栈将是下一步工作计划.

    作者贡献声明:郝栋栋设计了论文的整体逻辑架构,并完成了论文实验方案的设计与验证;高聪明完善论文内容,负责论文结构的讨论与修改;舒继武提出论文撰写指导意见.

计量
  • 文章访问数:  105
  • HTML全文浏览量:  50
  • PDF下载量:  80
  • 被引次数: 0
出版历程
  • 网络出版日期:  2025-02-26
  • 刊出日期:  2025-02-28

目录

/

返回文章
返回