上文最后留了两个问题:
1. write只是将数据写入内核缓存就返回了,并没有写入磁盘,如何证明? 2. 数据既然只是写入内核缓存,那数据什么时候写入磁盘?有哪些影响因素?可否改变?本文就讨论下这两个问题。
1. 脏页概念
因为硬盘的读写速度远赶不上内存的速度,系统就把读写比较频繁的数据事先放到内存中,以提高读写速度,这就叫高速缓存,linux是以页作为高速缓存的单位,当进程修改了高速缓存里的数据时,该页就被内核标记为脏页,内核将会在合适的时间把脏页的数据写到磁盘中去,以保持高速缓存中的数据和磁盘中的数据是一致的。
根据脏页的定义,write函数将数据写入内核缓存,然后返回。此时,数据还未写入磁盘,即内存数据与磁盘数据不一致,这时就会产生脏页。
2. 查看脏页
write函数返回时,数据在内存中会标记为脏页,如果能看到脏页的大小,并且与write写的数据大小一致,第一个问题则得到证明。
如何查看脏页大小呢?其实很简单:cat /proc/meminfo就可以做到。
1xzkj@xzkj:~$ cat /proc/meminfo 2MemTotal: 1514724 kB 3MemFree: 404436 kB 4MemAvailable: 1118264 kB 5Buffers: 273244 kB 6Cached: 502964 kB 7SwapCached: 160 kB 8Active: 578192 kB 9Inactive: 307812 kB 10Active(anon): 89016 kB 11Inactive(anon): 24160 kB 12Active(file): 489176 kB 13Inactive(file): 283652 kB 14Unevictable: 0 kB 15Mlocked: 0 kB 16SwapTotal: 1570812 kB 17SwapFree: 1570580 kB 18Dirty: 0 kB 19Writeback: 0 kB 20AnonPages: 109632 kB 21Mapped: 98840 kB 22Shmem: 3384 kB 23
输出的第18行,即为系统当前脏页大小。
3. 脏页实验 1//test.c 2#include <stdio.h> 3#include <sys/types.h> 4#include <sys/stat.h> 5#include <fcntl.h> 6#include <string.h> 7#include <stdlib.h> 8 9 10#define LEN 50*1024*1024 11 12int main() 13{ 14 int fd; 15 char *buf = malloc(LEN); 16 if (buf == NULL) 17 return -1; 18 19 memset(buf,0x5a,LEN); 20 21 fd = open("123.txt",O_RDWR|O_CREAT,666); 22 if (fd < 0) 23 return -1; 24 25 write(fd,buf,LEN); 26 //延时以便查看脏页 27 sleep(15); 28 29 free(buf); 30 31 close(fd); 32 33 return 0; 34}
测试代码中,我们向文件写入50MB数据,write后故意延迟15s,然后立刻通过上述命令查看脏页。测试结果:1xzkj@xzkj:~$ cat /proc/meminfo | grep Dirty 2Dirty: 51200 kB
可以看到脏页大小刚好等于50MB。那如何清空脏页呢?即如何快速把数据写入磁盘。可以使用 sync 命令。
1xzkj@xzkj:~$ sync 2xzkj@xzkj:~$ sync 3xzkj@xzkj:~$ cat /proc/meminfo | grep Dirty 4Dirty: 0 kB
4. fsync实验 fsync函数可同步数据到磁盘,并等待写磁盘结束后才返回。我们做个实验验证下。
1//test.c 2#include <stdio.h> 3#include <sys/types.h> 4#include <sys/stat.h> 5#include <fcntl.h> 6#include <string.h> 7#include <stdlib.h> 8 9 10#define LEN 50*1024*1024 11 12int main() 13{ 14 int fd; 15 char *buf = malloc(LEN); 16 if (buf == NULL) 17 return -1; 18 19 memset(buf,0x5a,LEN); 20 21 fd = open("123.bin",O_RDWR|O_CREAT,666); 22 if (fd < 0) 23 return -1; 24 25 write(fd,buf,LEN); 26 27 //write返回后,立刻调用fsync 28 fsync(fd); 29 30 //延时以便查看脏页 31 sleep(15); 32 33 free(buf); 34 35 close(fd); 36 37 return 0; 38}
测试代码里,write返回后,立刻调用fsync,然后延时,查看脏页大小
实验结果:
1xzkj@xzkj:~$ cat /proc/meminfo | grep Dirty 2Dirty: 4 kB
根据实验,fsync确实可将数据同步到磁盘。
5. sync实验
1//test.c 2#include <stdio.h> 3#include <sys/types.h> 4#include <sys/stat.h> 5#include <fcntl.h> 6#include <string.h> 7#include <stdlib.h> 8 9 10#define LEN 50*1024*1024 11 12int main() 13{ 14 int fd; 15 char *buf = malloc(LEN); 16 if (buf == NULL) 17 return -1; 18 19 memset(buf,0x5a,LEN); 20 21 fd = open("123.bin",O_RDWR|O_CREAT); 22 if (fd < 0) 23 return -1; 24 25 write(fd, buf ,LEN); 26 27 //fwrite返回后,立刻调用fsync 28 sync(fd); 29 30 //延时以便查看脏页 31 sleep(15); 32 33 free(buf); 34 35 close(fd); 36 37 return 0; 38}
sync函数也可将数据刷新到磁盘,我们也来做个实验验证下。
结果:
1xzkj@xzkj:~$ cat /proc/meminfo | grep Dirty 2Dirty: 0 kB
6. 脏页回写参数
write将数据写入内核缓存后,就返回了。那内核缓存里的数据什么时候写入磁盘呢?这由脏页回写参数决定。那如何查看这些参数呢?内核参数的查看与配置可使用sysctl命令:
1xzkj@xzkj:~$ sysctl -a 2>/dev/null| grep dirty 2vm.dirty_background_bytes = 0 3vm.dirty_background_ratio = 5 4vm.dirty_bytes = 0 5vm.dirty_expire_centisecs = 3000 6vm.dirty_ratio = 10 7vm.dirty_writeback_centisecs = 500 8vm.dirtytime_expire_seconds = 43200
其中:
vm.dirty_background_ratio:表示当文件系统缓存脏页数量达到系统内存百分之多少时(如5%)就会触发pdflush/flush/kdmflush等后台回写进程运行,将一定缓存的脏页异步地刷入外存,dirty_background_bytes与此类似,只是通过字节数来设置;
vm.dirty_ratio:绝对脏数据限制,当文件系统缓存脏页数量达到系统内存百分之多少时(如10%),新的IO请求将被阻塞,直到脏页数据写入磁盘;
vm.dirty_expire_centisecs:脏数据存活时间,单位为百分之一秒,上述是30秒;
dirty_writeback_centisecs:多长时间进行一次脏数据写操作。
7. 脏数据参数实验
1//test.c 2#include <stdio.h> 3#include <sys/types.h> 4#include <sys/stat.h> 5#include <fcntl.h> 6#include <string.h> 7#include <stdlib.h> 8 9 10#define LEN 50*1024*1024 11 12int main() 13{ 14 int fd; 15 char *buf = malloc(LEN); 16 if (buf == NULL) 17 return -1; 18 19 memset(buf,0x5a,LEN); 20 21 fd = open("123.bin",O_RDWR|O_CREAT); 22 if (fd < 0) 23 return -1; 24 25 write(fd, buf ,LEN); 26 27 //延时以便查看脏页 28 sleep(15); 29 30 free(buf); 31 32 close(fd); 33 34 return 0; 35} 还是刚开始的脏页实验代码,write后等待15s,上述实验中能够看到脏页数据大小为50MB。这里,我们更改脏页回写参数,将脏页存活时间改为1秒,即write返回1秒后,脏页数据将被刷入磁盘。
1xzkj@xzkj:~$ sudo sysctl -w vm.dirty_expire_centisecs=100 2vm.dirty_expire_centisecs = 100
1xzkj@xzkj:~$ cat /proc/meminfo | grep Dirty 2Dirty: 0 kB
可以看到,此时没有调用fsync或sync函数,脏页数据大小仍旧为0。即我们设置 的脏页回写参数起作用了。 至此,文件开始部分提出的两个问题,都已解决^_^。
欢迎关注公众号交流~

---------------------------------------------------------------------------------------------------------------------- 我们尊重原创,也注重分享,文章来源于微信公众号:Linux技术积累,建议关注公众号查看原文。如若侵权请联系qter@qter.org。 ----------------------------------------------------------------------------------------------------------------------
|