dm-log-writes¶
此目标需要两个设备:一个用于正常处理所有 I/O,另一个用于记录所有写入操作。这旨在帮助文件系统开发人员验证文件系统写入时元数据或数据的完整性。每个 WRITE 请求都会写入一个 log_write_entry,并且目标能够从用户空间获取任意数据插入到日志中。WRITE 请求中的数据会被复制到日志中,以便重放时能精确还原原始操作。
日志排序¶
一旦我们确定写入不再处于缓存中,我们就会按完成顺序记录事件。这意味着正常的 WRITE 请求实际上要等到下一个 REQ_PREFLUSH 请求之后才会被记录。这样做是为了方便用户空间以与磁盘上内容而非缓存中内容相关联的方式重放日志,从而更容易检测不正确的等待/刷新操作。
其工作原理是,一旦写入完成,所有 WRITE 请求都会被附加到一个列表中。一旦我们看到 REQ_PREFLUSH 请求,我们就会将此列表拼接到该请求上,并且一旦 FLUSH 请求完成,我们就会记录所有 WRITE 操作,然后记录 FLUSH。为了模拟断电的最坏情况,只有在发出 REQ_PREFLUSH 时已经完成的 WRITE 操作才会被添加。考虑以下示例(W 表示写入,C 表示完成)
W1,W2,W3,C3,C2,Wflush,C1,Cflush
日志将显示以下内容
W3,W2,flush,W1....
再次强调,这是为了模拟磁盘上的实际情况,这使我们能够检测到在特定时间点断电可能导致文件系统不一致的情况。
任何 REQ_FUA 请求都会绕过此刷新机制,并在完成后立即记录,因为这些请求显然会绕过设备缓存。
任何 REQ_OP_DISCARD 请求都被视为 WRITE 请求。否则,我们就会有所有的 DISCARD 请求,然后是 WRITE 请求,然后是 FLUSH 请求。考虑以下示例
WRITE 块 1, DISCARD 块 1, FLUSH
如果我们在 DISCARD 完成时记录它,重放将如下所示
DISCARD 1, WRITE 1, FLUSH
这与实际发生的情况不完全一致,并且在日志重放期间不会被发现。
目标接口¶
构造函数
log-writes <dev_path> <log_dev_path>
dev_path
所有 I/O 正常流向的设备。
log_dev_path
日志条目写入的设备。
状态
<#已记录条目> <最高分配扇区>
#已记录条目
已记录条目数
最高分配扇区
最高分配扇区
消息
mark <描述>
您可以使用 dmsetup 消息在日志中设置任意标记。例如,假设您想在每次写入后对文件系统进行 fsck 检查,但首先需要重放到 mkfs 处,以确保我们检查的是合理的文件系统,您可以这样做:
mkfs.btrfs -f /dev/mapper/log dmsetup message log 0 mark mkfs <run test>这将允许您将日志重放到 mkfs 标记处,然后从该点开始重放,并在您想要的间隔进行 fsck 检查。
每个日志的末尾都有一个标记,标签为“dm-log-writes-end”。
用户空间组件¶
有一个用户空间工具可以以各种方式为您重放日志。它可以在这里找到:https://github.com/josefbacik/log-writes
使用示例¶
假设您想测试文件系统上的 fsync。您可以这样做:
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<some test that does fsync at the end>
dmsetup message log 0 mark fsync
md5sum /mnt/btrfs-test/foo
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark fsync
mount /dev/sdb /mnt/btrfs-test
md5sum /mnt/btrfs-test/foo
<verify md5sum's are correct>
Another option is to do a complicated file system operation and verify the file
system is consistent during the entire operation. You could do this with:
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<fsstress to dirty the fs>
btrfs filesystem balance /mnt/btrfs-test
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark mkfs
btrfsck /dev/sdb
replay-log --log /dev/sdc --replay /dev/sdb --start-mark mkfs \
--fsck "btrfsck /dev/sdb" --check fua
这将重放日志直到它看到一个 FUA 请求,运行 fsck 命令,如果 fsck 通过,它将重放到下一个 FUA,直到完成或者 fsck 命令异常退出。