linux 文件IO与内存映射:page cache页高速缓存

描述

页高速缓存(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定义如下,其中主要的为writepagereadpage

  • 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的页高速缓存管理如下:
在这里插入图片描述
根据上图我们知道

  • 读文件流程如下
    1. 数据结构关联:inode -> i_mapping 指向address_space对象,address_space->host指向inode
    2. 数据结构关联:page->mapping 指向页缓存owneraddress_space
    3. 系统调用传参:文件描述符+文件偏移地址
    4. 操作系统找到文件address_space,根据偏移量到页缓存中查找page
    5. 若查找到,返回数据到用户空间
    6. 否则,内核新建一个page并加入到页缓存,数据从磁盘载入该项
    7. 调用readpage方法将数据返回给用户空间
  • 写文件流程如下:
    1. 数据结构关联:inode -> i_mapping 指向address_space对象,address_space->host指向inode
    2. 数据结构关联:page->mapping 指向页缓存owneraddress_space
    3. 系统调用传参:文件描述符+文件偏移地址
    4. 操作系统找到文件address_space,根据偏移量到页缓存中查找page
    5. 若查找到,将数据写入到该缓存中,该页成为脏页
    6. 若没有查找到,向缓存的计数树管理的页面中添加一个新的页面,并将用户空间的数据写入到该页面
    7. 当数据满足页缓存的时间或空间原理时,使用pdflush后台回写例程来将脏页数据会写到磁盘

    pdfush的实现如下:
    pdflush线程在系统中的空闲内存低于一个特定的阈值时,将脏页刷新回磁盘;
    该后台回写例程的目的在于在可用物理内存过低时,释放脏页以重新获得内存

    其中控制内存阈值的系统调用设置为:dirty_background_ratio,一旦页高速缓存中搜索树管理的内存页占总内存的比例超过这个数值时,内核便会调用函数wakeup_bdflush() 唤醒一个pdflush线程,随后pdflush线程进一步调用函数background_writeout()开始将脏页写会到磁盘,函数background_writeout()需要一个长整型参数,该参数指定试图写回的页面数目。直到满足以下两个条件:
    1.已经有指定的最小数目的页被写回到磁盘。
    2.已使用内存页已经减少,小于阈值dirty_background_ratio

缓存控制

关于控制操作系统页高速缓存占用内存比例可以控制如下两个参数:dirty_ratiodirty_background_ratio
关于dirty_background_ratio已经讲过,即脏页比例达到内存总空间的dirty_background_ratio阈值时,pdflush开始刷缓存,同时页高速缓存也可以接收io
关于dirty_ratio,即脏页比例达到内存总空间的dirty_ratio时,页高速缓存不接受客户端io,仅由pdflush刷数据

  • 获取参数配置 sysctl -a | grep dirty
  • 设置参数
    修改配置文件/etc/sysctl.conf,增加参数
    vm.dirty_background_ratio = 5
    vm.dirty_ratio = 10
    执行sysctl -p即可生效
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值