深入理解Page Cache要求对Unix/Linux系统内存管理有深入的了解。以Linux为例,在x86系统上它提供了基于三级页表和TLB的虚拟内存管理方式。实现了虚拟地址到物理地址的转换,同时为上层的内核态和用户态调用提供了统一的接口。对于内核态的调用,内核自身除了代码段、静态数据之外的内存的申请和是否而言,它提供了基于页框管理的API; 对应用户态的应用,在申请和使用内存的时候,提供了基于缺页异常的内存描述符合线性区对象的管理。具体需要阅读《深入理解Linux内核》。而和文件系统相关的一部分是内核中的线性区数据结构vm_area_struct。
Page Cache中的内容
Linux 系统中 page cache可以支持多种不同类型的页,比如下面的几种:
a.页中包含普通文件的数据和基于磁盘文件系统的目录;
b.特殊设备和文件基于内存映射后的页;
c.页中的数据是直接从块设备读到的数据(跳过了文件系统层@@@)
d.页中还包含已经交换到磁盘上的数据
e. IPC共享的存储区,比如share memory
页缓存数据结构
上述不同类型的页,对应了在调入和替换的时候不同的操作,为此需要为不同类型的页定义和实现不同的操作,为此引入了address_space作为桥梁,保证具体不同页的操作各自能实现期望的功能,同时向上提供统一的接口。涉及到的数据结构包括: inode/address_space:
struct inode {
…..
struct address_space * i_mapping;
struct address_space i_data;
….
}
struct address_space {
struct list_head clean_pages;
struct list_head dirty_pages;
struct list_head locked_pages;
unsigned long nrpages;
…...
struct address_space_operations * a_ops; // 属主页上的操作的方法
strcut indoe * host; //point to host node
struct vm_area_struct * i_mmap; // 指向私有的内存映射区域
struct vm_area_struct * i_mmap_shared; // 指向公有的内存映射区域
…….
int gfp_mask; // 属主页的内存分配器标志
}
可以看到上面两个数据结构可以相互指向,同时在address_space送还有一个统一的struct a_ops, 看看它里面的具体内容可以看到,它包括下面的操作方法:
. writepage(): 从页写入属主的磁盘映像
. readpage(): 从属主的磁盘映像读到页
.sync_page():启动在页上已经安排的I/O数据传送操作
.prepare_write():准备针对基于磁盘文件的写操作
.commit_write():完成磁盘文件的写操作
.bmap():从文件块索引获得逻辑块号(很重要的一个函数,在IO路径上很关键)
.flushpage():准备删除来自属的磁盘映射的页
.releasepage():由日志文件系统来准备释放页
.direct_IO():数据页的直接传送 (SDDK/nvme上应该考虑用到过,以便充分释放硬件性能)
页散列表
考虑到对大文件的处理过程,页高速缓存里装了许多和这个文件相关的页,此时为了查找指定偏移的文件内容,就需要扫描长长的页描述符链表,这可能变成了一个很耗时的操作,反而影响了page cache设计的初衷。为此,引入了页散列表(hash值)来加快查找。Linux内核中用page_hash_table【】的页描述符指针散列表,以address_space对象的地址和偏移量作为index来找到页表中指定文件偏移的内容。
可以看到,其实SVR4中的实现和Linux中的实现还是很像的,都有数据结构来实现程序中的段,然后基于段进行数据的缓存。
转载于:https://blog.51cto.com/xiamachao/1905121