函数sync、fsync与fdatasync的总结整理(必看篇)
⼀、术语解释
脏页:linux内核中的概念,因为硬盘的读写速度远赶不上内存的速度,系统就把读写⽐较频繁的数据事先放到内存中,以提⾼读写速度,这就叫⾼速缓存,linux是以页作为⾼速缓存的单位,当进程修改了⾼速缓存⾥的数据时,该页就被内核标记为脏页,内核将会在合适的时间把脏页的数据写到磁盘中去,以保持⾼速缓存中的数据和磁盘中的数据是⼀致的。
内存映射:内存映射⽂件,是由⼀个⽂件到⼀块内存的映射。Win32提供了允许应⽤程序把⽂件映射到⼀个进程的函数(CreateFileMapping)。内存映射⽂件与虚拟内存有些类似,通过内存映射⽂件可以保留⼀个地址空间的区域,同时将物理存储器提交给此区域,内存⽂件映射的物理存储器来⾃⼀个已经存在于磁盘上的⽂件,⽽且在对该⽂件进⾏操作之前必须⾸先对⽂件进⾏映射。使⽤内存映射⽂件处理存储于磁盘上的⽂件时,将不必再对⽂件执⾏I/O操作,使得内存映射⽂件在处理⼤数据量的⽂件时能起到相当重要的作⽤。
//摘录⾃百度百科
延迟写(delayed write): 传统的UNIX实现在内核中设有缓冲区⾼速缓存或页⾯⾼速缓存,⼤多数磁盘I/O
都通过缓冲进⾏。当将数据写⼊⽂件时,内核通常先将该数据复制到其中⼀个缓冲区中,如果该缓冲区尚未写满,则并不将其排⼊输出队列,⽽是等待其写满或者当内核需要重⽤该缓冲区以便存放其他磁盘块数据时,再将该缓冲排⼊到输出队列,然后待其到达队⾸时,才进⾏实际的I/O操作。这种输出⽅式就被称为延迟写。
//摘录⾃《UNIX环境⾼级编程第三版》P65
⼆、正⽂
延迟写减少了磁盘读写次数,但是却降低了⽂件内容的更新速度,使得欲写到⽂件中的数据在⼀段时间内并没有写到磁盘上。当系统发⽣故障时,这种延迟可能造成⽂件更新内容的丢失。为了保证磁盘上实际⽂件系统与缓冲区⾼速缓存中内容的⼀致性,UNIX系统提供了sync、fsync和fdatasync三个函数。
1、sync函数
sync函数只是将所有修改过的块缓冲区排⼊写队列,然后就返回,它并不等待实际写磁盘操作结束。
通常称为update的系统守护进程会周期性地(⼀般每隔30秒)调⽤sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调⽤sync函数。
2、fsync函数
fsync函数只对由⽂件描述符filedes指定的单⼀⽂件起作⽤,并且等待写磁盘操作结束,然后返回。
fsync可⽤于数据库这样的应⽤程序,这种应⽤程序需要确保将修改过的块⽴即写到磁盘上。
3、fdatasync函数
fdatasync函数类似于fsync,但它只影响⽂件的数据部分。⽽除数据外,fsync还会同步更新⽂件的属性。
对于提供事务⽀持的数据库,在事务提交时,都要确保事务⽇志(包含该事务所有的修改操作以及⼀个提交记录)完全写到硬盘上,才认定事务提交成功并返回给应⽤层。
4、fflush:标准IO函数(如fread,fwrite等)会在内存中建⽴缓冲,该函数刷新内存缓冲,将内容写⼊内核缓冲,要想将其真正写⼊磁盘,还需要调⽤fsync。(即先调⽤fflush然后再调⽤fsync,否则不会起作⽤)。fflush以指定的⽂件流描述符为参数(对应以fopen等函数打开的⽂件流),仅仅是把上层缓冲区中的数据刷新到内核缓冲区就返回,
因此相对于fsync⽽⾔不是很安全,还需要再调⽤⼀下fsync来把数据真正写⼊硬盘。使⽤函数
int fileno(FILE *stream);
把⽂件流描述符(fp)转换为⽂件描述符(fd),以⽅便fsync的调⽤,那么,在Linux操作系统上,怎样才能保证数据被正确地写⼊外部永久存储介质?
1. write不能满⾜要求,需要fsync
对于write函数,我们认为该函数⼀旦返回,数据便已经写到了⽂件中。但是这种概念只是宏观上的,⼀般情况下,对硬盘(或者其他持久存储设备)⽂件的write操作,更新的只是内存中的页缓存(page cache),⽽脏页不会⽴即更新到硬盘中,⽽是由操作系统统⼀调度,如flusher内核线程在满⾜⼀定条件时(⼀定时间间隔、内存中
的脏页达到⼀定⽐例)将脏页⾯同步到硬盘上(放⼊设备的IO请求队列)。因为write调⽤不会等到硬盘IO完成之后才返回,
设想如果操作系统在write调⽤之后、硬盘同步之前崩溃,则数据可能丢失。虽然这样的时间窗⼝很⼩,但是对于需要保证事务的持久化(durability)和⼀致性(consistency)的数据库程序来说,write()所提供的“松散的异步语义”是不够的,通常需要操作系统提供的同步IO(synchronized-IO)原语来保证:
函数原型:
int fsync(int fd);
fsync的功能是确保⽂件fd所有已修改的内容已经正确同步到硬盘上,该调⽤会阻塞等待直到设备报告IO完成。
PS:如果采⽤内存映射⽂件的⽅式进⾏⽂件IO(使⽤mmap,将⽂件的page cache直接映射到进程的地址空间,通过写内存的⽅式修改⽂件),也有类似的系统调⽤来确保修改的内容完全同步到硬盘之上:
#incude <sys/mman.h>
int msync(void *addr, size_t length, int flags)
msync需要指定同步的地址区间,如此细粒度的控制似乎⽐fsync更加⾼效(因为应⽤程序通常知道⾃⼰的脏页位置),但实际上(Linux)kernel中有着⼗分⾼效的数据结构,能够很快地出⽂件的脏页,使得fsync只会同步⽂件的修改内容。
2. fsync与fdatasync区别
除了同步⽂件的修改内容(脏页),fsync还会同步⽂件的描述信息(metadata,包括size、访问时间等等),因为⽂件的数据和metadata通常存在硬盘的不同地⽅,因此fsync⾄少需要两次IO写操作,多余的⼀次IO操作,根据Wikipedia的数据,当前硬盘驱动的平均寻道时间(Average seek time)⼤约是3~15ms,7200RPM硬盘的平均旋转延迟(Average rotational latency)⼤约为4ms,因此⼀次IO操作
的耗时⼤约为10ms左右。Posix同样定义了fdatasync,放宽了同步的语义以提⾼性能:
int fdatasync(int fd);
fdatasync的功能与fsync类似,但是仅仅在必要的情况下才会同步,因此可以减少⼀次IO写操作。
"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."
举例来说,⽂件的尺⼨(st_size)如果变化,是需要⽴即同步的,否则OS⼀旦崩溃,即使⽂件的数据部分已同步,由于metadata没有同步,依然读不到修改的内容。⽽最后访问时间(atime)/修改时间(mtime)是不需要每次都同步的,只要应⽤程序对这两个时间戳没有苛刻的要求,基本没有问题。
补充:函数open的参数O_SYNC/O_DSYNC有着和fsync/fdatasync类似的含义:使每次write都会阻塞等待硬盘IO完成。
O_SYNC 使每次write等待物理I/O操作完成,包括由write操作引起的⽂件属性更新所需的I/O。
O_DSYNC 使每次write等待物理I/O操作完成,但是如果该写操作并不影响读取刚写⼊的数据,则不需等待⽂件属性被更新。注意区别:
O_DSYNC和O_SYNC标志有微妙的区别:
仅当⽂件属性需要更新以反映⽂件数据变化(例如,更新⽂件⼤⼩以反映⽂件中包含了更多数据)时,O_DSYNC标志才影响⽂件属性。⽽设置O_SYNC标志后,数据和属性总是同步更新。当⽂件⽤O_DSYN标志打开,在重写其现有的部分内容时,⽂件时间属性不会同步更新。于此相反,⽂件如果是⽤O_SYNC标志打开的,那么对于该⽂件的每⼀次write都将在write返回前更新⽂件时间,这与是否改写现有字节或追加⽂件⽆关。相对于fsync/fdatasync,这样的设置不够灵活,应该很少使⽤。
3. 使⽤fdatasync优化⽇志同步
为了满⾜事务要求,数据库的⽇志⽂件是常常需要同步IO的。由于需要同步等待硬盘IO完成,所以事务的提交操作常常⼗分耗时,成为性能的瓶颈。在Berkeley DB下,如果开启了AUTO_COMMIT(所有独⽴的写操作⾃动具有事务语义)并使⽤默认的同步级别(⽇志完全同步到硬盘才返回),写⼀条记录的耗时⼤约为5~10ms级别,基本和⼀次IO操作(10ms)的耗时相同。
我们已经知道,在同步上fsync是低效的。但是如果需要使⽤fdatasync减少对metadata的更新,则需要确保⽂件的尺⼨在write 前后没有发⽣变化。⽇志⽂件天⽣是追加型(append-only)的,总是在不断增⼤,似乎很难利⽤好fdatasync。
Berkeley DB是处理⽇志⽂件的步骤:
1.每个log⽂件固定为10MB⼤⼩,从1开始编号,名称格式为“log.%010d"
2.每次log⽂件创建时,先写⽂件的最后1个page,将log⽂件扩展为10MB⼤⼩
3.向log⽂件中追加记录时,由于⽂件的尺⼨不发⽣变化,使⽤fdatasync可以⼤⼤优化写log的效率
4.如果⼀个log⽂件写满了,则新建⼀个log⽂件,也只有⼀次同步metadata的开销
三、总结
1、如果是对所有的缓冲区发出写硬盘的命令,应该使⽤sync函数,但应该注意该函数仅仅只是把该命令放⼊队列就返回了,在编程时需要注意。
2、如果是要把⼀个已经打开的⽂件所做的修改提交到硬盘,应调⽤fsync函数,该函数会在数据实际写⼊硬盘后才返回,因此是最安全最可靠的⽅式。
3、如果是针对⼀个已经打开的⽂件流操作,则应该⾸先调⽤fsync函数把修改同步到内核缓冲区,然后再调⽤fsync把修改真正的同步到硬盘。
四、附man⼿册关于fsync,fdatasync部分
fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device (or other permanent storage device) so that all changed information can be retrieved even after the sys‐
tem crashed or was rebooted. This includes writing through or flushing a disk cache if present. The call blocks until the device
reports that the transfer has completed. It also flushes metadata information associated with the file (see stat(2)).
Calling fsync() does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync() on a file descriptor for the directory is also needed.
fdatasync() is similar to fsync(), but does not flush modified metadata unless that metadata is needed in order to allow a subsequent
data retrieval to be correctly handled. For example, changes to st_atime or st_mtime (respectively, time of last access and time of last
modification; see stat(2)) do not require flushing because they are not necessary for a subsequent dat
a read to be handled correctly. On
the other hand, a change to the file size (st_size, as made by say ftruncate(2)), would require a metadata flush.
The aim of fdatasync() is to reduce disk activity for applications that do not require all metadata to be synchronized with the disk.
Linux、unix在内核中设有缓冲区、⾼速缓冲或页⾯⾼速缓冲,⼤多数磁盘I/O都通过缓冲进⾏,采⽤延迟写技术。
sync:将所有修改过的快缓存区排⼊写队列,然后返回,并不等待实际写磁盘操作结束;
fsync:只对有⽂件描述符制定的单⼀⽂件起作⽤,并且等待些磁盘操作结束,然后返回;
fdatasync:类似fsync,但它只影响⽂件的数据部分。fsync还会同步更新⽂件的属性;
fopen和open区别fflush:标准I/O函数(如:fread,fwrite)会在内存建⽴缓冲,该函数刷新内存缓冲,将内容写⼊内核缓冲,要想将其写⼊磁盘,还需要调⽤fsync。(先调⽤fflush后调⽤fsync,否则不起作⽤)。
以上就是⼩编为⼤家带来的函数sync、fsync与fdatasync的总结整理(必看篇)全部内容了,希望⼤家多多⽀持~

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。