Linux 数据回写

1、为什么我 fwrite/write 函数已经返回了,此时掉电或重启后数据会丢失?

问题的根源在于缓存的存在,由于存储设备属于低速设备,直接操作的话会有严重的延迟,所以通常会在 DRAM 上先缓

存一部分数据。而 DRAM 是易失性存储设备,掉电数据就丢了,所以要确保数据固化就要把数据回写

到存储设备上。

2、数据回写有两种方式:1.主动同步回写;2.异步后台回写;

2.1

write 只是把数据提交到内核的 Page Cache(假设没有启用 DIO),要想写到存储设备,必须通过

fdatasync 或 fsync 系统调用。

2.2

C 库也有一层文件缓存,所以掉电是因为数据还在这一层缓存中,熟悉 C 库的同学可能知道

这里要加一个 fflush,其实并不对,fflush 只能保证把缓存回写到下一层,即内核的 Page Cache,依

然需要调用 fsync 再刷到物理存储设备。

总结,如果你的程序需要确保数据立即写回,就需要考虑每一层的缓存,确保每一层都按顺序写回。

讲到这里,那有些人就会有疑问,如果我不需要数据马上写回到物理设备,但是我需要知道数据什么时

候会写回,这就涉及到我前面说的数据异步写回了。

Linux 内核会定时触发,把==已经提交到 Page Cache 的脏数据== 1 写回到物理设备,需要特别注意如果你的数据还在上层的缓存中,例如 Java 或 C 库的缓存中没有刷下来,那自然不会被内核的异步回

写机制写回。大致的异步回写机制步骤如下:

step 1:

step 2: 回写线程会遍历 Page Cache 寻找那些被标记为脏的时间超过 dirty_expire_centisecs 的页

面,并全部回写

step 3:

dirty_background_bytes,如果超过则回写所有脏数据

step 4:

需要注意,在脏数据超过 dirty_ratio 和 dirty_bytes 以后如果继续写数据会自动触发同步回写,即

这一次的 write 会把之前和本次的数据都写回到物理设备再返回。

最后再列一下 Linux 内核回写机制的几个可调阈值,它们都位于/proc/sys/vm 目录:

dirty_writeback_centisecs:

唤醒一次。

dirty_expire_centisecs:脏数据的过期时间,单位是 10ms,默认值是 3000,从 Page 被标记为

脏算起,超过这个时间会被认为是过期数据,所以默认情况下,脏数据量没有超过阈值时,数据要

等 30s 以上才会回写,实际上要考虑唤醒周期的影响,数据最长要等 35s 才会写回到存储设备

dirty_background_ratio & dirty_background_bytes:二者功能一样,前者是百分比(==这里的

基数不是总内存大小,而是可用内存大小,包括可回收的内存==),后者单位是字节,脏数据总

量要超过这个阈值才会全部回写,否则只会写过期数据。这两个是互斥的,写其中一个,另一个会

自动被清 0

dirty_ratio & dirty_bytes:二者功能一样,前者是百分比(这里的基数不是总内存大小,而是可

用内存大小,包括可回收的内存),后者单位是字节,脏数据量超过这个阈值以后会阻塞 write,

确保数据同步写回到存储设备

以上配置参数都可以根据自己的实际需求来调整,例如对于那些视频监控的产品,默认的配置可能

会导致数据累积到非常多才会回写,给存储设备的压力就非常大了,这时候可以减小。

dirty_background_ratio,dirty_writebac-k_centisecs,让数据写入更平滑。但是需要注意,考虑

到掉电数据安全,这两个值不能无限减小,太小会导致后台一直在做数据回写,无形中增加了掉电

丢数据的风险。最后再强调这些改动是全局的,即对所有的存储设备都生效,所以一定要慎重。

在 Linux 下可以通过 bashrc 中加入 echo 命令来实现,而 Android 可以再 init.rc 脚本里搜一下其

他写 proc 目录节点的位置,在附近加一下你的 vm 参数的调整,例如:

write /proc/sys/vm/dirty_background_ratio 5