[深入理解文件系统之八]SVR4中的Page Cache

  在SVR4中,所有文件的读写都会经过Page Cache。不同于有固定长度的物理cache,或者buffer cache, 或者DNLC. PageCache和其他cache的区别在于:它可以根据软件需求换进或者换出。另外也和buffer cache不同:buffer cache用设备和块号来索引cache,而page cache用vnode和offset来索引。


  • page cache的构成

     一个支持seg_map操作(比如当前段的缺页异常处理)的段;

     一个具有多种用途的空闲页表链表(list)


    当用到的page of file data 从memory cache中换出来(离开cache)的时候,它被加入空闲页表;

反正也可从空闲页表中取走。但有一点需要特别注意的是,尽管这个页在free list上,但它的标识符和数据还在,这样一旦内核想再次读那份数据,不用再从磁盘上去取,而直接把个页从free lis中移除即可。 

在unix系统上,实际是按照下面chunk的方式组织的:


The segmap structure is part of the kernel address space and is underpinned by the segmap_datastructure that describes the properties of the segment. The size of the segment is tunable and is split into MAXBSIZE(8KB) chunks where each 8KB chunk represents an 8KB window into a file. Each chunk is referenced by an smapstructure that contains a pointer to a vnode for the file and the offset within the file.


可以看到实际chunks的容量和条目有限,当都用尽的时候,就需要把一些smap搬出去。但考虑到后面可能还有对它的访问,因此就放在一个free list表上,这样下次读的时候,就也不需要再从磁盘读了。由此可见,Page Cache实际也是实现了两层的cache。这可能是后来zfs两级cache的前世了吧。SVR4中Page Cache 相关的数据结构整体的框图如下:

[深入理解文件系统之八]SVR4中的Page Cache


由上图可以看到,每个进程描述符号proc数据结构中的address space 成员 as指向当前进程的一个段表,每个段对应一个用来记录页映射的segmap_data数据结构,其中的smd_sm指向一组包含vnode和偏移的数据结构的链表。


  • PageCache 的操作

申请PageCache的时候:类似buffer cache中getblk()的调用,在page cache中是调用segmap_getmap()来实现的。


addr_tsegmap_getmap(struct seg *seg, vnode_t *vp, uint_t *offset);

这个函数从seg数据结构中s_base 到s_base + s_size的范围内,往其成员segmap_data所指向的smd_sm链表中新增一个表示其起始虚拟地址和长度的节点,然后把这个虚拟地址返回。这里需要注意,在这里只分配了内核虚拟地址,并没有内核上的物理页框和其对应。


而在释放PageCache的时候,调用类似buffer cache 中的brelse()函数。

int segmap_release(struct seg *seg, addr_t addr, u_int flags)


注意:这是SVR4中的Page Cache 和早期其他内核中的page cache 不一样的一个重要区别,在SVR4中segmap_getmap()返回的地址并没有真实物理页框和其对应,而是在后面真正有读操作发生的时候才有物理页框的资源。具体的实现过程,可以通过下面一个完整读的例子看到。


在从文件系统读数据过程中,在Page Cache层会执行一端下面的代码:

...............

kaddr = segmap_getmap(segkmap, vp, 8192);

uiomove(kaddr, 1024, UIO_READ, uiop);

segmap_release(segkmap, kaddr, SM_FREE);

...............

segmap_getmap返回的虚拟地址会传递给uiomove(), 这个函数根据UIO_READ标志开始执行具体的读操作。首先,当它访问kaddr的时候,由于并没有物理页和其对应,于是会触发一个缺页中断,它会从内核中kernel address space (kas)已记录的所有的段的起始地址和长度的信息,索引到当前地址对应的段,进而调用这个段对应的缺页处理程序,核心的示意代码如下:

segkmap->s_ops->fault(seg, addr, ssize, type, rw);


根据传递到fault句柄的s_base和addr参数, 可以从smap数据结构中找到对饮的vnode,然后调用对用的VOP_GETPAGE()函数,他会申请合适的页并从磁盘读回数据。等这些做完之后,page fault 函数才处理完,然后继续执行uiomove()后续的工作。更详细的过程可以参考下面的流程图:

[深入理解文件系统之八]SVR4中的Page Cache

通过上面的流程可以看到,SVR4通过在真正读的时候才出发page fault的机制实现了页只有在真正发生读的时候才分配(延时分配)。而文件写的过程在segmap_release()函数之前和读操作的流程差不多,可能的区别在于它的flags参数,此时可能包括下面的多种:

SM_WRITE:页应该写回到文件,通过VOP_PUTPAGE()

SM_ASYNC:页可以异步写

SM_FREE:页可以释放了

SM_INVAL:无效的页

SM_DONTNEED:文件系统不会再访问该页了


看上去,上面的操作是不是物理cache的基本操作很像呢?






















本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/1904907,如需转载请自行联系原作者

上一篇:关于PowerShell脚本执行时,提示服务器不愿意处理该请求


下一篇:使用IDA调试SO脱壳,环境准备及各步骤原理详解