Linux/UNIX编程如何保证文件落盘
本文转载自Linux/UNIX编程如何保证文件落盘
导语
我们编写程序write数据到文件中时,其实数据不会立马写入磁盘,而是会经过层层缓存。每层缓存都有自己的刷新时机,每层缓存都刷新后才会写入磁盘。这些缓存的存在是为了加速读写操作,因为如果每次读写都对应真实磁盘操作,那么读写的效率会大大降低。带来的坏处是如果期间发生掉电或者别的故障,还未写入磁盘的数据就丢失了。对于数据安全敏感的应用,比如数据库,比如交易程序,这是无法忍受的。所以操作系统提供了保证文件落盘的机制。我们来看下这些机制的原理和使用。
I/O缓冲区机制
图片来自:https://lwn.net/Articles/457667/
上图说明了操作系统到磁盘的数据流,以及经过的缓冲区。首先数据会先存在于应用的内存空间,如果调用库函数写入,库函数可能还会把数据缓存在库函数所维护的缓冲区空间中,比如C标准库stdio提供的方法就会进行缓存,目的是为了减少系统调用的次数。这两个缓存都是在用户空间中的。库函数缓存刷新时,会调用write系统调用写入内核空间,内核同样维护了一个页缓存(page cache),操作系统会在合适的时间把脏页的数据写入磁盘。即使是写入磁盘了,磁盘也可能维护了一个缓存,在这个时候掉电依然会丢失数据的,只有写入了磁盘的持久存储物理介质上,数据才是真正的落盘了,是安全的。我们接下来就是要研究如何做到这一步。
用户空间缓冲区
用户空间的缓存分为应用程序本身维护的缓冲区与库维护的缓冲区。
应用本身维护的缓冲区需要开发者自己刷新,调用库函数写入到库函数的缓冲区中。如果应用程序不依赖任何库函数,而是直接使用系统调用,那么则是把数据写入系统的缓冲区去。
库函数一般都会维护缓冲区,目的是简化应用程序的编写,应用程序就不需要编写维护缓冲区的代码,同时性能也得到了提高,因为缓冲区大大减少了系统调用的次数,而系统调用是非常耗时的,系统调用涉及到用户态到内核态的切换,这个切换需要很多的步骤与校验,较为耗时。
比如C标准库stdio就维护着一个缓冲区,对应这个缓冲区,C标准库提供了fflush
方法强制把缓冲区数据写入操作系统。
Java的OutputStream
接口提供了一个flush
方法,具体的作用要看实现类的具体实现。BufferedOutputStream#flush
就会把自己维护的缓冲区数据写入下一层的OutputStream
。如果是new BufferedOutputStream(new FileOutputStream("/"))
这样的模式,则调用BufferedOutputStream#flush
会将数据写入操作系统。
内核缓冲区
应用程序直接或者通过库函数间接的使用系统调用write
将数据写入操作系统缓冲区。
UNIX系统在内核中设有高速缓存或页面高速缓存。目的是为了减少磁盘读写次数。
用户写入系统的数据先写入系统缓冲区,系统缓冲区写满后,将其排入输出队列
,然后得到队首时,才进行实际的IO操作。这种输出方式被称为延迟写
。
UNIX系统提供了三个系统调用来执行刷新内核缓冲区:sync
,fsync
,fdatasync
。
sync
void sync(void)
sync
函数只是将所有修改过的块缓冲区排入输出队列
就返回,并不等待实际的写磁盘操作返回。
操作系统的update
系统守护进程会周期地调用sync
函数,来保证系统中的数据能定期落盘。
根据sync(2) - Linux manual page的描述,Linux对sync
的实现与POSIX规范不太一样,POSIX规范中,sync
可能在文件真正落盘前就返回,而Linux的实现则是文件真正落盘后才会返回。所以Linux中,sync
与fsync
的效果是一样的!但是1.3.20之前的Linux存在BUG,导致sync并不会在真正落盘后返回。
fsync
void fsync(int filedes)
fsync
对指定的文件起作用,它传输内核缓冲区中这个文件的数据到存储设备中,并阻塞直到存储设备响应说数据已经保存好了。
fsync
对文件数据与文件元数据都有效。文件的元数据可以理解为文件的属性数据,比如文件的更新时间,访问时间,长度等。
fdatasync
void fdatasync(int filedes)
fdatasync
和fsync
类似,两者的区别是,fdatasync
不一定需要刷新文件的元数据部分到存储设备。
是否需要刷新文件的元数据,是要看元数据的变化部分是否对之后的读取有影响,比如文件元数据的访问时间st_atime
和修改时间st_mtime
变化了,fdatasync
不会去刷新元数据数据到存储设备,因为即使这个数据丢失了不一致了,也不影响故障恢复后的文件读取。但是如果文件的长度st_size
变化了,那么就需要刷新元数据数据到存储设备。
所以如果你每次都更新文件长度,那么调用fsync
和fdatasync
的效果是一样的。
但是如果更新能做到不修改文件长度,那么fdatasync
能比fsync
少了一次磁盘写入,这个是非常大的速度提升。
O_SYNC
和O_DSYNC
除了上面三个系统调用,open
系统调用在打开文件时,可以设置和同步相关的标志位:O_SYNC
和O_DSYNC
。
设置O_SYNC
的效果相当于是每次write
后自动调用fsync
。
设置O_DSYNC
的效果相当于是每次write
后自动调用fdatasync
。
关于新建文件
在一个文件上调用fsync
/fdatasync
只能保证文件本身的数据落盘,但是对于文件系统来说,目录中也保存着文件信息,fsync
/fdatasync
的调用并不会保证这部分的数据落盘。如果此时发生掉电,这个文件就无法被找到了。
所以对于新建文件来说,还需要在父目录上调用fsync
。
关于覆盖现有文件
覆盖现有文件时,如果发生掉电,新的数据是不会写入成功,但是可能会污染现有的数据,导致现有数据丢失。
所以最佳实践是新建一个临时文件,写入成功后,再替换原有文件。具体步骤:
- 新建一个临时文件
- 向临时文件写入数据
- 对临时文件调用
fsync
,保证数据落盘。期间发生掉电对现有文件无影响。 - 重命名临时文件为目标文件名
- 对父目录调用
fsync
存储设备缓冲区
存储设备为了提高性能,也会加入缓存。高级的存储设备能提供非易失性的缓存,比如有掉电保护的缓存。但是无法对所有设备做出这种保证,所以如果数据只是写入了存储设备的缓存的话,遇到掉电等故障,依然会导致数据丢失。
对于保证数据能保存到存储设备的持久化存储介质上,而不管设备本身是否有易失性缓存,操作系统提供了write barriers
这个机制。
开启了write barriers
的文件系统,能保证调用fsync
/fdatasync
数据持久化保存,无论是否发生了掉电等其他故障,但是会导致性能下降。
许多文件系统提供了配置write barriers
的功能。比如ext3
, ext4
, xfs
和 btrfs
。mount
参数-o barrier
表示开启写屏障,调用fsync
/fdatasync
能保证刷新存储设备的缓存到持久化介质上。-o nobarrier
则表示关闭写屏障,调用fsync
/fdatasync
无法保证数据落盘。
Linux默认开启写屏障,所以默认情况下,我们调用fsync
/fdatasync
,就可以认为是文件真正的可靠落盘了。
对于这个层面的数据安全保证来说,应用程序是不需要去考虑的,因为如果这台机器的硬盘被挂载为没有开启写屏障,那么可以认为这个管理员知道这个风险,他选择了更高的性能,而不是更高的安全性。
总结
- 文件数据从应用程序写入磁盘,需要经过多个缓冲区:应用本身的缓冲区,库的缓冲区,操作系统缓冲区,磁盘缓冲区
- 如果文件数据只是写入缓冲区,而还未写入硬盘的持久化存储设备上,那么断电等故障会导致数据丢失
- 库层面刷新缓冲区:C标准库的
fflush
,JDK的OutputStream#flush
- 操作系统层面刷新缓冲区:
fsync
可以刷新文件数据+元数据缓冲区fdatasync
可以刷新文件数据,在不影响读取的情况下,可以不刷新文件元数据,性能更好一些open
系统调用的O_SYNC
标志位可以在每次write
后自动调用fsync
open
系统调用的O_DSYNC
标志位可以在每次write
后自动调用fdatasync
- 存储设备层面刷新缓冲区:文件系统支持开启/关闭写屏障
write barriers
,如果开启写屏障,则fsync
/fdatasync
可以保证文件写入磁盘的持久化设备中,如果关闭写屏障,则fsync
/fdatasync
只能保证文件写入磁盘,此时文件可能存在于磁盘的缓存中
参考资料
- 《UNIX环境高级编程》
- Ensuring data reaches disk
- linux 同步IO: sync、fsync与fdatasync - CSDN博客
- sync(2) - Linux manual page
- fsync(2) - Linux manual page
- sync/fsync/fdatasync的简单比较 - CSDN博客
- Everything You Always Wanted To Know About fsync() - xavier roche’s homework
- Linux OS: Write Barriers - 德哥@Digoal的日志 - 网易博客
- Linux Barrier I/O 实现分析与barrier内存屏蔽分析总结 - 综合编程类其他综合 - 红黑联盟
- Chapter 16. Write Barriers
- [Barriers and journaling filesystems LWN.net]
Linux/UNIX编程如何保证文件落盘的更多相关文章
- Java如何保证文件落盘?
本文转载自Java如何保证文件落盘? 导语 在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失.JDK也封装了对 ...
- Linux/UNIX编程:使用C语言实现简单的 ls 命令
刚好把 Linux/UNIX 编程中的文件和IO部分学完了,就想编写个 ls 命令练习一下,本以为很简单,调用个 stat 就完事了,没想到前前后后弄了七八个小时,90%的时间都用在格式化(像 ls ...
- 学习linux/unix编程方法的建议(转)
假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux ...
- 学习linux/unix编程方法的建议,学习Linux的四个步骤(转)
解答:学习Linux的四个步骤假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过我想大概可以分为4个阶段,水平从低到高从安装使用=>linux ...
- 在linux/unix中查找大文件
在linux/unix中查找大文件,如查找大于100M文件的位置路径,查找等于10M文件的位置路径等等,下面就介绍几个实现快速查找的命令: 1. 查找指定目录下所有大于100M的文件,命令为 find ...
- Linux系统:保证数据安全落盘
在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer来加速 ...
- Linux:保证数据安全落盘
背景 在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer ...
- Linux/Unix编程中的线程安全问题【转】
转自:http://blog.csdn.net/zhengzhoudaxue2/article/details/6432984 在目前的计算机科学中,线程是操作系统调度的最小单元,进程是资源分配的最小 ...
- [linux]Socket编程的头文件
socket编程中需要用到的头文件 sys/types.h:数据类型定义 sys/socket.h:提供socket函数及数据结构 netinet/in.h:定义数据结构sockaddr_in arp ...
随机推荐
- 详解Java8特性之新的日期时间 API
详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...
- Pythonchallenge1过关攻略
第一关上来是一个电视,上面写着2^38,这就非常关键了,这时候我们已经有了大致思路,再看一眼电视机下面的话确认一下,"Hint: try to change the URL address. ...
- hdu1517 A Multiplication Game
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission ...
- Codeforces Round #552 (Div. 3) C. Gourmet Cat (数学,模拟)
题意:你要带着你的喵咪一起去旅行,你的喵在星期\(1,4,7\)吃喵粮\(x\),在星期\(2,6\)吃喵粮\(y\),在星期\(3,5\)吃喵粮\(z\),你只有\(a\)个\(x\),\(b\)个 ...
- Codeforces Round #658 (Div. 2) C1. Prefix Flip (Easy Version) (构造)
题意:给你两个长度为\(n\)的01串\(s\)和\(t\),可以选择\(s\)的前几位,取反然后反转,保证\(s\)总能通过不超过\(3n\)的操作得到\(t\),输出变换总数,和每次变换的位置. ...
- 8.PowerShell DSC之Push
前言 LCM的默认mode就是push,所以对于push模式,我们直接就三步走 以下是示例: 1.编写配置 Authoring Configuration WebsiteTest { # Import ...
- 数据可视化 -- Python
前提条件: 熟悉认知新的编程工具(jupyter notebook) 1.安装:采用pip的方式来安装Jupyter.输入安装命令pip install jupyter即可: 2.启动:安装完成后,我 ...
- 5.2 spring5源码--spring AOP源码分析三---切面源码分析
一. AOP切面源码分析 源码分析分为三部分 1. 解析切面 2. 创建动态代理 3. 调用 源码的入口 源码分析的入口, 从注解开始: 组件的入口是一个注解, 比如启用AOP的注解@EnableAs ...
- 4.安装fluentd用于收集集群内部应用日志
作者 微信:tangy8080 电子邮箱:914661180@qq.com 更新时间:2019-06-13 11:02:14 星期四 欢迎您订阅和分享我的订阅号,订阅号内会不定期分享一些我自己学习过程 ...
- 蓝桥杯-摔手机问题【dp】
非常详细的题解:戳这里 例题:poj-3783 Balls Balls Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 115 ...