存储介质同步

内核为了提升 IO 性能,在内存中维护了一些 缓冲页 ( buffer cache pages ),用于缓存磁盘 数据块 ( data blocks )。

读取文件时,如果相关缓冲页已经存在,则直接从内存中复制数据。这样便避免了慢速的磁盘 IO ,要知道内存速度一般要比外部存储快至少一个数量级!

写入文件时,则是先写到缓冲区(内存),内核在合适的时刻将缓冲区刷到磁盘,完成持久化。这样做好处不少:

  • 合并写操作 ——将多个 IO 写操作合并成一个
  • 优化写操作调度 ——调整 IO 写操作实际执行顺序

然而,有得必有失——由于写入不同步,存在 丢数据 的风险。试想,你写一个文件,数据刚写入内存缓存页,内核还没来得及刷盘,机器便断电了!

应用程序调用 write 系统调用后, 内核只保证数据拷贝到内核缓冲区 。至于数据是否成功落盘,应用程序一无所知!

好在,通过 fsync 系统调用,应用程序也可以确保数据成功写入到磁盘中。

看看下面这个例子:

append.c

  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. int main(int argc, char *argv[])
  6. {
  7. // check cmdline arguments
  8. if (argc != 3) {
  9. fprintf(stderr, "Bad arguments!");
  10. return 1;
  11. }
  12. char *pathname = argv[1];
  13. char *data = argv[2];
  14. // open file
  15. int fd = open(pathname, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
  16. if (fd == -1) {
  17. perror("Can not open file");
  18. return 2;
  19. }
  20. // write data
  21. int bytes_written = write(fd, data, strlen(data));
  22. if (bytes_written == -1) {
  23. perror("Can not write file");
  24. return 3;
  25. }
  26. // sync data to disk
  27. int ret = fsync(fd);
  28. if (ret == -1) {
  29. perror("Fail to sync data");
  30. return 4;
  31. }
  32. // prompt bytes appended
  33. printf("Bytes appended: %d\n", bytes_written);
  34. // close file
  35. close(fd);
  36. return 0;
  37. }

例子与普通的 文件写入 区别不大,只不过紧跟 write 系统调用之后,使用 fsync 系统调用确保数据刷到磁盘:

32-36 行,调用 fsync 系统调用,将数据刷到磁盘。

fsync 成功返回则意味着数据已经写入磁盘,此后,就算机器断电,数据也不会丢失。

接下来, 编译该程序:

  1. $ make
  2. gcc -c -o append.o append.c
  3. gcc -o append append.o

lines.txt 文件追加一行,内容为 first line

  1. $ ./append lines.txt 'first line
  2. > '
  3. Bytes appended: 11
  4. $ cat lines.txt
  5. first line

注意到,追加的行是带换行符( \n )的。

继续追加:

  1. $ ./append lines.txt 'another line
  2. > '
  3. Bytes appended: 13
  4. $ cat lines.txt
  5. first line
  6. another line
  7. $ ./append lines.txt 'third line
  8. > '
  9. Bytes appended: 11
  10. $ cat lines.txt
  11. first line
  12. another line
  13. third line

加上 fsync 系统调用之后,只要 append 命令运行成功,数据 一定落到磁盘

如果不带 fsync 系统调用,就算命令成功退出,也不能保证数据成功写入磁盘。内核会在合适的时间内同步数据,最终还是成功落盘。尽管如此,在系统异常的情况下,存在 丢失数据 的可能性。

下一步

订阅更新,获取更多学习资料,请关注我们的 微信公众号

../../_images/wechat-mp-qrcode.png小菜学编程

参考文献