文章目录
- 描述
- 原理
- 作用
- 测试
- 实现
- 缓存控制
描述
页高速缓存(page cache)是从实际物理内存中开辟出来一部分内存空间,用作操作系统的磁盘读写缓存。比如客户端写入的数据并不直接写入磁盘,而是写入到这一段物理内存中即代表已经写完,这样由内存本身的高速读写性能是能够提升系统整体io性能。
基本管理单位:页
原理
页高速缓存的淘汰原理是根据:时间局部原理,空间局部原理;即最长时间未被访问(时间局部性)或者被进程引用次数最少(空间局部性)的页面最先被淘汰
关于页高速缓存存在操作系统的哪个层次可以参考如下图:

作用
我们用户使用系统调用读写(read/write)时,页高速缓存的基本工作原理如下图:
即写请求先写入page cache,再由page cache落盘
读请求同样先从page cache 中读,入无法读到,则由page cache从磁盘读出
落盘方式:
写请求在数据写入到page cahe中后会直接返回客户端写入完毕,但是数据本身并未完全写入到磁盘,而是等到page cache达到操作系统刷新缓存的比例之后才会将缓存中经过淘汰算法计算的页脏数据同步到磁盘。
测试
命令测试
dd if=/dev/zero of=./test.dat bs=1M count=10 #向文件中写入数据
cat /proc/meminfo |grep Dirty #查看内存脏页情况
sync #将内存中的脏页同步到磁盘
cat /proc/meminfo |grep Dirty #再次查看内存脏也情况
代码测试
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#define NAME "testfile"
int main()
{
char buf[100];
memset(buf,0,100);
//带有SYNC标记得打开文件
int fd=open(NAME,O_CREAT|O_RDWR|O_SYNC, 0666);
fgets(buf,100,stdin);
int result = write(fd,buf,sizeof(buf));
if ( -1 == result) {
printf("write failed\n");
_exit(-1);
}
printf("write size is %d\n",result);
fsync(fd);
printf("sync the page cache\n");
return 0;
}
实现
每当内核开始执行一个页IO操作时,就先到高速缓存中找。这样就可以大大减少磁盘操作。
页高速缓存的实现中起主要作用的一个对象为
address_space
数据结构
3.10.0-957.5.1.el7.x86_64/include/linux/fs.h
struct address_space {
/*通常情况下,会与一个索引节点(inode)关联,这时host域就会指向该索引节点
如果关联对象不是一个索引节点的话,比如address_space和swapper关联时,这时host域会被置为NULL*/
struct inode *host; /* owning inode */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* page_tree lock */
unsigned int i_mmap_writable; /* VM_SHARED ma count */
/*i_mmap字段是一个优先搜索树,它的搜索范围包含了在address_sapce中私有的和共享的页面*/
struct prio_tree_root i_mmap; /* list of all mappings */
struct list_head i_mmap_nonlinear; /* VM_NONLINEAR ma list */
spinlock_t i_mmap_lock; /* i_mmap lock */
atomic_t truncate_count; /* truncate re count */
/*nrpages反应了address_space空间的大小*/
unsigned long nrpages; /* total number of pages */
pgoff_t writeback_index; /* writeback start offset */
/*a_ops域指向地址空间对象中的操作函数表*/
struct address_space_operations *a_ops; /* operations table */
unsigned long flags; /* gfp_mask and error flags */
struct backing_dev_info *backing_dev_info; /* read-ahead information */
spinlock_t private_lock; /* private lock */
struct list_head private_list; /* private list */
struct address_space *assoc_mapping; /* associated buffers */
};
操作函数列表
address_space_operations
定义如下,其中主要的为
writepage
和
readpage
- readpage根据file即address_spaces的mapping尝试从page cache中读缓存页,如果搜索页不在搜索树(基数树radix tree)管理的缓存页面中,则内核会重新创建一个缓存也加入到搜索树中。
- writepage 则根据当前内存页page是否有回写标记,即是否为脏页;如果是则将当前内存页的数据写入到磁盘,并从搜素树中删除该页
mm/filemap.c
struct address_space_operations {
int (*writepage)(struct page *, struct writeback_control *);
int (*readpage) (struct file *, struct page *);
int (*sync_page) (struct page *);
int (*writepages) (struct address_space *, struct writeback_control *);
int (*set_page_dirty) (struct page *);
int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);
int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
int (*commit_write) (struct file *, struct page *, unsigned, unsigned);
sector_t (*bmap)(struct address_space *, sector_t);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);
};
page cache的页高速缓存管理如下:
根据上图我们知道
- 读文件流程如下
- 数据结构关联:
指向inode -> i_mapping
对象,address_space
指向address_space->host
inode
- 数据结构关联:
指向页缓存page->mapping
的owner
address_space
- 系统调用传参:文件描述符+文件偏移地址
- 操作系统找到文件
,根据偏移量到页缓存中查找address_space
page
- 若查找到,返回数据到用户空间
- 否则,内核新建一个
并加入到页缓存,数据从磁盘载入该项page
- 调用
方法将数据返回给用户空间readpage
- 写文件流程如下:
- 数据结构关联:
指向inode -> i_mapping
对象,address_space
指向address_space->host
inode
- 数据结构关联:
指向页缓存page->mapping
的owner
address_space
- 系统调用传参:文件描述符+文件偏移地址
- 操作系统找到文件
,根据偏移量到页缓存中查找address_space
page
- 若查找到,将数据写入到该缓存中,该页成为脏页
- 若没有查找到,向缓存的计数树管理的页面中添加一个新的页面,并将用户空间的数据写入到该页面
- 当数据满足页缓存的时间或空间原理时,使用pdflush后台回写例程来将脏页数据会写到磁盘
pdfush
的实现如下:
pdflush线程在系统中的空闲内存低于一个特定的阈值时,将脏页刷新回磁盘;
该后台回写例程的目的在于在可用物理内存过低时,释放脏页以重新获得内存
缓存控制
- 获取参数配置
sysctl -a | grep dirty
-
设置参数
修改配置文件
/etc/sysctl.conf
,增加参数
vm.dirty_background_ratio = 5
执行vm.dirty_ratio = 10
即可生效sysctl -p