0
点赞
收藏
分享

微信扫一扫

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


文章目录

  • ​​描述​​
  • ​​原理​​
  • ​​作用​​
  • ​​测试​​
  • ​​实现​​
  • ​​缓存控制​​

描述

页高速缓存(page cache)是从实际物理内存中开辟出来一部分内存空间,用作操作系统的磁盘读写缓存。比如客户端写入的数据并不直接写入磁盘,而是写入到这一段物理内存中即代表已经写完,这样由内存本身的高速读写性能是能够提升系统整体io性能。
基本管理单位:页

原理

页高速缓存的淘汰原理是根据:时间局部原理,空间局部原理;即最长时间未被访问(时间局部性)或者被进程引用次数最少(空间局部性)的页面最先被淘汰

关于页高速缓存存在操作系统的哪个层次可以参考如下图:

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

作用

我们用户使用系统调用读写(read/write)时,页高速缓存的基本工作原理如下图:

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


即写请求先写入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的页高速缓存管理如下:

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


根据上图我们知道

  • 读文件流程如下
  1. 数据结构关联:​​inode -> i_mapping​​​ 指向​​address_space​​​对象,​​address_space->host​​​指向​​inode​
  2. 数据结构关联:​​page->mapping​​​ 指向页缓存​​owner​​​的​​address_space​
  3. 系统调用传参:文件描述符+文件偏移地址
  4. 操作系统找到文件​​address_space​​​,根据偏移量到页缓存中查找​​page​
  5. 若查找到,返回数据到用户空间
  6. 否则,内核新建一个​​page​​并加入到页缓存,数据从磁盘载入该项
  7. 调用​​readpage​​方法将数据返回给用户空间
  • 写文件流程如下:
  1. 数据结构关联:​​inode -> i_mapping​​​ 指向​​address_space​​​对象,​​address_space->host​​​指向​​inode​
  2. 数据结构关联:​​page->mapping​​​ 指向页缓存​​owner​​​的​​address_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_ratio​​​和​​dirty_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 条评论