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_accessed
对page
进行PG_referenced
和PG_active
的标记和清除,并在active list
和inactive
之间进行迁移;
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的主要内容,如果未能解决你的问题,请参考以下文章
Linux 内核 内存管理物理分配页 ⑧ ( __alloc_pages_slowpath 慢速路径调用函数源码分析 | 获取首选内存区域 | 异步回收内存页 | 最低水线也分配 | 直接分配 )
Linux 内核 内存管理物理分配页 ⑥ ( get_page_from_freelist 快速路径调用函数源码分析 | 检查内存区域水线 | 判定节点回收 | 判定回收距离 | 回收分配页 )