Chinaunix首页 | 论坛 | 博客
  • 博客访问: 769598
  • 博文数量: 196
  • 博客积分: 115
  • 博客等级: 民兵
  • 技术积分: 354
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-13 23:19
文章分类

全部博文(196)

文章存档

2021年(1)

2019年(5)

2018年(11)

2017年(15)

2016年(13)

2015年(46)

2014年(81)

2013年(22)

2012年(2)

分类: LINUX

2018-11-29 13:16:38

     最近的时光,闲暇下来的时候基本是在看PostgreSQL的source code,最近在看storage,其中file部分整体看的比较快,因为有2年的文件系统的实战经验。但是自己当年,不会的东西太多,没有能力细细的研读kernel 和相关的东西。我看到了fadvise 这个接口,然后展开学习了这个接口。相当的东西都是学习网上其他前辈的的,很多源自淘宝的褚霸 霸爷,向相关的前辈致敬。
      
Linux操作系统在读文件中的某页的时候(pagesize=4k),它会首先去查找缓存,看下缓存中是否有要读的那个页面。没有的话,就去后备设备(大多数是块设备)去读上来。读上来对应页面的内容也会缓存在内存中,下一次再读同一个页面的时候,因为缓存中已经有个这个文件,不需要磁盘操作,所以会提升速度。talk is cheap,我实验证明之:
  1. root@manu:~# time cp NVIDIA-Linux-x86-310.44.run bean_1

  2. real    0m0.593s
  3. user    0m0.004s
  4. sys     0m0.128s
  5. root@manu:~# time cp NVIDIA-Linux-x86-310.44.run bean_2

  6. real    0m0.055s
  7. user    0m0.004s
  8. sys     0m0.052s
  9. root@manu:~# time cp NVIDIA-Linux-x86-310.44.run bean_3

  10. real    0m0.056s
  11. user    0m0.000s
  12. sys     0m0.052s
   
   
我们看到第一操作这个38M文件的用了0.6second,但是第二次操作的时候的时候,只用了0.055和0.056秒,差距是10倍。原因无他,第一次牵扯到了磁盘操作,需要将NVIDIA-Linux-x86-310.44.run的内容读入缓存之中。第二次的时候,就不需要了操作磁盘了,因为缓存中已经有了这个文件的页面。
   
到了此时,我们有必要讲述下一个很有名气很常用的命令了,那就是free。
    

   上面一副图源自Linux Performance and Tuning Guideline,很好的一本书。这附图基本讲明确了内存的构成。
   
   如何计算,内存的使用呢,霸爷今年有篇Linux used 内存到哪里去了?讲的特别细致,我就不多说了,我们关心的是cache。

   首先,我们写一个新文件,用dd灌一个文件,我们知道,写的时候,文件的内容会在缓存中,有后台进程定期刷写进磁盘。这部分内容就会计入cache之中:
   dd写文件之前:
   
   然后用dd灌一个新文件:
   

   我们发现cache的内容显著增加从185232升到了361156,大小接近与我们的新文件bean_1. 我说这就是这个文件缓存在内存的大小。空口无凭,如何证明:
    这时候,我们需要提到的另一个主角就要登场了,mincore系统调用。Linux提供了这个系统调用用来判断文件的某个页面是否驻留在内存中。内核代码在mm/mincore.c下面,代码比较简单。这个系统调用实现没啥好说的,可是这个系统调用的作用实在是太大了,有了它,你给我一个文件名,我就能告诉你这个文件某个页面是否驻留在内存中。事实上已经有不少工具这么做了。 
   首先有个工具是linux-ftools,在Ubuntu下下载方式如下::

  1. apt-get install install mercurial

  2. hg clone https://code.google.com/p/linux-ftools/
    然后我们可以用configure ;make ;make install将这个包安装,这个包提供了一个叫linux-fincore的二进制可执行文件,可以用来看文件页面在内存的驻留情况,给出了统计信息:
   
   linux-fincore这个工具清楚的告诉我们:我们一个共缓存了4096个页面,缓存率是100%,即所有的页面都在内存中可以找到。很神奇吧,其实代码非常简单,就是用了open,mmap,stat,mincore等有限的系统调用。我们这只可以把这个工具做的更强大,不止是统计,把每个页面是否出现在内存都展现出来。我们一起看下代码实现:
  1.     fd = open( path, O_RDONLY );
  2.     ....
  3.     if ( fstat( fd, &file_stat ) < 0 )
  4.     ....
  5.     file_mmap = mmap((void *)0, file_stat.st_size, PROT_NONE, MAP_SHARED, fd, 0 );
  6.     ...
  7.     size_t calloc_size = (file_stat.st_size+page_size-1)/page_size;

  8.     mincore_vec = calloc(1, calloc_size);
  9.     ....

  10.     if ( mincore(file_mmap, file_stat.st_size, mincore_vec) != 0 )
  11.     ....
  12.     if (mincore_vec[page_index]&1) {

  13.             ++cached;
    我们去掉了那些异常判断,去掉不相关的展现逻辑,核心代码就是这么几行。 open获得文件描述符,stat获取文件的长度,页面的大小是4K ,有了长度,我们就知道了,我们需要多少个int来存放结果。mmap建立映射关系,mincore获取文件页面的驻留情况,从起始地址开始,长度是filesize,结果保存在mincore_vec数组里。如果mincore[page_index]&1 == 1,那么表示该页面驻留在内存中,否则没有对应缓存。
   vmtouch也是一个相关的工具,也提供类似的功能,代码路径在:,只有一个文件,直接编译即可:
  1. root@manu:~/code/c/classical/pearl/vmtouch# gcc -Wall -O3 -o vmtouch vmtouch.c
  2. root@manu:~/code/c/classical/pearl/vmtouch# ll
  3. 总用量 48
  4. drwxr-xr-x 2 manu root  4096 4月 26 23:50 ./
  5. drwxrwxr-x 8 manu manu  4096 4月 26 23:47 ../
  6. -rwxr-xr-x 1 root root 22220 4月 26 23:50 vmtouch*
  7. -rw-rw-r-- 1 manu manu 16106 4月 26 23:49 vmtouch.c
  8. root@manu:~/code/c/classical/pearl/vmtouch# cp vmtouch /usr/bin/
   
  使用vmtouch工具同样可以看出,bean_1文件,100%的页面都在缓存之中。这部分的实现同linux-fincore大同小异。 
  看到这里,我们发现文件一直驻留在内存中,实际上Linux采用的策略是没超过门限之前,操作系统不作为。至于Linux操作系统的策略,本身又可以写一篇文章,本文不多说,本文只说下用户有什么办法改变现状。假如说,我刚才写的文件bean_1,其实我不会在再操作它了,内存完全没有必要缓存160M的内容在内存里面,怎么告知内存?
 最粗暴的方法是最容易想到的,
  1. echo 3 >/proc/sys/vm/drop_caches
    这部分内核代码位于fs/drop_caches.c里面。调用这个之后,cache部分的内存会被释放。其实是没有关系,纵然cache占满了内存,运行一个大程序也不会出现内存不足,因为操作系统可以回收这部分的内存。
   
除了这条粗暴的方法外,Linux提供了posix_fadvise系统调用,可以允许用户给linux 提建议。

  1.       #include <fcntl.h>

  2.       int posix_fadvise(int fd, off_t offset, off_t len, int advice);
  3.       
  4.       posix_fadvise():
  5.            _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L
   Linux相当于承认自己不够聪明,请用户给它点提示。 它支持用户提那些内容的提示呢?兄弟们可以自行man 一下,常用的是:

  1.        POSIX_FADV_WILLNEED
  2.               The specified data will be accessed in the near future.

  3.        POSIX_FADV_DONTNEED
  4.               The specified data will not be accessed in the near future.
    第一个POSIX_FADV_WILLNEED 相当与告知Linux,这个文件,在不久的将来我要用,请帮我准备好相应的页面(从磁盘读入内存),本质相当与告诉linux预读。
    第二个POSIX_FADV_DONTNEED 相当与告知linux ,这个文件哥不用了,请你不要在把它放在内存里面浪费内存了,把内存留给能需要兄弟吧。本质相当与sync。
    请看PostgreSQL中的相关应用:
   
  1. int
  2. FilePrefetch(File file, off_t offset, int amount)
  3. {
  4. #if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
  5.     int            returnCode;

  6.     Assert(FileIsValid(file));

  7.     DO_DB(elog(LOG, "FilePrefetch: %d (%s) " INT64_FORMAT " %d",
  8.              file, VfdCache[file].fileName,
  9.              (int64) offset, amount));

  10.     returnCode = FileAccess(file);
  11.     if (returnCode < 0)
  12.         return returnCode;

  13.     returnCode = posix_fadvise(VfdCache[file].fd, offset, amount,
  14.                              POSIX_FADV_WILLNEED); //预读

  15.     return returnCode;
  16. #else
  17.     Assert(FileIsValid(file));
  18.     return 0;
  19. #endif
  20. }

  1. int
  2. pg_flush_data(int fd, off_t offset, off_t amount)
  3. {
  4. #if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
  5.     return posix_fadvise(fd, offset, amount, POSIX_FADV_DONTNEED);
  6. #else
  7.     return 0;
  8. #endif
  9. }
    淘宝的霸爷深入分析了POSIX_FADV_DONTNEED相关的内核代码。关心实现的可以去读霸爷的http://blog.yufeng.info/archives/1917    
    现在有个这个fadvise宝贝,我们就能把某文件彻底赶出缓存,代码非常简单(
)   
  1. int clear_file_cache(const char *filename)
  2. {
  3.     struct stat st;
  4.     if(stat(filename , &st) < 0) {
  5.         fprintf(stderr , "stat localfile failed, path:%s\n",filename);
  6.         return -1;
  7.     }

  8.     int fd = open(filename, O_RDONLY);
  9.     if( fd < 0 ) {
  10.         fprintf(stderr , "open localfile failed, path:%s\n",filename);
  11.         return -1;
  12.     }

  13.     //clear cache by posix_fadvise

  14.     if( posix_fadvise(fd,0,st.st_size,POSIX_FADV_DONTNEED) != 0) {
  15.         printf("Cache FADV_DONTNEED failed, %s\n",strerror(errno));
  16.     }
  17.     else {
  18.         printf("Cache FADV_DONTNEED done\n");
  19.     }

  20.     return 0;
  21. }
    除此意外,vmtouch的 -e选项也提供了类似的功能,还有linux-ftools 里面的linux-fadvise也有类似的功能,本质都是posix_fadvise。
   

    我们看到,清除之后,缓存中在也没有bean_1文件的页面了。

后记:
   这里面的水比较深,很多地方可以扩展开来,比如缓存到什么程度,操作系统开始出面清理缓存,在比如posix_fadvise的kernel实现,在比如介绍mincore系统调用的时候,我们发现,多个系统调用组合才能得到文件的缓存信息,这太慢了,Chris Frost提出了一个新的系统调用fincore,感兴趣的可以查看。 另外,低于mincore系统调用,只返回是否在文件对应对页是否存在在缓存中,这太浪费了,明明可以把是否dirty一并返回,我今天一直纠结与如何返回文件页面在缓存的dirty情况,很蛋疼,没解决。实际上mincore完全可以顺路返回这个值。毕竟int有32bit,只用一个bit太浪费了。不能展的太开,否则,就收敛不了了,另外,我的功能还不到。
 fs and page cache.pdf

参考文献
posix_fadvise清除缓存的误解和改进措施


阅读(1888) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~