固态设备 (SSD) 是一种复杂的设备,其性能取决于使用方式。NVMe 固态硬盘 (SSD) 的主要组件包括:
控制器:管理 SSD 的操作,包括数据处理、错误纠正和存储管理。
NAND 闪存:存储数据。
NAND 芯片:存储数据在闪存的单元上。
PCIe 总线:高速接口,其速度比硬盘访问快得多。
DRAM:用作 SSD 和外部设备之间的缓存。
SSD 的其他组件包括:电源管理模块、热控制器和加密引擎。
本文旨在介绍 SSD 内部发生的操作,结合 SPDK 的用户态驱动[1],以便能够更好地理解 SPDK 的存储处理逻辑和优势。
*注:SSD 硬件实际工作原理的严格准确的指南,建议参考规范组织的文档。
如上面提到的NVMe SSD的主要组成部分,NVMe 固态盘的存储介质主要是 NAND 介质,这种介质具有一些重要属性:
NAND 闪存介质被分组为大单元,通常称为擦除块。擦除块的大小特定于具体厂商的实现,但可以认为介于 1MiB 和 8MiB 之间。对于每个擦除块,每个位可以以位粒度写入一次(即,将其位从 0 翻转为 1)。为了第二次写入擦除块,必须擦除整个块(即,将块中的所有位翻转回 0)。这是上面的不对称部分。擦除一个块会造成可测量的磨损,并且每个块只能被擦除有限的次数。
SSD 向主机系统公开呈现块的接口,使驱动器看起来好像由一组固定大小的逻辑块组成,这些逻辑块通常为 512B 或 4KiB。这些块是设备固件管理的逻辑地址,它们不会静态映射到具体介质上的位置。相反,每次写入逻辑块时,都会选择并写入 NAND 闪存上的新位置,并更新逻辑块与其物理位置的映射。选择新位置的算法是整体 SSD 性能的关键部分,通常称为闪存转换层或 FTL。该算法必须正确分配块以考虑磨损(称为磨损均衡),并将它们高效分布在 NAND 芯片上以提高总可用性和存储性能。
最简单的模型是使用类似于 RAID 的算法将每个芯片上的所有物理介质分组在一起,然后按顺序写入该集合。真正的 SSD 要复杂得多,但对于理解SSD内部地址管理来说,这是一个可以类比的简单模型。
闪存转换层 FTL 的一个结果是逻辑块不一定始终与 NAND 上的物理位置相对应。基于此,有一个命令可以清除块的转换。在 NVMe 中,此命令称为 deallocate,在 SCSI 中称为 unmap,在 SATA 中称为 trim。当用户尝试读取没有映射到物理位置的块时,驱动器将执行以下两项操作之一:
立即成功完成读取请求,而不执行任何数据传输。这是可以接受的,因为驱动器返回的数据并不比用户数据缓冲区中已有的数据更有效。
返回全 0 作为数据。
选择 #1 的操作在 SSD 厂商这里更为常见。举个例子:对没有任何有效数据的 NVMe SSD 执行读操作,通常会显示远远超出驱动器声称的性能,因为它实际上并未传输任何数据。由此,在进行基准 SSD 读测试时,建议先写入所有块,覆盖整个 SSD 的存储空间,然后再读取它们,这样的测试更接近于实际业务的场景!
随着 SSD 的写入,内部日志最终将消耗所有可用的擦除块。为了继续写入,SSD 必须释放其中一些块。此过程通常称为垃圾收集。所有 SSD 都会保留一定数量的内部使用的擦除块,以便它们可以保证有可用的擦除块可供垃圾收集。垃圾收集通常按以下方式进行:
选择目标擦除块(一个好的处理是选择最近最少使用的擦除块)。
遍历擦除块中的每个条目并确定它是否仍然是有效的逻辑块。
通过读取有效逻辑块并将其写入不同的擦除块来移动它们。
擦除整个擦除块并将其标记为可供使用。
如果由于擦除块已经为空而可以跳过步骤 3,那么垃圾收集显然会更加高效。有两种方法可以大大提高跳过步骤 3 的可能性。第一种方法是 SSD 会保留超出其报告容量的额外擦除块(称为 Over Provisioning ,这个值设置的越大,对于找到干净的擦除块越容易。带来的开销是,实际应用看到的 SSD 的容量越小。),因此从统计上讲,擦除块不包含有效数据的可能性更大。第二种方法是软件可以按追加写模式按顺序写入设备上的块,在不再需要旧数据时将其丢弃。在这种情况下,通常上层软件可以保证最近最少使用的擦除块不会包含任何必须移动的有效数据。
SPDK 用户态驱动对于 NVMe SSD 的读写,以下是主要的接口[2]:
主要功能 | 描述 |
---|---|
spdk_nvme_ns_cmd_read() | 将读取 I/O 提交到指定的 NVMe 命名空间。 |
spdk_nvme_ns_cmd_readv() | 向指定的 NVMe 命名空间提交读取 I/O。 |
spdk_nvme_ns_cmd_read_with_md() | 将带元数据的读取 I/O 提交到指定的 NVMe 命名空间。 |
spdk_nvme_ns_cmd_write() | 向指定的 NVMe 命名空间提交写入 I/O。 |
spdk_nvme_ns_cmd_writev() | 向指定的 NVMe 命名空间提交写入 I/O。 |
spdk_nvme_ns_cmd_write_with_md() | 向指定的 NVMe 命名空间提交写入 带元数据的I/O。 |
spdk_nvme_ns_cmd_write_zeroes() | 向指定的 NVMe 命名空间提交写入零 I/O。 |
spdk_nvme_ns_cmd_dataset_management() | 向指定的 NVMe 命名空间提交数据集管理请求,包括 deallocate 操作。 |
spdk_nvme_ns_cmd_flush() | 向指定的 NVMe 命名空间提交刷新请求。 |
spdk_nvme_ctrlr_cmd_io_raw() | 将给定的 NVM I/O 命令发送到 NVMe 控制器。 |
spdk_nvme_ctrlr_cmd_io_raw_with_md() | 将给定的 NVM I/O 命令连同元数据一起发送到 NVMe 控制器。 |
通过 SPDK 用户态驱动的多核并发、异步处理、主动轮询,可以高效结合本文提到的 NAND SSD 的内在的主要处理逻辑,详细的介绍可以参考:剖析SPDK读写NVMe盘过程--从hello_world开始。
以下是一个典型的存储优化[3]:
下面的数据来自参考链接4,在 SPDK v24.05 版本上,单个 CPU Core,通过 SPDK NVMe Bdev 设备(在 SPDK NVMe 用户态驱动之上构建的用户态块设备),基于 4K 70/30 随机读写得到的数据,横坐标是 NVMe SSD 盘的数量,纵坐标是整体的 IOPS。
*注:更多硬件配置和测试方法,参考下面的参考文档[4]。
参考文献:
转载须知
推荐阅读
剖析SPDK读写NVMe盘过程--从hello_world开始
往期阅读
使用NTB加速基于NVMe-oF的边缘小规模存储集群的数据传输
SPDK FIO Bdev Plugin支持RPC,让资源管理面更容易