数据完整性¶
1. 简介¶
现代文件系统具有数据和元数据的校验和功能,以防止数据损坏。然而,损坏的检测是在读取时进行的,这可能是在数据写入后的几个月。届时,应用程序试图写入的原始数据很可能已经丢失。
解决方案是确保磁盘实际存储了应用程序想要存储的内容。最近对SCSI系列协议(SBC数据完整性字段、SCC保护提案)以及SATA/T13(外部路径保护)的添加试图通过为I/O添加完整性元数据来弥补这一点。完整性元数据(或SCSI术语中的保护信息)包括每个扇区的校验和以及一个递增的计数器,以确保各个扇区按正确的顺序写入。对于某些保护方案,还确保I/O被写入磁盘上的正确位置。
当前的存储控制器和设备实现了各种保护措施,例如校验和和擦除。但是这些技术在其各自独立的域中工作,或者最多在I/O路径中的相邻节点之间工作。DIF和其他完整性扩展的有趣之处在于,保护格式定义明确,I/O路径中的每个节点都可以验证I/O的完整性,如果检测到损坏则拒绝该I/O。这不仅可以防止损坏,还可以隔离故障点。
2. 数据完整性扩展¶
如上所述,协议扩展仅保护控制器和存储设备之间的路径。但是,许多控制器实际上允许操作系统与完整性元数据(IMD)交互。我们一直在与几家FC/SAS HBA供应商合作,以使保护信息能够在控制器之间传输。
SCSI数据完整性字段的工作原理是将8个字节的保护信息附加到每个扇区。数据+完整性元数据存储在磁盘上的520字节扇区中。当在控制器和目标之间传输时,数据+ IMD是交错的。T13提案类似。
由于操作系统处理520(和4104)字节扇区非常不方便,因此我们与多家HBA供应商联系,并鼓励他们允许分离数据和完整性元数据的散布-收集列表。
控制器将在写入时交错缓冲区,并在读取时分割它们。这意味着Linux可以将数据缓冲区DMA到主机内存以及从主机内存DMA,而无需更改页面缓存。
此外,SCSI和SATA规范都要求的16位CRC校验和在软件中计算有些繁重。基准测试发现,计算此校验和对许多工作负载的系统性能产生了重大影响。某些控制器允许在与操作系统接口时使用更轻量级的校验和。例如,Emulex支持TCP/IP校验和。从操作系统收到的IP校验和在写入时转换为16位CRC,反之亦然。这使得Linux或应用程序可以以非常低的成本生成完整性元数据(与软件RAID5相当)。
就检测位错误而言,IP校验和比CRC弱。但是,优势实际上在于数据缓冲区和完整性元数据的分离。这两个不同的缓冲区必须匹配才能完成I/O。
数据和完整性元数据缓冲区的分离以及校验和的选择被称为数据完整性扩展。由于这些扩展超出了协议机构(T10、T13)的范围,因此Oracle及其合作伙伴正在努力在存储网络行业协会内将其标准化。
3. 内核更改¶
Linux中的数据完整性框架使保护信息能够固定到I/O并发送到支持它的控制器/从支持它的控制器接收。
SCSI和SATA中完整性扩展的优点是它们使我们能够保护从应用程序到存储设备的整个路径。但是,与此同时,这也是最大的缺点。这意味着保护信息的格式必须是磁盘可以理解的格式。
通常,Linux/POSIX应用程序对它们访问的存储设备的复杂性不了解。虚拟文件系统开关和块层使硬件扇区大小和传输协议之类的东西对应用程序完全透明。
但是,在准备要发送到磁盘的保护信息时,需要此级别的详细信息。因此,端到端保护方案的概念是一个分层违规。应用程序了解它是在访问SCSI还是SATA磁盘是完全不合理的。
Linux中实现的数据完整性支持试图向应用程序隐藏这一点。就应用程序(以及在某种程度上是内核)而言,完整性元数据是附加到I/O的不透明信息。
当前的实现允许块层自动为任何I/O生成保护信息。最终的目标是将完整性元数据的计算移至用户空间以用于用户数据。元数据和内核内部产生的其他I/O仍将使用自动生成接口。
某些存储设备允许每个硬件扇区都标记有16位值。此标记空间的所有者是块设备的所有者。即,在大多数情况下是文件系统。文件系统可以使用此额外空间来标记它们认为合适的扇区。由于标记空间有限,因此块接口允许通过交错标记更大的块。这样,可以将8 * 16位的信息附加到典型的4KB文件系统块。
这也意味着诸如fsck和mkfs之类的应用程序将需要从用户空间访问以操作标签。正在研究此接口的直通接口。
4. 块层实现细节¶
4.1 Bio¶
当启用CONFIG_BLK_DEV_INTEGRITY时,数据完整性补丁会向struct bio添加一个新字段。bio_integrity(bio) 返回指向 struct bip 的指针,其中包含 bio 完整性有效负载。本质上,bip 是一个精简的 struct bio,它包含一个 bio_vec,其中包含完整性元数据和所需的内务管理信息(bvec 池、矢量计数等)。
内核子系统可以通过调用 bio_integrity_alloc(bio) 在 bio 上启用数据完整性保护。这将分配 bip 并将其附加到 bio。
随后可以使用 bio_integrity_add_page() 附加包含完整性元数据的各个页面。
bio_free() 将自动释放 bip。
4.2 块设备¶
块设备可以在 queue_limits 结构的 integrity 子结构中设置完整性信息。
分层块设备将需要选择适合所有子设备的配置文件。queue_limits_stack_integrity()
可以帮助解决这个问题。当前支持 DM 和 MD 线性、RAID0 和 RAID1。由于应用程序标签,RAID4/5/6 将需要额外的工作。
5.0 块层完整性 API¶
5.1 普通文件系统¶
普通文件系统不知道底层块设备能够发送/接收完整性元数据。在 WRITE 的情况下,IMD 将在
submit_bio()
时由块层自动生成。READ 请求将导致 I/O 完整性在完成后得到验证。可以使用
/sys/block/<bdev>/integrity/write_generate和
/sys/block/<bdev>/integrity/read_verify标志来切换IMD的生成和验证。
5.2 完整性感知文件系统¶
一个完整性感知的文件系统可以使用附加的IMD准备I/O。如果块设备支持,它也可以使用应用程序标签空间。
bool bio_integrity_prep(bio);
要为 WRITE 生成 IMD 并为 READ 设置缓冲区,文件系统必须调用 bio_integrity_prep(bio)。
在调用此函数之前,必须设置 bio 数据方向和起始扇区,并且 bio 应添加所有数据页面。调用者有责任确保在 I/O 进行时 bio 不会更改。如果由于某种原因准备失败,则使用错误完成 bio。
5.3 传递现有完整性元数据¶
那些生成自己的完整性元数据或能够从用户空间传输 IMD 的文件系统可以使用以下调用
struct bip * bio_integrity_alloc(bio, gfp_mask, nr_pages);
分配 bio 完整性有效负载并将其挂在 bio 上。nr_pages 指示需要在完整性 bio_vec 列表(类似于 bio_alloc())中存储多少页保护数据。
完整性有效负载将在 bio_free() 时释放。
int bio_integrity_add_page(bio, page, len, offset);
将包含完整性元数据的页附加到现有的 bio。该 bio 必须具有现有的 bip,即必须先调用 bio_integrity_alloc()。 对于 WRITE 操作,页中的完整性元数据必须采用目标设备可理解的格式,但值得注意的是,扇区号将在请求遍历 I/O 堆栈时重新映射。这意味着使用此调用添加的页将在 I/O 期间被修改!完整性元数据中的第一个引用标签的值必须为 bip->bip_sector。
只要 bip bio_vec 数组中有空间 (nr_pages),就可以使用 bio_integrity_add_page() 添加页。
在 READ 操作完成后,附加的页将包含从存储设备接收到的完整性元数据。接收方有责任处理它们并在完成后验证数据完整性。
2007-12-24 Martin K. Petersen <martin.petersen@oracle.com>