一、page cache简介
page cache,又称pcache,其中文名称为页高速缓冲存储器,简称页高缓。page cache的大小为一页,通常为4K。在linux读写文件时,它用于缓存文件的逻辑内容,从而加快对磁盘上映像和数据的访问。
二、page cache的功能详解
在从外存的一页到内存的一页的映射过程中,page cache与buffer cache、swap cache共同实现了高速缓存功能,以下是其简单映射图,
外存的一页(分解为几块,可能不连续)
|
|
物理磁盘的磁盘块
|
|
内存的buffer cache
|
|
内存的一页(由一个页框划分的几个连续buffer cache构成)
|
|
页高缓系统
在这个过程中,内存管理系统和VFS与page cache交互,内存管理系统负责维护每项page cache的分配和回收,同时在使用memory map方式访问时负责建立映射;VFS负责page cache与用户空间的数据交换。
三、page cache的管理
在Linux内核中,文件的每个数据块最多只能对应一个page cache项,它通过两个数据结构来管理这些cache项,一个是radix tree,另一个是双向链表。
Radix tree是一种搜索树,Linux内核利用这个数据结构,快速查找脏的(dirty)和回写的(writeback)页面,得到其文件内偏移,从而对page cache进行快速定位。图1是radix tree的一个示意图,该radix tree的分叉为4(22),树高为4,用来快速定位8位文件内偏移。
另一个数据结构是双向链表,Linux内核为每一片物理内存区域(zone)维护active_list和 inactive_list两个双向链表,这两个list主要用来实现物理内存的回收。这两个链表上除了文件Cache之外,还包括其它匿名 (Anonymous)内存,如进程堆栈等。
四、page cache相关API及其实现
Linux内核中与文件Cache操作相关的API有很多,按其使用方式可以分成两类:一类是以拷贝方式操作的相关接口,如read/write/sendfile等,其中sendfile在2.6系列的内核中已经不再支持;另一类是以地址映射方式操作的相关接口,如mmap等。
第一种类型的API在不同文件的Cache之间或者Cache与应用程序所提供的用户空间buffer之间拷贝数据,其实现原理如图2所示。
第二种类型的API将Cache项映射到用户空间,使得应用程序可以像使用内存指针一样访问文件,Memory map访问Cache的方式在内核中是采用请求页面机制实现的,其工作过程如图3所示。
首先,应用程序调用mmap(图中1),陷入到内核中后调用do_mmap_pgoff(图中2)。该函数从应用程序的地址空间中分配一段区域作为映射的内存地址,并使用一个VMA(vm_area_struct)结构代表该区域,之后就返回到应用程序(图中3)。当应用程序访问mmap所返回的地址指针时(图中4),由于虚实映射尚未建立,会触发缺页中断(图中5)。之后系统会调用缺页中断处理函数(图中6),在缺页中断处理函数中,内核通过相应区域的 VMA结构判断出该区域属于文件映射,于是调用具体文件系统的接口读入相应的Page Cache项(图中7、8、9),并填写相应的虚实映射表。经过这些步骤之后,应用程序就可以正常访问相应的内存区域了。
五、pagecache与inode cache关系
page cache 在代码中又称 inode page cache,足以显示page cache 和inode紧密关联. 加入page cache 和加入inode cache是同一个意思.加入page cache意味着同时加入page cache hash表和inode queue(也建立了page和addr sapce的关系). 见函数add_to_page_cache_locked,__add_to_page_cache即可取证.从page cache 删除在程序中叫__remove_inode_page,再次显示inode 和page cache的"一体化".
六、应用
linux 对文件的访问除了显示使用O_DIRECT标志,所有的访问都会经过pagecache。例如open一个文件,通过read系统调用读取数据的流程:
用户态发起read操作,传入要读取的文件的fd、偏移以及数据存储空间
陷入内核态vfs_read函数,generic_file_aio_read-->do_generic_file_read
do_generic_file_read的流程可以简化如下:
start:
if (pagecache中存在文件和偏移的页面) {
进行相应的预读策略,并将相应的内容读取到用户指定空间
} else {
生成通用块层读取策略,提交给通用块层,本进程进入uninterruptible状态,等待读取完成。
goto start;
}
从上面的流程可以看出,在非DIRECT方式下,磁盘文件操作都是经过pagecache的。
每个文件在内存中最多有一个pagecache镜像,也就是说对应同一个文件,所有的进程共享pagecache。后面讲到的vmtouch正式利用这个特性才得以正常工作。
如果pagecache能够合理的存储文件的话,会极大的提供进程运行的效率(内存操作效率 >> 磁盘操作效率)。
七、系统全局的pagecache和优化
free
/proc/sys/vm/drop_caches
/proc/sys/vm/pagecache_limit_mb
通过free命令可以看到当前系统下总共有多少内存用于pagecache
linux-19:~ # free
total used free shared buffers cached
Mem: 8057748 6803284 1254464 0 430468 4896108
-/+ buffers/cache: 1476708 6581040
Swap: 8393920 8824 8385096
从命令的输出可以看到总共有4G多的内存用于pagecache。
虽然pagecache可以显著的优化对文件操作的性能,但是过多的pagecache会导致系统空闲内存减少,影响操作系统的整体性能,比如当空闲内存减少后会影响未进入pagecache的文件的读取性能。
我们可以通过命令强制内核释放掉pagecache,命令如下所示:
echo “1” > /proc/sys/vm/drop_caches
上述命令执行完后,会暂时释放掉pagecache,如果要想一直保证系统有足够的空闲内存,可以通过一个crontab来定时执行上面的命令(这可不是内核的风格^_^)。于是Vaidyananthan Srinivasan添加了一个参数pagecache_limit_mb来防止pagecache积聚导致系统性能下降的问题(从2.6.20内核开始演进过来,之前的参数包括pagecache_ratio,不过最新的内核都删除了这些参数,使用了cleancache)。
关于pagecache_limit_mb参加附录1,关于cleancache参加附录2
八、基于文件的pagecache和优化
vmtouch
在做性能优化的时候,经常会用到查看特定的某些文件的pagecache情况,那么内核是不是提供相应的工具呢?
很遗憾,或许是因为这个工具太过简单,以至于一直没能进入内核的tools列表。关于这个工具的代码详见附录3
这个工具提供了查看指定文件的pagecache情况,以及强制对文件进行pagecache和清除pagecache。这里主要使用了两个系统调用mincore和posix_fadvise。
例如查看文件file1的pagecache情况
linux-12:~/performance/testfile # /root/vmtouch file1
Files: 1
Directories: 0
Resident Pages: 302428/302428 1G/1G 100%
Elapsed: 0.025512 seconds
从这里可以看出,文件file1一共有302428个页面,全部都在内存里面。