Linux的Cache和Buffer理解( 二 )


接着 , 我们看下使用dd命令对块设备写操作前后的内存变化:
[0225_19:10:44:10s][root@test nfs_dir] # cat /proc/meminfo[0225_19:10:44:10s]MemTotal: 90532 kB[0225_19:10:44:10s]MemFree: 58988 kB[0225_19:10:44:10s]Buffers: 0 kB[0225_19:10:44:10s]Cached: 4144 kB...... ......[0225_19:11:13:11s][root@test nfs_dir] # dd if=/dev/zero of=/dev/h_sda bs=10M count=2000 &[0225_19:11:17:11s][root@test nfs_dir] # cat /proc/meminfo[0225_19:11:17:11s]MemTotal: 90532 kB[0225_19:11:17:11s]MemFree: 11852 kB[0225_19:11:17:11s]Buffers: 36224 kB[0225_19:11:17:11s]Cached: 4148 kB...... ......[0225_19:11:21:11s][root@test nfs_dir] # cat /proc/meminfo[0225_19:11:21:11s]MemTotal: 90532 kB[0225_19:11:21:11s]MemFree: 11356 kB[0225_19:11:21:11s]Buffers: 36732 kB[0225_19:11:21:11s]Cached: 4148kB...... ......[0225_19:11:41:11s][root@test nfs_dir] # cat /proc/meminfo[0225_19:11:41:11s]MemTotal: 90532 kB[0225_19:11:41:11s]MemFree: 11864 kB[0225_19:11:41:11s]Buffers: 36264 kB[0225_19:11:41:11s]Cached: 4148 kB….. ……裸写块设备前Buffs为0 , 裸写硬盘过程中每隔一段时间查看内存信息发现Buffers一直在增加 , 空闲内存越来越少 , 而Cached数量一直保持不变 。
总结:
通过代码分析及实际操作 , 我们理解了buffer cache和page cache都会占用内存 , 但也看到了两者的差别 。page cache针对文件的cache , buffer是针对块设备数据的cache 。Linux在可用内存充裕的情况下 , 不会主动释放page cache和buffer cache 。
1.2 使用posix_fadvise控制Cache在Linux中文件的读写一般是通过buffer io方式 , 以便充分利用到page cache 。
Buffer IO的特点是读的时候 , 先检查页缓存里面是否有需要的数据 , 如果没有就从设备读取 , 返回给用户的同时 , 加到缓存一份;写的时候 , 直接写到缓存去 , 再由后台的进程定期刷到磁盘去 。这样的机制看起来非常的好 , 实际也能提高文件读写的效率 。
但是当系统的IO比较密集时 , 就会出问题 。当系统写的很多 , 超过了内存的某个上限时 , 后台的回写线程就会出来回收页面 , 但是一旦回收的速度小于写入的速度 , 就会触发OOM 。最关键的是整个过程由内核参与 , 用户不好控制 。
那么到底如何才能有效的控制cache呢?
目前主要由两种方法来规避风险:

1、 走direct io;
2、 走buffer io , 但是定期清除无用page cache;
这里当然讨论的是第二种方式 , 即在buffer io方式下如何有效控制page cache 。
在程序中只要知道文件的句柄 , 就能用:
int posix_fadvise(int fd, off_t offset, off_t len, int advice);POSIX_FADV_DONTNEED (该文件在接下来不会再被访问)
但是曾有开发人员反馈怀疑该接口的有效性 。那么该接口确实有效吗?首先 , 我们查看mm/fadvise.c内核代码来看posix_fadvise是如何实现的:
/* * POSIX_FADV_WILLNEED could set PG_Referenced, and POSIX_FADV_NOREUSE could * deactivate the pages and clear PG_Referenced. */SYSCALL_DEFINE4(fadvise64_64, int, fd, loff_t, offset, loff_t, len, int, advice){ … … … … /* => 将指定范围内的数据从page cache中换出 */ case POSIX_FADV_DONTNEED: /* => 如果后备设备不忙的话 , 先调用__filemap_fdatawrite_range把脏页面刷掉 */ if (!bdi_write_congested(mapping->backing_dev_info)) /* => WB_SYNC_NONE: 不是同步等待页面刷新完成 , 只是提交了 */ /* => 而fsync和fdatasync是用WB_SYNC_ALL参数等到完成才返回的 */ __filemap_fdatawrite_range(mapping, offset, endbyte, WB_SYNC_NONE);/* First and last FULL page! */ start_index = (offset+(PAGE_CACHE_SIZE-1)) >> PAGE_CACHE_SHIFT; end_index = (endbyte >> PAGE_CACHE_SHIFT);/* => 接下来清除页面缓存 */ if (end_index >= start_index) { unsigned long count = invalidate_mapping_pages(mapping, start_index, end_index);/* * If fewer pages were invalidated than expected then * it is possible that some of the pages were on * a per-cpu pagevec for a remote CPU. Drain all * pagevecs and try again. */ if (count < (end_index - start_index + 1)) { lru_add_drain_all(); invalidate_mapping_pages(mapping, start_index, end_index); } } break;… … … …}我们可以看到如果后台系统不忙的话 , 会先调用__filemap_fdatawrite_range把脏页面刷掉 , 刷页面用的参数是是 WB_SYNC_NONE , 也就是说不是同步等待页面刷新完成 , 提交完写脏页后立即返回了 。
然后再调invalidate_mapping_pages清除页面 , 回收内存:


推荐阅读