通过本文你会清楚知道 fsync()、fdatasync()、sync()、O_DIRECT、O_SYNC、REQ_PREFLUSH、REQ_FUA的区别和作用。
fsync() fdatasync() sync() 是什么?
首先它们是系统调用。
fsync
fsync(int fd) 系统调用把打开的文件描述符fd相关的所有缓冲元数据和数据与都刷新到磁盘上(non-volatile storage)
fdatasync
fdatasync(int fd) 类似fsync,但不flush元数据,除非元数据影响后面读数据。比如文件修改时间元数据变了就不会刷,而文件大小变了影响了后面对该文件的读取,这个会一同刷下去。所以fdatasync的性能要比fsync好。
sync
sync(void) 系统调用会使包含更新文件的所有内核缓冲区(包含数据块、指针块、元数据等)都flush到磁盘上。
O_DIRECT O_SYNC REQ_PREFLUSH REQ_FUA 是什么?
它们都是flag,可能最终的效果相同,但它们在不同的层面上。
O_DIRECT O_SYNC是系统调用open的flag参数,REQ_PREFLUSH REQ_FUA 是kernel bio的flag参数。
要理解这几个参数要需要知道两个页缓存:
一个是你的内存,free -h可以看到的buff/cache;
另外一个是硬盘自带的page cache。
一个io写盘的简单流程如下:
可以对比linux storage stack diagram:
O_DIRECT
O_DIRECT 表示io不经过系统缓存,这可能会降低你的io性能。它同步传输数据,但不保证数据安全。
备注:后面说的数据安全皆表示数据被写到磁盘的non-volatile storage
通过dd命令可以清楚看到 O_DIRECT和非O_DIRECT区别,注意buff/cache的变化:
#清理缓存:
#echo 3 > /proc/sys/vm/drop_caches
#free -h
total used free shared buff/cache available
Mem: 62G 1.1G 61G 9.2M 440M 60G
Swap: 31G 0B 31G
#dd without direct
#dd if=/dev/zero of=/dev/bcache0 bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 27.8166 s, 38.6 MB/s
#free -h
total used free shared buff/cache available
Mem: 62G 1.0G 60G 105M 1.5G 60G
Swap: 31G 0B 31G
#echo 3 > /proc/sys/vm/drop_caches
#free -h
total used free shared buff/cache available
Mem: 62G 626M 61G 137M 337M 61G
Swap: 31G 0B 31G
#dd with direct
#dd if=/dev/zero of=/dev/bcache0 bs=1M count=1024 oflag=direct
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 2.72088 s, 395 MB/s
#free -h
total used free shared buff/cache available
Mem: 62G 628M 61G 137M 341M 61G
Swap: 31G 0B 31G
O_SYNC
O_SYNC 同步io标记,保证数据安全写到non-volatile storage
REQ_PREFLUSH
REQ_PREFLUSH 是bio的request flag,表示在本次io开始时先确保在它之前完成的io都已经写到非易失性存储里。
我理解REQ_PREFLUSH之确保在它之前完成的io都写到非易失物理设备,但它自己可能是只写到了disk page cache里,并不确保安全。
可以在一个空的bio里设置REQ_PREFLUSH,表示回刷disk page cache里数据。
REQ_FUA
REQ_FUA 是bio的request flag,表示数据安全写到非易失性存储再返回
实验验证
重新编译了bcache内核模块,打印出bio->bi_opf,opf是bio的 operation flag,它有req flag or组成,决定io的行为。
request flag
#from linux source code include/linux/blk_types.h
enum req_opf {
/* read sectors from the device */
REQ_OP_READ = 0,
/* write sectors to the device */
REQ_OP_WRITE = 1,
/* flush the volatile write cache */
REQ_OP_FLUSH = 2,
/* discard sectors */
REQ_OP_DISCARD = 3,
/* securely erase sectors */
REQ_OP_SECURE_ERASE = 5,
/* reset a zone write pointer */
REQ_OP_ZONE_RESET = 6,
/* write the same sector many times */
REQ_OP_WRITE_SAME = 7,
/* reset all the zone present on the device */
REQ_OP_ZONE_RESET_ALL = 8,
/* write the zero filled sector many times */
REQ_OP_WRITE_ZEROES = 9,
/* SCSI passthrough using struct scsi_request */
REQ_OP_SCSI_IN = 32,
REQ_OP_SCSI_OUT = 33,
/* Driver private requests */
REQ_OP_DRV_IN = 34,
REQ_OP_DRV_OUT = 35,
REQ_OP_LAST,
};
enum req_flag_bits {
__REQ_FAILFAST_DEV = /* 8 no driver retries of device errors */
REQ_OP_BITS,
__REQ_FAILFAST_TRANSPORT, /* 9 no driver retries of transport errors */
__REQ_FAILFAST_DRIVER, /* 10 no driver retries of driver errors */
__REQ_SYNC, /* 11 request is sync (sync write or read) */
__REQ_META, /* 12 metadata io request */
__REQ_PRIO, /* 13 boost priority in cfq */
__REQ_NOMERGE, /* 14 don't touch this for merging */
__REQ_IDLE, /* 15 anticipate more IO after this one */
__REQ_INTEGRITY, /* 16 I/O includes block integrity payload */
__REQ_FUA, /* 17 forced unit access */
__REQ_PREFLUSH, /* 18 request for cache flush */
__REQ_RAHEAD, /* 19 read ahead, can fail anytime */
__REQ_BACKGROUND, /* 20 background IO */
__REQ_NOWAIT, /* 21 Don't wait if request will block */
__REQ_NOWAIT_INLINE, /* 22 Return would-block error inline */
.....
}
bio->bi_opf 对照表
下面测试时候需要用到,可以直接跳过,有疑惑的时候回来查看。
|十进制flag |十六进制flag|REQ_FLG|
| --- | --- | --- |
|2409 | 1000 0000 0001 | REQ_OP_WRITE | REQ_SYNC|
|4096 | 0001 0000 0000 0000 | REQ_META | REQ_READ|
|34817 | 1000 1000 0000 0001 | REQ_OP_WRITE | REQ_SYNC | REQ_IDLE |
|399361 | 0110 0001 1000 0000 0001 | REQ_OP_WRITE | REQ_SYNC | REQ_META | REQ_FUA | REQ_PREFLUSH |
|264193 | 0100 0000 1000 0000 0001| REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH |
|165889 | 0010 1000 1000 0000 0001 | REQ_OP_WRITE | REQ_SYNC | REQ_IDLE | REQ_FUA |
|1048577| 0001 0000 0000 0000 0000 0001 | REQ_OP_WRITE | REQ_BACKGROUND |
|0010 0000 1000 0000 0001| REQ_OP_WRITE | REQ_SYNC | REQ_FUA|
测试
用测试工具dd对块设备/dev/bcache0直接测试。
笔者通过dd源码已确认:oflag=direct表示文件已O_DIRECT打开,oflag=sync 表示已O_SYNC打开,conv=fdatasync表示dd结束后会发送一个fdatasync(fd), conv=fsync表示dd结束后会发送一个fsync(fd)
- direct
#dd if=/dev/zero of=/dev/bcache0 oflag=direct bs=8k count=1
//messages
kernel: bcache: cached_dev_make_request() bi_opf 34817, size 8192
bi_opf 34817, size 8192:bi_opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE,是一个同步写,可以看到不保证数据安全
- direct & sync
#dd if=/dev/zero of=/dev/bcache0 oflag=direct,sync bs=8k count=1
kernel: bcache: cached_dev_make_request() bi_opf 165889, size 8192
kernel: bcache: cached_dev_make_request() bi_opf 264193, size 0
bi_opf 165889, size 8192: bi_opf=REQ_OP_WRITE | REQ_SYNC | REQ_IDLE | REQ_FUA ,是一个同步写请求,并且该io直接写到硬盘的non-volatile storage
bi_opf 264193, size 0: bi_opf= REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH ,size = 0,表示回刷disk的page cache保证以前写入的io都刷到non-volatile storage
通过这个可以理解O_SYNC为什么可以保证数据安全。
#dd if=/dev/zero of=/dev/bcache0 oflag=direct,sync bs=8k count=2
kernel: bcache: cached_dev_make_request() iop_opf 165889, size 8192
kernel: bcache: cached_dev_make_request() iop_opf 264193, size 0
kernel: bcache: cached_dev_make_request() iop_opf 165889, size 8192
kernel: bcache: cached_dev_make_request() iop_opf 264193, size 0
写两个io到设备,从上面可以看到以O_SYNC打开文件,每个write都会发送一个flush请求,这对性能的影响比较大,所以在一般实现中不已O_SYNC打开文件,而是在几个io结束后调用一次fdatasync。
- without direct
#dd if=/dev/zero of=/dev/bcache0 bs=8k count=1
kernel: bcache: cached_dev_make_request() bi_opf 2049, size 4096
kernel: bcache: cached_dev_make_request() bi_opf 2049, size 4096
bi_opf 2049, size 4096:bi_opf = REQ_OP_WRITE | REQ_SYNC,同步写请求,可以看到原先8k的io在page cache里被拆成了2个4k的io写了下来,不保证数据安全
- direct & fdatasync
#dd if=/dev/zero of=/dev/bcache0 oflag=direct conv=fdatasync bs=8k count=1
kernel: bcache: cached_dev_make_request() bi_opf 34817, size 8192
kernel: bcache: cached_dev_make_request() bi_opf 264193, size 0
bi_opf 34817, size 8192:bi_opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE, 同步写请求,这个不保证数据安全
bi_opf 264193, size 0:bi_opf = REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH,一个disk page cache 回刷请求,这个就是fdatasync()下发的。
- direct & sync & fdatasync
#dd if=/dev/zero of=/dev/bcache0 oflag=direct,sync conv=fdatasync bs=8k count=1
kernel: bcache: cached_dev_make_request() bi_opf 165889, size 8192
kernel: bcache: cached_dev_make_request() bi_opf 264193, size 0
kernel: bcache: cached_dev_make_request() bi_opf 264193, size 0
bi_opf 165889, size 8192: bi_opf=REQ_OP_WRITE | REQ_SYNC | REQ_IDLE | REQ_FUA ,是一个同步写请求,并且该io直接写到硬盘的non-volatile storage
bi_opf 264193, size 0: bi_opf= REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH ,size = 0,表示回刷disk的page cache保证以前写入的io都刷到non-volatile storage
结合上面的分析,这三个bio其实就是一个写io,两个flush io,分别由O_SYNC和fdatasync触发。
- direct & fsync
#dd if=/dev/zero of=/dev/bcache0 oflag=direct conv=fsync bs=8k count=1
kernel: bcache: cached_dev_make_request() bi_opf 34817, size 8192
kernel: bcache: cached_dev_make_request() bi_opf 264193, size 0
同direct + fdatasync,应该是写的是一个块设备,没有元数据或元数据没有变化,所以fdatasync和fsync收到的bio是一样的
彩蛋
如何打开关闭硬盘缓存
查看当前硬盘写Cache状态
#hdparm -W /dev/sda
/dev/sda:
write-caching = 1 (on)
关闭硬盘的写Cache
#hdparm -W 0 /dev/sda
打开硬盘的写Cache
#hdparm -W 1 /dev/sda