数据完整性¶
1. 简介¶
现代文件系统支持对数据和元数据进行校验和计算,以防止数据损坏。然而,损坏的检测是在读取时进行的,这可能在数据写入数月后才发生。届时,应用程序试图写入的原始数据很可能已经丢失。
解决方案是确保磁盘实际存储了应用程序想要存储的数据。SCSI 系列协议(SBC 数据完整性字段,SCC 保护提案)以及 SATA/T13(外部路径保护)的最新增补都试图通过增加对 I/O 中附加完整性元数据的支持来弥补这一不足。完整性元数据(或 SCSI 术语中的保护信息)包括每个扇区的校验和,以及一个递增计数器,以确保各个扇区以正确的顺序写入。对于某些保护方案,还包括确保 I/O 写入磁盘上的正确位置。
当前的存储控制器和设备实现了各种保护措施,例如校验和和擦洗。但这些技术在各自独立的域中工作,或者充其量只是在 I/O 路径中的相邻节点之间工作。DIF 和其他完整性扩展的有趣之处在于,保护格式定义良好,并且 I/O 路径中的每个节点都可以验证 I/O 的完整性,如果检测到损坏则拒绝它。这不仅可以防止损坏,还可以隔离故障点。
2. 数据完整性扩展¶
如前所述,协议扩展只保护控制器和存储设备之间的路径。然而,许多控制器实际上允许操作系统与完整性元数据(IMD)交互。我们已经与多家 FC/SAS HBA 供应商合作,使保护信息能够在它们控制器之间传输。
SCSI 数据完整性字段的工作方式是,将 8 字节的保护信息附加到每个扇区。数据 + 完整性元数据以 520 字节扇区存储在磁盘上。数据 + IMD 在控制器和目标之间传输时是交错的。T13 提案类似。
由于操作系统处理 520(和 4104)字节扇区非常不便,我们与多家 HBA 供应商接洽,鼓励他们允许分离数据和完整性元数据的分散-聚集列表。
控制器在写入时将缓冲区交错,在读取时将它们拆分。这意味着 Linux 可以将数据缓冲区通过 DMA 传输到宿主内存和从宿主内存传输,而无需更改页缓存。
此外,SCSI 和 SATA 规范强制要求的 16 位 CRC 校验和在软件中计算起来有些繁重。基准测试发现,计算此校验和对许多工作负载的系统性能产生了显著影响。一些控制器允许在与操作系统交互时使用更轻量级的校验和。例如,Emulex 支持 TCP/IP 校验和。从操作系统接收到的 IP 校验和在写入时会转换为 16 位 CRC,反之亦然。这使得 Linux 或应用程序能够以非常低的成本生成完整性元数据(与软件 RAID5 相当)。
就检测位错误而言,IP 校验和比 CRC 弱。然而,其真正的优势在于数据缓冲区和完整性元数据的分离。这两个不同的缓冲区必须匹配才能完成 I/O。
数据和完整性元数据缓冲区的分离以及校验和的选择被称为数据完整性扩展。由于这些扩展超出了协议机构(T10, T13)的范围,甲骨文及其合作伙伴正试图在存储网络行业协会中将其标准化。
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 普通文件系统¶
普通文件系统并不知道底层块设备能够发送/接收完整性元数据。在写入操作时,IMD 将在
submit_bio()
调用时由块层自动生成。读取请求将导致 I/O 完整性在完成后被验证。IMD 的生成和验证可以通过
/sys/block/<bdev>/integrity/write_generate和
/sys/block/<bdev>/integrity/read_verify标志进行切换。
5.2 完整性感知文件系统¶
一个完整性感知文件系统可以准备附加了 IMD 的 I/O。如果块设备支持,它还可以使用应用程序标签空间。
bool bio_integrity_prep(bio);
为了生成写入操作的 IMD 并为读取操作设置缓冲区,文件系统必须调用 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()。对于写入操作,页面中的完整性元数据必须是目标设备能够理解的格式,但值得注意的是,当请求遍历 I/O 栈时,扇区号将被重新映射。这意味着使用此调用添加的页面在 I/O 期间将被修改!完整性元数据中的第一个引用标签必须具有 bip->bip_sector 的值。
只要 bip bio_vec 数组(nr_pages)中有空间,就可以使用 bio_integrity_add_page() 添加页面。
读取操作完成后,附加的页面将包含从存储设备接收到的完整性元数据。接收方有责任在完成后处理它们并验证数据完整性。
2007-12-24 Martin K. Petersen <martin.petersen@oracle.com>