文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Linux的Cache和Buffer理解

2024-12-24 21:32

关注

我们知道,Linux下频繁存取文件或单个大文件时物理内存会很快被用光,当程序结束后内存不会被正常释放而是一直作为cahce占着内存。因此系统经常会因为这点导致OOM产生,尤其在等大压力场景下概率较高,此时,第一时间查看cache和buffer内存是非常高的。此类问题目前尚未有一个很好的解决方案,以往遇到大多会做规避处理,因此本案尝试给出一个分析和解决的思路。

解决该问题的关键是理解什么是cache和buffer,什么时候消耗在哪里以及如何控制cache和buffer,所以本问主要围绕这几点展开。整个讨论过程尽量先从内核源码分析入手,然后提炼APP相关接口并进行实际操作验证,最后总结给出应用程序的编程建议。

可以通过free或者cat /proc/meminfo查看到系统的buffer和cache情况。

free命令的全解析

1. Cache和Buffer分析

从cat /proc/meminfo入手,先看看该接口的实现:

  1. static int meminfo_proc_show(struct seq_file *m, void *v)  
  2.  
  3. ……  
  4. cached = global_page_state(NR_FILE_PAGES) -  
  5.  total_swapcache_pages() - i.bufferram;  
  6. if (cached < 0 
  7.  cached = 0 
  8. ……  
  9.  seq_printf(m,  
  10.  "MemTotal: %8lu kB\n"  
  11.  "MemFree: %8lu kB\n"  
  12.  "Buffers: %8lu kB\n"  
  13.  "Cached: %8lu kB\n"  
  14.  ……  
  15.  ,  
  16.  K(i.totalram),  
  17.  K(i.freeram),  
  18.  K(i.bufferram),  
  19.  K(cached),  
  20.  ……  
  21.  );  
  22. ……  

其中,内核中以页框为单位,通过宏K转化成以KB为单位输出。这些值是通过si_meminfo来获取的:

  1. void si_meminfo(struct sysinfo *val) 
  2.  val->totalram = totalram_pages
  3.  val->sharedram = 0
  4.  val->freeram = global_page_state(NR_FREE_PAGES); 
  5.  val->bufferram = nr_blockdev_pages(); 
  6.  val->totalhigh = totalhigh_pages
  7.  val->freehigh = nr_free_highpages(); 
  8.  val->mem_unit = PAGE_SIZE

其中bufferram来自于nr_blockdev_pages(),该函数计算块设备使用的页框数,遍历所有块设备,将使用的页框数相加。而不包含普通文件使用的页框数。

  1. long nr_blockdev_pages(void)  
  2.  
  3.  struct block_device *bdev;  
  4.  long ret = 0 
  5.  spin_lock(&bdev_lock);  
  6.  list_for_each_entry(bdev, &all_bdevs, bd_list) {  
  7.  ret += bdev->bd_inode->i_mapping->nrpages;  
  8.  }  
  9.  spin_unlock(&bdev_lock);  
  10.  return ret;  

从以上得出meminfo中cache和buffer的来源:

通过内核代码分析(这里略过复杂的内核代码分析),虽然两者在实现上差别不是很大,都是通过address_space对象进行管理的,但是page cache是对文件数据的缓存而buffer cache是对块设备数据的缓存。对于每个块设备都会分配一个def_blk_ops的文件操作方法,这是设备的操作方法,在每个块设备的inode(bdev伪文件系统的inode)下面会存在一个radix tree,这个radix tree下面将会放置缓存数据的page页。这个page的数量将会在cat /proc/meminfobuffer一栏中显示。也就是在没有文件系统的情况下,采用dd等工具直接对块设备进行操作的数据会缓存到buffer cache中。如果块设备做了文件系统,那么文件系统中的文件都有一个inode,这个inode会分配ext3_ops之类的操作方法,这些方法是文件系统的方法,在这个inode下面同样存在一个radix tree,这里也会缓存文件的page页,缓存页的数量在cat /proc/meminfo的cache一栏进行统计。此时对文件操作,那么数据大多会缓存到page cache,不多的是文件系统文件的元数据会缓存到buffer cache。

这里,我们使用cp命令拷贝一个50MB的文件操作,内存会发生什么变化:

  1. [root nfs_dir] # ll -h file_50MB.bin 
  2. -rw-rw-r-- 1 4104 4106 50.0M Feb 24 2016 file_50MB.bin 
  3. [root nfs_dir] # cat /proc/meminfo 
  4. MemTotal: 90532 kB 
  5. MemFree: 65696 kB 
  6. Buffers: 0 kB 
  7. Cached: 8148 kB 
  8. …… 
  9. [root@test nfs_dir] # cp file_50MB.bin / 
  10. [root@test nfs_dir] # cat /proc/meminfo 
  11. MemTotal: 90532 kB 
  12. MemFree: 13012 kB 
  13. Buffers: 0 kB 
  14. Cached: 60488 kB 

可以看到cp命令前后,MemFree从65696 kB减少为13012 kB,Cached从8148 kB增大为60488 kB,而Buffers却不变。那么过一段时间,Linux会自动释放掉所用的cache内存吗?一个小时后查看proc/meminfo显示cache仍然没有变化。

接着,我们看下使用dd命令对块设备写操作前后的内存变化:

  1. [0225_19:10:44:10s][root@test nfs_dir] # cat /proc/meminfo 
  2. [0225_19:10:44:10s]MemTotal: 90532 kB  
  3. [0225_19:10:44:10s]MemFree: 58988 kB  
  4. [0225_19:10:44:10s]Buffers: 0 kB  
  5. [0225_19:10:44:10s]Cached: 4144 kB  
  6. ...... ......  
  7. [0225_19:11:13:11s][root@test nfs_dir] # dd if=/dev/zero of=/dev/h_sda bs=10M count=2000 &  
  8. [0225_19:11:17:11s][root@test nfs_dir] # cat /proc/meminfo  
  9. [0225_19:11:17:11s]MemTotal: 90532 kB  
  10. [0225_19:11:17:11s]MemFree: 11852 kB  
  11. [0225_19:11:17:11s]Buffers: 36224 kB  
  12. [0225_19:11:17:11s]Cached: 4148 kB  
  13. ...... ......  
  14. [0225_19:11:21:11s][root@test nfs_dir] # cat /proc/meminfo  
  15. [0225_19:11:21:11s]MemTotal: 90532 kB  
  16. [0225_19:11:21:11s]MemFree: 11356 kB  
  17. [0225_19:11:21:11s]Buffers: 36732 kB  
  18. [0225_19:11:21:11s]Cached: 4148kB  
  19. ...... ......  
  20. [0225_19:11:41:11s][root@test nfs_dir] # cat /proc/meminfo  
  21. [0225_19:11:41:11s]MemTotal: 90532 kB  
  22. [0225_19:11:41:11s]MemFree: 11864 kB  
  23. [0225_19:11:41:11s]Buffers: 36264 kB  
  24. [0225_19:11:41:11s]Cached: 4148 kB  
  25. ….. …… 

裸写块设备前Buffs为0,裸写硬盘过程中每隔一段时间查看内存信息发现Buffers一直在增加,空闲内存越来越少,而Cached数量一直保持不变。

总结:

通过代码分析及实际操作,我们理解了buffer cache和page cache都会占用内存,但也看到了两者的差别。page cache针对文件的cache,buffer是针对块设备数据的cache。Linux在可用内存充裕的情况下,不会主动释放page cache和buffer cache。

2. 使用posix_fadvise控制Cache

在Linux中文件的读写一般是通过buffer io方式,以便充分利用到page cache。

Buffer IO的特点是读的时候,先检查页缓存里面是否有需要的数据,如果没有就从设备读取,返回给用户的同时,加到缓存一份;写的时候,直接写到缓存去,再由后台的进程定期刷到磁盘去。这样的机制看起来非常的好,实际也能提高文件读写的效率。

但是当系统的IO比较密集时,就会出问题。当系统写的很多,超过了内存的某个上限时,后台的回写线程就会出来回收页面,但是一旦回收的速度小于写入的速度,就会触发OOM。最关键的是整个过程由内核参与,用户不好控制。

那么到底如何才能有效的控制cache呢?

目前主要由两种方法来规避风险:

这里当然讨论的是第二种方式,即在buffer io方式下如何有效控制page cache。

在程序中只要知道文件的句柄,就能用:

  1. int posix_fadvise(int fd, off_t offset, off_t len, int advice); 

POSIX_FADV_DONTNEED (该文件在接下来不会再被访问),但是曾有开发人员反馈怀疑该接口的有效性。那么该接口确实有效吗?首先,我们查看mm/fadvise.c内核代码来看posix_fadvise是如何实现的:

  1.   
  2. SYSCALL_DEFINE4(fadvise64_64, int, fd, loff_t, offset, loff_t, len, int, advice)  
  3.  
  4.  … … … …  
  5.    
  6.  case POSIX_FADV_DONTNEED:  
  7.    
  8.  if (!bdi_write_congested(mapping->backing_dev_info))  
  9.    
  10.    
  11.  __filemap_fdatawrite_range(mapping, offset, endbyte,  
  12.  WB_SYNC_NONE);  
  13.   
  14.  
  15.    
  16.  start_index = (offset+(PAGE_CACHE_SIZE-1)) >> PAGE_CACHE_SHIFT;  
  17.  end_index = (endbyte >> PAGE_CACHE_SHIFT);  
  18.   
  19.  
  20.   
  21.  if (end_index >= start_index) {  
  22.  unsigned long count = invalidate_mapping_pages(mapping,  
  23.  start_index, end_index); 
  24.   
  25.  
  26.    
  27.  if (count < (end_index - start_index + 1)) {  
  28.  lru_add_drain_all();  
  29.  invalidate_mapping_pages(mapping, start_index,  
  30.  end_index);  
  31.  }  
  32.  }  
  33.  break;  
  34. … … … …  

我们可以看到如果后台系统不忙的话,会先调用__filemap_fdatawrite_range把脏页面刷掉,刷页面用的参数是是 WB_SYNC_NONE,也就是说不是同步等待页面刷新完成,提交完写脏页后立即返回了。

然后再调invalidate_mapping_pages清除页面,回收内存:

  1.   
  2. unsigned long invalidate_mapping_pages(struct address_space *mapping,  
  3.  pgoff_t start, pgoff_t end)  
  4.  
  5.  struct pagevec pvec;  
  6.  pgoff_t index = start 
  7.  unsigned long ret;  
  8.  unsigned long count = 0 
  9.  int i;  
  10.   
  11.  
  12.    
  13.   
  14.  
  15.  pagevec_init(&pvec, 0);  
  16.  while (index <= end && pagevec_lookup(&pvec, mapping, index,  
  17.  min(end - index, (pgoff_t)PAGEVEC_SIZE - 1) + 1)) {  
  18.  mem_cgroup_uncharge_start();  
  19.  for (i = 0; i < pagevec_count(&pvec); i++) {  
  20.  struct page *page = pvec.pages[i];  
  21.   
  22.  
  23.    
  24.  index = page->index;  
  25.  if (index > end)  
  26.  break;  
  27.   
  28.  
  29.  if (!trylock_page(page))  
  30.  continue;  
  31.  WARN_ON(page->index != index);  
  32.    
  33.  ret = invalidate_inode_page(page);  
  34.  unlock_page(page);  
  35.    
  36.  if (!ret)  
  37.  deactivate_page(page);  
  38.  count += ret;  
  39.  }  
  40.  pagevec_release(&pvec);  
  41.  mem_cgroup_uncharge_end();  
  42.  cond_resched();  
  43.  index++;  
  44.  }  
  45.  return count;  
  46.    
  47.  
  48.   
  49.   
  50. int invalidate_inode_page(struct page *page)  
  51.  
  52.  struct address_space *mapping = page_mapping(page);  
  53.  if (!mapping)  
  54.  return 0;  
  55.    
  56.  if (PageDirty(page) || PageWriteback(page))  
  57.  return 0;  
  58.    
  59.  if (page_mapped(page))  
  60.  return 0;  
  61.    
  62.  return invalidate_complete_page(mapping, page);  
  63.  
  64. 从上面的代码可以看到清除相关的页面要满足二个条件: 1. 不脏且没在回写; 2. 未被使用。如果满足了这二个条件就调用invalidate_complete_page继续:  
  65.   
  66. static int  
  67. invalidate_complete_page(struct address_space *mapping, struct page *page)  
  68.  
  69.  int ret;  
  70.   
  71.  
  72.  if (page->mapping != mapping)  
  73.  return 0;  
  74.   
  75.  
  76.  if (page_has_private(page) && !try_to_release_page(page, 0))  
  77.  return 0;  
  78.   
  79.  
  80.    
  81.  ret = remove_mapping(mapping, page); 
  82.    
  83.  
  84.  return ret;  
  85.  
  86.   
  87.  
  88.   
  89.   
  90. int remove_mapping(struct address_space *mapping, struct page *page) 
  91.  
  92.  if (__remove_mapping(mapping, page)) {  
  93.    
  94.  page_unfreeze_refs(page, 1);  
  95.  return 1;  
  96.  }  
  97.  return 0;  
  98.    
  99.  
  100.   
  101.   
  102. static int __remove_mapping(struct address_space *mapping, struct page *page)  
  103.  
  104.  BUG_ON(!PageLocked(page));  
  105.  BUG_ON(mapping != page_mapping(page));  
  106.   
  107.  
  108.  spin_lock_irq(&mapping->tree_lock);  
  109.    
  110.  if (!page_freeze_refs(page, 2))  
  111.  goto cannot_free;  
  112.    
  113.  if (unlikely(PageDirty(page))) {  
  114.  page_unfreeze_refs(page, 2);  
  115.  goto cannot_free; 
  116.  } 
  117.   
  118.  if (PageSwapCache(page)) {  
  119.  swp_entry_t swap = { .val = page_private(page) }; 
  120.  __delete_from_swap_cache(page);  
  121.  spin_unlock_irq(&mapping->tree_lock);  
  122.  swapcache_free(swap, page);  
  123.  } else {  
  124.  void (*freepage)(struct page *);  
  125.   
  126.  
  127.  freepage = mapping->a_ops->freepage;  
  128.   
  129.    
  130.   __delete_from_page_cache(page); 
  131.   spin_unlock_irq(&mapping->tree_lock); 
  132.   mem_cgroup_uncharge_cache_page(page); 
  133.    
  134.   if (freepage != NULL) 
  135.   freepage(page);  
  136.  }  
  137.   
  138.   return 1;  
  139.   
  140.  
  141. cannot_free:  
  142.  spin_unlock_irq(&mapping->tree_lock);  
  143.  return 0;  
  144.  
  145.   
  146.    
  147.   
  148. void __delete_from_page_cache(struct page *page)  
  149.  
  150.  struct address_space *mapping = page->mapping;  
  151.    
  152.  trace_mm_filemap_delete_from_page_cache(page);  
  153.    
  154.  if (PageUptodate(page) && PageMappedToDisk(page))  
  155.  cleancache_put_page(page);  
  156.  else  
  157.  cleancache_invalidate_page(mapping, page);  
  158.   
  159.  
  160.  radix_tree_delete(&mapping->page_tree, page->index);  
  161.    
  162.  page->mapping = NULL 
  163.    
  164.   
  165.  mapping->nrpages--; 
  166.  __dec_zone_page_state(page, NR_FILE_PAGES);  
  167.  if (PageSwapBacked(page))  
  168.  __dec_zone_page_state(page, NR_SHMEM);  
  169.  BUG_ON(page_mapped(page)); 
  170.    
  171.  
  172.    
  173.  if (PageDirty(page) && mapping_cap_account_dirty(mapping)) {  
  174.  dec_zone_page_state(page, NR_FILE_DIRTY);  
  175.  dec_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE); 
  176.  } 

看到这里我们就明白了:为什么使用了posix_fadvise后相关的内存没有被释放出来:页面还脏是最关键的因素。

但是我们如何保证页面全部不脏呢?fdatasync或者fsync都是选择,或者Linux下新系统调用sync_file_range都是可用的,这几个都是使用WB_SYNC_ALL模式强制要求回写完毕才返回的。所以应该这样做:

  1. fdatasync(fd); 
  2. posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); 

总结:

使用posix_fadvise可以有效的清除page cache,作用范围为文件级。下面给出应用程序编程建议:

3. 使用vmtouch控制Cache

vmtouch是一个可移植的文件系统cahce诊断和控制工具。近来该工具被广泛使用,最典型的例子是:移动应用Instagram(照片墙)后台服务端使用了vmtouch管理控制page cache。了解vmtouch原理及使用可以为我们后续后端设备所用。

快速安装指南:

  1. $ git clone https://github.com/hoytech/vmtouch.git 
  2. $ cd vmtouch 
  3. $ make 
  4. $ sudo make install 

vmtouch用途:

vmtouch实现:

其核心分别是两个系统调用,mincore和posix_fadvise。两者具体使用方法使用man帮助都有详细的说明。posix_fadvise已在上文提到,用法在此不作说明。简单说下mincore:

  1. NAME 
  2.  mincore - determine whether pages are resident in memory 
  3.  
  4.   
  5. SYNOPSIS 
  6.  #include <unistd.h> 
  7.  #include <sys/mman.h> 
  8.   
  9.  
  10.  int mincore(void *addr, size_t length, unsigned char *vec); 
  11.  
  12.   
  13.  Feature Test Macro Requirements for glibc (see feature_test_macros(7)): 
  14.  
  15.   
  16.  mincore(): _BSD_SOURCE || _SVID_SOURCE 

mincore需要调用者传入文件的地址(通常由mmap()返回),它会把文件在内存中的情况写在vec中。

vmtouch工具用法:

Usage:vmtouch [OPTIONS] ... FILES OR DIRECTORIES ...

Options:

用法举例:

例1、 获取当前/mnt/usb目录下cache占用量

  1. [root@test nfs_dir] # mkdir /mnt/usb && mount /dev/msc /mnt/usb/  
  2. [root@test usb] # vmtouch .  
  3.  Files: 57  
  4.  Directories: 2  
  5.  Resident Pages: 0/278786 0/1G 0%  
  6.  Elapsed: 0.023126 seconds 

例2、 当前test.bin文件的cache占用量?

  1. [root@test usb] # vmtouch -v test.bin  
  2. test.bin  
  3. [ ] 0/25600  
  4.   
  5.  
  6.  Files: 1  
  7.  Directories: 0  
  8.  Resident Pages: 0/25600 0/100M 0%  
  9.  Elapsed: 0.001867 seconds 

这时使用tail命令将部分文件读取到内存中:

  1. [root@test usb] # busybox_v400 tail -n 10 test.bin > /dev/null 

现在再来看一下:

  1. [root@test usb] # vmtouch -v test.bin  
  2. test.bin  
  3. [ o] 240/25600  
  4.   
  5.  
  6.  Files: 1  
  7.  Directories: 0  
  8.  Resident Pages: 240/25600 960K/100M 0.938%  
  9.  Elapsed: 0.002019 seconds 

可知目前文件test.bin的最后240个page驻留在内存中。

例3、 最后使用-t选项将剩下的test.bin文件全部读入内存:

  1. [root@test usb] # vmtouch -vt test.bin  
  2. test.bin  
  3. [OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 25600/25600  
  4.   
  5.  
  6.  Files: 1  
  7.  Directories: 0  
  8.  Touched Pages: 25600 (100M) 
  9.  Elapsed: 39.049 seconds 

例4、 再把test.bin占用的cachae全部释放:

  1. [root@test usb] # vmtouch -ev test.bin  
  2. Evicting test.bin  
  3.   
  4.  
  5.  Files: 1  
  6.  Directories: 0  
  7.  Evicted Pages: 25600 (100M)  
  8.  Elapsed: 0.01461 seconds 

这时候再来看下是否真的被释放了:

  1. [root@test usb] # vmtouch -v test.bin  
  2. test.bin  
  3. [ ] 0/25600  
  4.   
  5.  
  6.  Files: 1  
  7.  Directories: 0  
  8.  Resident Pages: 0/25600 0/100M 0%  
  9.  Elapsed: 0.001867 seconds 

以上通过代码分析及实际操作总结了vmtouch工具的使用,建议APP组后续集成或借鉴vmtouch工具并灵活应用到后端设备中,必能达到有效管理和控制page cache的目的。

4. 使用BLKFLSBUF清Buffer

通过走读块设备驱动IOCTL命令实现,发现该命令能有效的清除整个块设备所占用的buffer。

  1. int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,  
  2.  unsigned long arg)  
  3.  
  4.  struct gendisk *disk = bdev->bd_disk;  
  5.  struct backing_dev_info *bdi;  
  6.  loff_t size;  
  7.  int ret, n;  
  8.   
  9.  
  10.  switch(cmd) {  
  11.  case BLKFLSBUF:  
  12.  if (!capable(CAP_SYS_ADMIN))  
  13.  return -EACCES;  
  14.   
  15.  
  16.  ret = __blkdev_driver_ioctl(bdev, mode, cmd, arg);  
  17.  if (!is_unrecognized_ioctl(ret))  
  18.  return ret;  
  19.   
  20.  
  21.  fsync_bdev(bdev);  
  22.  invalidate_bdev(bdev); 
  23.  return 0;  
  24. case ……:  
  25. …………  
  26.  
  27.   
  28.  
  29.   
  30. void invalidate_bdev(struct block_device *bdev)  
  31.  
  32.  struct address_space *mapping = bdev->bd_inode->i_mapping;  
  33.   
  34.  
  35.  if (mapping->nrpages == 0)  
  36.  return;  
  37.   
  38.  
  39.  invalidate_bh_lrus();  
  40.  lru_add_drain_all();   
  41.  invalidate_mapping_pages(mapping, 0, -1);  
  42.    
  43.  cleancache_invalidate_inode(mapping);  
  44.  
  45. EXPORT_SYMBOL(invalidate_bdev); 

光代码不够,现在让我们看下对/dev/h_sda这个块设备执行BLKFLSBUF的IOCTL命令前后的实际内存变化:

  1. [0225_19:10:25:10s][root@test nfs_dir] # cat /proc/meminfo  
  2. [0225_19:10:25:10s]MemTotal: 90532 kB  
  3. [0225_19:10:25:10s]MemFree: 12296 kB  
  4. [0225_19:10:25:10s]Buffers: 46076 kB  
  5. [0225_19:10:25:10s]Cached: 4136 kB  
  6. …………  
  7. [0225_19:10:42:10s][root@test nfs_dir] # /mnt/nfs_dir/a.out  
  8. [0225_19:10:42:10s]ioctl cmd BLKFLSBUF ok!  
  9. [0225_19:10:44:10s][root@test nfs_dir] # cat /proc/meminfo  
  10. [0225_19:10:44:10s]MemTotal: 90532 kB  
  11. [0225_19:10:44:10s]MemFree: 58988 kB  
  12. [0225_19:10:44:10s]Buffers: 0 kB  
  13. …………  
  14. [0225_19:10:44:10s]Cached: 4144 kB 

执行的效果如代码中看到的,Buffers已被全部清除了,MemFree一下增长了约46MB,可以知道原先的Buffer已被回收并转化为可用的内存。整个过程Cache几乎没有变化,仅增加的8K cache内存可以推断用于a.out本身及其他库文件的加载。

上述a.out的示例如下:

  1. #include <stdio.h>  
  2. #include <fcntl.h>  
  3. #include <errno.h> 
  4. #include <sys/ioctl.h> 
  5. #define BLKFLSBUF _IO(0x12, 97) 
  6. int main(int argc, char* argv[])  
  7.  
  8.  int fd = -1;  
  9.  fd = open("/dev/h_sda", O_RDWR); 
  10.  if (fd < 0
  11.  { 
  12.  return -1; 
  13.  }  
  14.  if (ioctl(fd, BLKFLSBUF, 0))  
  15.  {  
  16.  printf("ioctl cmd BLKFLSBUF failed, errno:%d\n", errno);  
  17.  } 
  18.  close(fd); 
  19. printf("ioctl cmd BLKFLSBUF ok!\n"); 
  20.  return 0;  

综上,使用块设备命令BLKFLSBUF能有效的清除块设备上的所有buffer,且清除后的buffer能立即被释放变为可用内存。

利用这一点,联系后端业务场景,给出应用程序编程建议:

5. 使用drop_caches控制Cache和Buffer

/proc是一个虚拟文件系统,我们可以通过对它的读写操作作为与kernel实体间进行通信的一种手段.也就是说可以通过修改/proc中的文件来对当前kernel的行为做出调整。关于Cache和Buffer的控制,我们可以通过echo 1 > /proc/sys/vm/drop_caches进行操作。

首先来看下内核源码实现:

  1. int drop_caches_sysctl_handler(ctl_table *table, int write,  
  2.  void __user *buffer, size_t *length, loff_t *ppos)  
  3.  
  4.  int ret;  
  5.   
  6.  
  7.  ret = proc_dointvec_minmax(table, write, buffer, length, ppos);  
  8.  if (ret)  
  9.  return ret;  
  10.  if (write) {  
  11.    
  12.  if (sysctl_drop_caches & 1)  
  13.    
  14.  iterate_supers(drop_pagecache_sb, NULL);  
  15.  if (sysctl_drop_caches & 2)  
  16.  drop_slab();  
  17.  }  
  18.  return 0;  
  19.  
  20.   
  21.  
  22.   
  23. void iterate_supers(void (*f)(struct super_block *, void *), void *arg)  
  24.  
  25.  struct super_block *sb, *p = NULL 
  26.   
  27.  
  28.  spin_lock(&sb_lock);  
  29.  list_for_each_entry(sb, &super_blocks, s_list) {  
  30.  if (hlist_unhashed(&sb->s_instances))  
  31.  continue;  
  32.  sb->s_count++;  
  33.  spin_unlock(&sb_lock);  
  34.   
  35.  
  36.  down_read(&sb->s_umount); 
  37.  if (sb->s_root && (sb->s_flags & MS_BORN))  
  38.  f(sb, arg);  
  39.  up_read(&sb->s_umount); 
  40.   
  41.  
  42.  spin_lock(&sb_lock);  
  43.  if (p) 
  44.  __put_super(p);  
  45.  p = sb 
  46.  }  
  47.  if (p)  
  48.  __put_super(p);  
  49.  spin_unlock(&sb_lock); 
  50.   
  51.  
  52.   
  53. static void drop_pagecache_sb(struct super_block *sb, void *unused)  
  54.  
  55.  struct inode *inode, *toput_inode = NULL 
  56.   
  57.  
  58.  spin_lock(&inode_sb_list_lock);  
  59.    
  60.  list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {  
  61.  spin_lock(&inode->i_lock);  
  62.    
  63.  if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) ||  
  64.  (inode->i_mapping->nrpages == 0)) { 
  65.  spin_unlock(&inode->i_lock);  
  66.  continue;  
  67.  }  
  68.  __iget(inode);  
  69.  spin_unlock(&inode->i_lock);  
  70.  spin_unlock(&inode_sb_list_lock);  
  71.    
  72.  invalidate_mapping_pages(inode->i_mapping, 0, -1);  
  73.  iput(toput_inode);  
  74.  toput_inode = inode;  
  75.  spin_lock(&inode_sb_list_lock);  
  76.  } 
  77.  spin_unlock(&inode_sb_list_lock);  
  78.  iput(toput_inode);  

综上,echo 1 > /proc/sys/vm/drop_caches会清除所有inode的缓存页,这里的inode包括VFS的inode、所有文件系统inode(也包括bdev伪文件系统块设备的inode的缓存页)。所以该命令执行后,就会将整个系统的page cache和buffer cache全部清除,当然前提是这些cache都是非脏的、没有正被使用的。

接下来看下实际效果:

  1. [root@test usb] # cat /proc/meminfo 
  2. MemTotal: 90516 kB 
  3. MemFree: 12396 kB 
  4. Buffers: 96 kB 
  5. Cached: 60756 kB 
  6. [root@test usb] # busybox_v400 sync 
  7. [root@test usb] # busybox_v400 sync 
  8. [root@test usb] # busybox_v400 sync 
  9. [root@test usb] # echo 1 > /proc/sys/vm/drop_caches 
  10. [root@test usb] # cat /proc/meminfo 
  11. MemTotal: 90516 kB 
  12. MemFree: 68820 kB 
  13. Buffers: 12 kB 
  14. Cached: 4464 kB 

可以看到Buffers和Cached都降了下来,在drop_caches前建议执行sync命令,以确保数据的完整性。sync 命令会将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件等。

上面的设置虽然简单但是比较粗暴,使cache的作用基本无法发挥,尤其在系统压力比较大时进行drop cache处理容易产生问题。因为drop_cache是全局在清内存,清的过程会加页面锁,导致有些进程等页面锁时超时,导致问题发生。因此,需要根据系统的状况进行适当的调节寻找最佳的方案。

6. 经验总结

以上分别讨论了Cache和Buffer分别从哪里来?什么时候消耗在哪里?如何分别控制Cache和Buffer这三个问题。最后还介绍了vmtouch工具的使用。

要深入理解Linux的Cache和Buffer牵涉大量内核核心机制(VFS、内存管理、块设备驱动、页高速缓存、文件访问、页框回写),需要制定计划在后续工作中不断理解和消化。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯