代码编织梦想


每个进程打开一个文件的时候,都会生成一个表示这个文件的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;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44901612/article/details/127002674

05 | Linux内存回收——Swap机制-爱代码爱编程

前言 上一节,我们通过一个斐波那契数列的案例,学习了内存泄漏的分析。如果在程序中直接或间接地分配了动态内存,一定要记得释放掉它们,否则就会导致内存泄漏,严重时甚至会耗尽系统内存。 反过来讲,当发生了内存泄漏时,或者运行了大内存的应用程序,导致系统的内存资源紧张时,系统又会如何应对呢? 在内存基础篇我们已经学过,这其实会导致两种可能结果,内存回收和 O

Linux 内存回收机制-爱代码爱编程

1.回收整体框图 __get_free_pages:返回的是虚拟地址; alloc_pages:返回的是struct page*结构; unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) { struct page *page; /* * __get_fre

linux 内存回收机制-爱代码爱编程

linux 4.14 aarch32 一、内存回收的对象 page_cache 文件映射缓存,文件读写和代码段等占用一些内存,缓存文件以提高文件访问的效率,当内存紧张的时候page_cache会被丢弃或者协会磁盘后丢弃,以回收物理内存 匿名映射因为不与文件关联想要回收其所占空间不能想pages_pache那样直接丢弃,利用外存空间将部分内存swap出

linux进程回收机制,linux内存回收机制-爱代码爱编程

1回收哪些页面 Page cache; 用户地址空间的内存映射页面; Slab缓存:如dentry和inode cache; 匿名页:进程堆栈和mmap匿名映射内存区;回收前先换置到swap; 2何时回收 Kswapd定期唤醒:当系统空闲内存小于阈值则进行页面回收; 直接页面回收:假设操作系统需要通过伙伴系统为用户进程分配一大块内存,或者

linux cache 进程,深入理解linux中的page cache-爱代码爱编程

Buffer Cache Buffer cache是指磁盘设备上的raw data(指不以文件的方式组织)以block为单位在内存中的缓存,早在1975年发布的Unix第六版就有了它的雏形,Linux最开始也只有buffer cache。事实上,page cache是1995年发行的1.3.50版本中才引入的。不同于buffer cache以磁盘的b

linux将cache中的内存回收,Linux内存中的Cache真的能被回收么?-爱代码爱编程

原标题:Linux内存中的Cache真的能被回收么? | 作者:zorro | 来自:http://liwei.life/author/zorro/ 在Linux系统中,我们经常用free命令来查看系统内存的使用状态。在一个RHEL6的系统上,free命令的显示内容大概是这样一个状态: [root@tencent64 ~]# free t

linux将cache中的内存回收,技术|Linux 内存中的 Cache 真的能被回收么?-爱代码爱编程

在 Linux 系统中,我们经常用 free 命令来查看系统内存的使用状态。在一个 RHEL6 的系统上,free 命令的显示内容大概是这样一个状态: [root@tencent64 ~]# free total used free shared buffers cached Mem: 132256952 72571772 59685180

linux内存回收(一)---kswapd回收-爱代码爱编程

​ 正式开始十一之旅,有大量的时间将目前工作中遇到的内存回收进行总结下,主要是对内存回收的整个过程进行重新梳理。在linux操作系统中,当内存充足的时候,内核会尽量使用内存作为文件缓存(page cache),从而提高系统的性能。例如page cache缓冲硬盘中的内容,dcache、icache缓存文件系统的数据,这些内容是为了提升性能而设计的,还可以再

linux内存回收(二)--直接内存回收机制-爱代码爱编程

上一章,我们学习了kswapd的内存回收的机制,其本身是一个内核线程,它和调用者的关系是异步的,那么本章就开始学习内核的内存回收的方式。因为在不同的内存分配路径中,会触发不同的内存回收方式,内存回收针对的目标有两种,一种是针对zone的,另一种是针对一个memcg的,而这里我们只讨论针对zone的内存回收,对于内存回收讨论以下三种方式 快速内存回收: 处

linux全局内存机制和cgroup内存机制-爱代码爱编程

1 内存页的分类 linux进程使用的内存页面(page)的主要分类: 1)匿名页:来自堆、栈、数据段,需要换出到交换区 (swap-out)来回收(reclaim)。 2)共享内存:来自匿名 mmap 映射、shmem 共享内存,需要换出到交换区(swap-out)来回收(reclaim)。 3)文件页:来自代码段、文件映射,需要通过页面换出(page

文件读I/O流程与Cache机制和Cache回收-爱代码爱编程

一 读文件过程 用户进程读取文件数据,有两种情形: 1 所需要的数据不在内存中,也即不在页面Cache中。这时就需要直接从磁盘上读取; 2 所需的数据已经在内存中,此时只需从页面Cache中找到具体位置,然后将数据拷贝到用户缓冲区。而不需要进行磁盘I/O操作。 1.1 数据读入页面Cache中 在不是DIRECT_IO的情况下,系统调用read执