携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情 >>
编写程序write数据到文件中是,数据不会立即写入磁盘,而是会经过层层缓存。缓存是为了加速读写操作,避免频繁的io开销,但是保存在缓存中的数据,遇到宕机或者是别的故障,存在数据丢失的问题。所以操作系统提供了保证文件落盘的机制。
- 文件数据从应用程序写入磁盘,需要经过多个缓冲区:应用本身的缓冲区,库的缓冲区,操作系统缓冲区,磁盘缓冲区
为什么需要缓冲区——减少系统调用的次数
一般的操作:库函数缓存刷新时,会调用write系统调用写入内核空间,内核同样维护了一个页缓存(page cache),操作系统会在合适的时间把脏页的数据写入磁盘。磁盘也可能维护一个缓存,只有写入了磁盘的持久存储物理介质上,数据才真正落盘。
用户空间缓冲区
应用本身的缓冲区
开发者自己刷新,调用库函数写入到库函数的缓冲区中。
库的缓冲区
C标准库的fflush,JDK的OutputStream#flush会强制将缓冲区数据写入操作系统
内核缓冲区
操作系统缓冲区
为什么需要高速缓存或页面高速缓存——减少磁盘读写次数。
用户写入系统的数据先写入系统缓冲区,系统缓冲区写满后,将其排入输出队列,然后得到队首时,才进行实际的IO操作。这种输出方式被称为延迟写。
UNIX系统提供了三个系统调用来执行刷新内核缓冲区:sync,fsync,fdatasync。
-
sync函数只是将所有修改过的块缓冲区排入输出队列就返回,不保证一定落盘。 -
fsync指定文件落盘,堵塞直至返回,可以刷新文件数据+元数据缓冲区 -
fdatasync可以刷新文件数据,在不影响读取的情况下,可以不刷新文件元数据,性能更好一些- 如果更新不修改文件长度,后者比前者能够少一次磁盘写入
除此之外,open打开文件时,可以设置同步相关的标志位O_SYNC和O_DSYNC
O_SYNC标志位可以在每次write后自动调用fsyncO_DSYNC标志位可以在每次write后自动调用fdatasync
补充:
对于新建文件,还需要在父目录上调用fsync
对于覆盖现有文件,最好先tmp,然后写tmp,fsync,rename,最后对父目录调用fsync
存储设备(磁盘)缓冲区
磁盘缓冲区
-
文件系统支持开启/关闭写屏障
write barriers,linux默认开启- 如果开启写屏障,则
fsync/fdatasync可以保证文件写入磁盘的持久化设备中 - 如果关闭写屏障,则
fsync/fdatasync只能保证文件写入磁盘,此时文件可能存在于磁盘的缓存中
- 如果开启写屏障,则