显式易失性回写缓存控制

简介

许多存储设备,尤其是在消费市场,都带有易失性回写缓存。这意味着设备在数据实际写入非易失性存储之前,会向操作系统发出 I/O 完成信号。这种行为显然加快了各种工作负载,但也意味着操作系统在执行数据完整性操作(如 fsync、sync 或卸载)时,需要强制将数据写入非易失性存储。

Linux 块层提供了两个简单的机制,让文件系统可以控制存储设备的缓存行为。这些机制包括强制缓存刷新和请求的强制单元访问 (FUA) 标志。

显式缓存刷新

可以将 REQ_PREFLUSH 标志 ORed 到从文件系统提交的 bio 的读/写标志中,这将确保在实际 I/O 操作开始之前,存储设备的易失性缓存已被刷新。这明确保证了在标记的 bio 开始之前,先前完成的写入请求已写入非易失性存储。此外,可以在一个空 bio 结构上设置 REQ_PREFLUSH 标志,这只会导致显式缓存刷新,而没有任何依赖的 I/O。建议使用blkdev_issue_flush() 辅助函数来进行纯缓存刷新。

强制单元访问

可以将 REQ_FUA 标志 ORed 到从文件系统提交的 bio 的读/写标志中,这将确保只有在数据已提交到非易失性存储后,才会发出此请求的 I/O 完成信号。

文件系统的实现细节

文件系统可以简单地设置 REQ_PREFLUSH 和 REQ_FUA 位,而无需担心底层设备是否需要任何显式缓存刷新以及如何实现强制单元访问。REQ_PREFLUSH 和 REQ_FUA 标志都可以设置在单个 bio 上。

块驱动程序的功能设置

对于不支持易失性写缓存的设备,不需要驱动程序支持,块层会在进入驱动程序之前完成空的 REQ_PREFLUSH 请求,并从带有有效载荷的请求中去除 REQ_PREFLUSH 和 REQ_FUA 位。

对于带有易失性写缓存的设备,驱动程序需要通过设置

BLK_FEAT_WRITE_CACHE

标志在 queue_limits 的 feature 字段中,告知块层它支持刷新缓存。对于也支持 FUA 位的设备,还需要通过设置

BLK_FEAT_FUA

标志在 queue_limits 结构的 features 字段中,告知块层传递 REQ_FUA 位。

基于 bio 的块驱动程序的实现细节

对于基于 bio 的驱动程序,如果驱动程序设置了 BLK_FEAT_WRITE_CACHE 标志,则 REQ_PREFLUSH 和 REQ_FUA 位会简单地传递给驱动程序,驱动程序需要处理它们。

注意: 当 _未_ 设置 BLK_FEAT_FUA 标志时,也会传递 REQ_FUA 位。任何设置 BLK_FEAT_WRITE_CACHE 的基于 bio 的驱动程序也需要处理 REQ_FUA。

对于重映射驱动程序,REQ_FUA 位需要传播到底层设备,并且需要为设置了 REQ_PREFLUSH 位的 bios 实现全局刷新。

blk-mq 驱动程序的实现细节

当设置了 BLK_FEAT_WRITE_CACHE 标志时,带有有效载荷的 REQ_OP_WRITE | REQ_PREFLUSH 请求会自动转换为块层的 REQ_OP_FLUSH 请求序列,后跟实际的写入。

当设置了 BLK_FEAT_FUA 标志时,REQ_FUA 位会简单地传递给 REQ_OP_WRITE 请求,否则,对于设置了 REQ_FUA 位的 bio 提交,块层会在写入请求完成后发送 REQ_OP_FLUSH 请求。