Linux内存回收机制page cache

Posted bubbleben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内存回收机制page cache相关的知识,希望对你有一定的参考价值。


每个进程打开一个文件的时候,都会生成一个表示这个文件的struct file,但是文件的struct inode只有一个,inode才是文件的唯一标识,指向struct address_space的指针就是内嵌在inode的;
page cache中,每个page都有对应的文件,address_space管理一个文件在内存中缓存的所有page,它将属于同一文件的page联系起来,将这些page的操作方法与文件所属的文件系统联系起来;

本文基于linux-5.0内核源码分析

include/linux/fs.h
include/linux/pagemap.h

mm/filemap.c

1. file

// 描述一个被打开的文件
struct file 
	union 
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	 f_u;
    // 文件路径
	struct path		f_path;
    // 文件对应的inode
	struct inode		*f_inode;	/* cached value */
    // 文件操作函数: 包含open, write, read, mmap等
	const struct file_operations	*f_op;

	/*
	 * Protects f_ep, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	enum rw_hint		f_write_hint;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	struct list_head	f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
    // 文件对应的address_space
	struct address_space	*f_mapping;
	errseq_t		f_wb_err;
 __randomize_layout

2. inode

// 每个文件都使用1个inode表示
struct inode 
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;

#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
    // inode操作函数: 包含rmdir, mkdir, symlink, unlink等
	const struct inode_operations	*i_op;
    // 指向super_block的指针
	struct super_block	*i_sb;
    // 指向address_space的指针
	struct address_space	*i_mapping;
    ......
 __randomize_layout;

3. address_space

struct address_space 
    // 指向其对应文件的inode
	struct inode		*host;
    // 4.14-186版本使用radix树存储address_space中的所有page
	// struct radix_tree_root	i_pages;	/* cached pages */
    // 5.0版本使用xarray
	struct xarray		i_pages;
	gfp_t			gfp_mask;	/* implicit gfp mask for allocations */
	atomic_t		i_mmap_writable;/* count VM_SHARED mappings */
    // 管理address_space对应文件的多个vma映射
	struct rb_root_cached	i_mmap;		/* tree of private and shared mappings */
	struct rw_semaphore	i_mmap_rwsem;	/* protect tree, count, list */    
    // 包含的所有页数
	unsigned long		nrpages;	/* number of total pages */
	/* number of shadow or DAX exceptional entries */
	unsigned long		nrexceptional;
	pgoff_t			writeback_index;/* writeback starts here */
    // 定义page cache与磁盘(backing store)交互的一系列操作函数: 包括writepage, readpage, freepage和putback_page等
	const struct address_space_operations *a_ops;	/* methods */
	unsigned long		flags;		/* error bits */
	errseq_t		wb_err;
	spinlock_t		private_lock;	/* for use by the address_space */
	struct list_head	private_list;	/* for use by the address_space */
	void			*private_data;	/* ditto */
 __attribute__((aligned(sizeof(long)))) __randomize_layout;

4. find_get_page

// mapping: 被搜索的文件对应的address_space
// offset: page在address_space中的偏移量
// 如果是page cache则返回对应的page并将引用计数加1, 否则返回NULL
static inline struct page *find_get_page(struct address_space *mapping,
					pgoff_t offset)

	return pagecache_get_page(mapping, offset, 0, 0);

通过find_get_page查找page cache
(1)如果在page cache中未找到,就会触发page fault,然后调用__page_cache_alloc在内存中分配若干物理页面,最后将数据从磁盘对应位置复制到内存;
(2)如果在page cache中找到,就会调用mark_page_accessedpage进行PG_referencedPG_active的标记和清除,并在active listinactive之间进行迁移;

4.1 pagecache_get_page

struct page *pagecache_get_page(struct address_space *mapping, pgoff_t offset,
	int fgp_flags, gfp_t gfp_mask)

	struct page *page;

repeat:
    // 从address_space的xarray中根据偏移量查找缓存的page
	page = find_get_entry(mapping, offset);
	if (xa_is_value(page))
		page = NULL;
    // 查找失败进入no_page流程
	if (!page)
		goto no_page;

	if (fgp_flags & FGP_LOCK) 
		if (fgp_flags & FGP_NOWAIT) 
			if (!trylock_page(page)) 
				put_page(page);
				return NULL;
			
		 else 
			lock_page(page);
		

		/* Has the page been truncated? */
		if (unlikely(page->mapping != mapping)) 
			unlock_page(page);
			put_page(page);
			goto repeat;
		
		VM_BUG_ON_PAGE(page->index != offset, page);
	

    // 查找page时fgp_flags为0, page不会被引用
	if (fgp_flags & FGP_ACCESSED)
		mark_page_accessed(page);

no_page:
    // 查找失败, 但是允许申请新的page
	if (!page && (fgp_flags & FGP_CREAT)) 
		int err;
		if ((fgp_flags & FGP_WRITE) && mapping_cap_account_dirty(mapping))
			gfp_mask |= __GFP_WRITE;
		if (fgp_flags & FGP_NOFS)
			gfp_mask &= ~__GFP_FS;

        // 调用alloc_pages分配0阶的page
		page = __page_cache_alloc(gfp_mask);
		if (!page)
			return NULL;

		if (WARN_ON_ONCE(!(fgp_flags & FGP_LOCK)))
			fgp_flags |= FGP_LOCK;

		/* Init accessed so avoid atomic mark_page_accessed later */
        // 设置PG_referenced标志位
		if (fgp_flags & FGP_ACCESSED)
			__SetPageReferenced(page);

        // 将page添加到address_space的缓存
		err = add_to_page_cache_lru(page, mapping, offset, gfp_mask);
		if (unlikely(err)) 
			put_page(page);
			page = NULL;
			if (err == -EEXIST)
				goto repeat;
		
	

	return page;

4.2 add_to_page_cache_lru

int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
				pgoff_t offset, gfp_t gfp_mask)

	void *shadow = NULL;
	int ret;

    // 设置PG_locked
	__SetPageLocked(page);
    // 将page加入xarray缓存
	ret = __add_to_page_cache_locked(page, mapping, offset,
					 gfp_mask, &shadow);

    if (unlikely(ret))
		__ClearPageLocked(page);
	else 
		WARN_ON_ONCE(PageActive(page));
		if (!(gfp_mask & __GFP_WRITE) && shadow)
			workingset_refault(page, shadow);
        // 将page加入lru链表
		lru_cache_add(page);
	
	return ret;

5. read_cache_page

struct page *read_cache_page(struct address_space *mapping,
				pgoff_t index,
				int (*filler)(void *, struct page *),
				void *data)

	return do_read_cache_page(mapping, index, filler, data, mapping_gfp_mask(mapping));

5.1 do_read_cache_page

static struct page *do_read_cache_page(struct address_space *mapping,
				pgoff_t index,
				int (*filler)(void *, struct page *),
				void *data,
				gfp_t gfp)

	struct page *page;
	int err;
repeat:
    // find_get_page查找page cache失败而且页不允许申请新的page
	page = find_get_page(mapping, index);
	if (!page) 
        // 调用__page_cache_alloc分配page cache
		page = __page_cache_alloc(gfp);
        // 如果仍然分配失败, 则返回错误码ENOMEM
		if (!page)
			return ERR_PTR(-ENOMEM);
        // 将page加入address_space的xarray缓存
		err = add_to_page_cache_lru(page, mapping, index, gfp);
		if (unlikely(err)) 
			put_page(page);
			if (err == -EEXIST)
				goto repeat;
			/* Presumably ENOMEM for radix tree node */
			return ERR_PTR(err);
		

filler:
		err = filler(data, page);
		if (err < 0) 
			put_page(page);
			return ERR_PTR(err);
		

        // 读取文件数据到page cache
		page = wait_on_page_read(page);
		if (IS_ERR(page))
			return page;
        // 读取成功走out流程
		goto out;
	
	if (PageUptodate(page))
		goto out;

    // 读取文件数据到page cache
	wait_on_page_locked(page);
	if (PageUptodate(page))
		goto out;

	/* Distinguish between all the cases under the safety of the lock */
	lock_page(page);

	/* Case c or d, restart the operation */
	if (!page->mapping) 
		unlock_page(page);
		put_page(page);
		goto repeat;
	

	/* Someone else locked and filled the page in a very small window */
	if (PageUptodate(page)) 
		unlock_page(page);
		goto out;
	

	ClearPageError(page);
	goto filler;

out:
    // 将page标记为引用状态
	mark_page_accessed(page);
	return page;

以上是关于Linux内存回收机制page cache的主要内容,如果未能解决你的问题,请参考以下文章

内核机制引起Page Cache被回收而产生的业务性能下降

linux内存管理概述

Linux 内核 内存管理物理分配页 ⑧ ( __alloc_pages_slowpath 慢速路径调用函数源码分析 | 获取首选内存区域 | 异步回收内存页 | 最低水线也分配 | 直接分配 )

Linux 内核 内存管理物理分配页 ⑥ ( get_page_from_freelist 快速路径调用函数源码分析 | 检查内存区域水线 | 判定节点回收 | 判定回收距离 | 回收分配页 )

Linux内存中的Cache真的能被回收么

linux kernel内存回收机制