麒麟软件产品专区:https://product.kylinos.cn
开发者专区:https://developer.kylinos.cn
文档中心:https://documentkylinos.cn
dd现象
使用银河麒麟高级服务器操作系统执行两次dd用例后,表现不一致:
命令用例:/usr/bin/time dd if=/dev/zero of=/home/d.bin bs=2M count=4096
结果如图:
可以发现,在第二次执行dd时,速率只有412MB/S
现象猜测分析
基于上述的现象,猜测第一次dd没有等待数据完全落盘就结束了;而第二次dd是等待数据落盘之后才结束。
可以做如下复现和测试:
系统版本:
- Kernel:
- 4.19.90-89.11.v2401.ky10.x86_64
- Build:
- Kylin Linux Advanced Server
- release V10 SP3 2403/(Halberd)-x86_64-Build20/20240426
测试步骤:
执行:
- /usr/bin/time dd if=/dev/zero of=/home/d.bin bs=2M count=4096
再次执行:
- /usr/bin/time dd if=/dev/zero of=/home/d.bin bs=2M count=4096
可复现这个结果:
图1
分析和实验
删除d.bin后rm -rf d.bin,执行:
- strace -ttt -T -o strace_output_0.txt dd if=/dev/zero of=/home/d.bin bs=2M count=4096
- strace -ttt -T -o strace_output_1.txt dd if=/dev/zero of=/home/d.bin bs=2M count=4096
将生成两次对dd命令的系统调用报告,分析这两个output报告文件,对比close操作时,可以发现,第一次的dd,close耗时短,第二次的dd,close耗时更长
图2 strace系统调用对比
左边是第一次,close操作用不到1秒;
右边是第二次,close操作用了6秒,对比图1结果,第二次与第一次时间差接近6秒。
测试说明这个close()系统调用操作是导致第二次dd慢的原因。问题要从close调用的方向去分析。
根因分析
dd if=/dev/zero of=4G bs=1M count=4096在执行时,会以O_WRONLY|O_CREAT|O_TRUNC的方式打开文件,首次执行时,文件不存在则创建;
第二次执行时O_TRUNC就会发挥作用了,它会先把文件长度给TRUNC到0,然后再写数据;(这是dd程序本身执行的逻辑决定的)
而xfs文件系统,为了数据完整性,对这个这种情况做了加强------即:再检测到这种情况下,会在文件close时触发数据下刷,而且是同步的,就是导致dd性能降低的原因。
- 第一次运行时,由于目标文件/home/d.bin不存在,属于纯粹的内存操作,没有实际的磁盘IO开销。
- 第二次运行时,由于目标文件已存在且被写入了数据,而需要/dev/zero真实读取数据,并将数据写入磁盘文件。这就引入了额外的磁盘IO操作,成为了新的性能瓶颈。
- cpu占用率的差异就是由于落盘操作造成的,第一次dd时,都是操作的内存所以cpu占用率高;
- 第二次dd由于数据落盘会有IO等待,cpu占用率就会降下来。
验证猜测
使用watch监测页内存回写到硬盘
使用两个终端,并进行测试与观测,第一次dd
|---------------------------------------------------------------------------------------------------------------|---------------------------------|
| 1. rm -rf d.bin 2. echo 3 > /proc/sys/vm/drop_caches 3. time dd if=/dev/zero of=/home/d.bin bs=2M count=4096 | 1. watch -n 1 cat /proc/meminfo |
图三 第一次dd 的数据回写观测
第二次dd
|-----------------------------------------------------------------------------------------------|------------------------------------|
| 1. echo 3 > /proc/sys/vm/drop_caches 2. time dd if=/dev/zero of=/home/d.bin bs=2M count=4096 | 1. watch -n 1 cat /proc/meminfo 2. |
图4 第二次dd的数据回写观测
可以发现,第一次dd并没有writeback回写,即:没有从页内存往硬盘落数据,通过ls看到的d.bin的文件数据,是在内存中,只是因为有这个"机制"存在,让用户感觉这个数据落盘了;
第二次dd,由于出现了脏页(因为d.bin存在导致缓存数据不同),内核进行了数据落盘的操作,通过watch命令,观测到了在dd过程中,writeback就有数据流量。
使用sar观测I/O
|---------------------------------------------------------|-------------|
| 1. time dd if=/dev/zero of=/home/d.bin bs=2M count=4096 | 1. sar -d 1 |
第一次dd的情况
图5 第一次dd的I/O情况
第二次dd的情况
图6 第二次dd的I/O情况
可以看到,第二次的磁盘I/O操作比第一次dd多很多,说明有很多I/O落盘的操作,佐证页缓存机制在工作。
如何让dd观测结果稳定?
优化dd测试方法
既然dd的运行逻辑决定了最后观测的结果,那么可以通过指定一些参数,来更准确 的进行测试:
通过添加ofag=direct参数,不走系统缓存
测试命令和观测结果如下:
- [root@localhost home]# rm -rf d.bin
- [root@localhost home]# time dd if=/dev/zero of=/home/d.bin bs=2M count=4096 oflag=direct
- 记录了4096+0 的读入
- 记录了4096+0 的写出
- 8589934592字节(8.6 GB,8.0 GiB)已复制,11.2041 s,767 MB/s
- real0m11.205s
- user0m0.003s
- sys0m0.694s
- [root@localhost home]# time dd if=/dev/zero of=/home/d.bin bs=2M count=4096 oflag=direct
- 记录了4096+0 的读入
- 记录了4096+0 的写出
- 8589934592字节(8.6 GB,8.0 GiB)已复制,11.4382 s,751 MB/s
- real0m11.443s
- user0m0.000s
- sys0m0.700s
- [root@localhost home]#
图7 dd添加参数后
可以看到,在添加oflag=direct参数后,dd命令不管是第几次都稳定了;
通过观察页内存,可以发现,直接写时,数据落盘,没有通过脏页内存回写。
通过添加conv=notrunc方式,不截断输出文件
默认情况下,如果输出文件已经存在,dd命令会首先将其截断为0字节大小。然后再将输入数据写入该输出文件。
当指定conv=notrunc参数时,dd命令将保留输出文件已有的数据,而只是从输出文件的当前文件结尾处开始覆盖写入数据。
删除生成的d.bin文件
如果每次测试前,都删除d.bin文件,那么每次dd都是通过内存出来,速率也可以稳定
为什么FreeBSD的表现和Linux不同?
freebsd上使用zfs文件系统,而linux上使用了xfs文件系统,他们在数据下刷机制上也存在一些差异:
对于 XFS,代码中的 filemap_flush(VFS_I(ip)->i_mapping) 实际上是调用 VFS(虚拟文件系统)层的通用页高速缓存写入磁盘的操作。
具体来说,它会遍历给定映射(mapping)关联的所有脏页,将这些脏页写入对应的后备存储设备(通常是磁盘)。这个过程是同步执行的,即该函数直到所有脏页都被写入磁盘才会返回。
以上是XFS文件系统特有实现,而zfs实现不同,所以不存在同样的问题。
cp现象
在d6.bin存在的情况下,使用cp d.bin d6.bin进行拷贝,如果使用了truncate -s 0 d6.bin,会使拷贝速度急速上升的。为什么?
现象分析
cp a b慢
truncate -s 0 b; cp a b快
直接cp a b时对b文件打开是带O_TRUNC标志(d6.bin已存在),就会在close的时候进行同步下刷,速度就不会快;
而truncate -s 0 b; cp a b,cp对b的打开同样是带O_TRUNC标志的,但是由于在cp之前,执行了truncate的操作,根据xfs文件系统落盘的机制,在cp的时候O_TRUNC就不起作用了,也就是在close的时候不会进行同步下刷;=》内核会对同样的两次操作进行优化,避免做重复的工作。
xfs文件系统源码
先看紫色字的注释:
如果之前对该文件进行了截断操作,删除了旧的文件数据。我们希望在最后关闭该文件时,提前(early)将数据写出到磁盘。这个问题特别容易在以下情况发生:先截断文件,然后通过缓冲区写入(重写)数据(延迟分配delalloc),接着系统崩溃。我们在这里所做的,实际上是大大缩小了可能遇到这个问题的时间窗口。
所谓空文件问题,是指在截断、重写文件数据的过程中,如果系统崩溃,可能导致原有数据被删除,而新写入的数据还未持久化到磁盘,从而使文件内容变为空或不完整。
为了避免这种情况,XFS会在关闭经过截断和重写的文件时,主动提前将缓冲区数据刷新到磁盘。这样可以最大限度减小系统崩溃导致数据丢失的窗口期,从而提高数据完整性和一致性。
执行过程如下:
判断文件系统状态xfs_is_shutdown,执行以下操作:
a. 判断truncated状态:XFS_ITRUNCATED会把XFS_ITRUNCATED复位,返回true;如果flags里面没有XFS_ITRUNCATED置位,返回false;
b. 如果上一步标志被清除(即之前发生了截断 ),调用 xfs_iflags_clear(XFS_IDIRTY_RELEASE) 清除 XFS_IDIRTY_RELEASE 标志。该标志表示需要在卸载时将延迟写入的元数据写入磁盘。
c. 如果上一步标志被清除,调用 filemap_flush(VFS_I(ip)->i_mapping) 将所有延迟的元数据写入磁盘。(数据下刷 )
所以手动执行truncate命令时,变量truncated=false,if(truncated)函数就不会执行,也就没有数据下刷的操作,观测结果"看上去"cp速度很快。
验证
这个情况同样可以用sar观测I/O被证实: