【linux3.10】从内存反向映射(reverse mapping)的角度来看vma的组织和使用

准备

名词解释

虚拟内存vma

  假如你来设计linux内核,你会如何管理有限的内存呢?最直观最简单的一种方法是,只要有人(进程)申请内存资源,你就直接给它。但这样有个问题,如果某些人(进程)申请了很多内存但却不使用,那这些内存岂不是被白白浪费掉了?于是你想了个办法,有人申请内存时,先拿小本本记录下来某人(进程)申请了多大内存,但先不给他使用,防止他拿了内存却不干事,等到他真的要用的时候,再根据本本上的记录来给他实际分配内存。这样,虚拟内存的概念就有了,你在小本本上的每一条记录,都是一块虚拟内存。
  linux内核的虚拟内存也是如此,只是一条条的记录而已,只不过内核给这条记录起了一个高大上的名字,叫做虚拟内存,也叫做vma(virtual mempry area)。
  虚拟内存的概念很简单,但实现起来却是细节很多。比如你要管理你的这些记录,哪些被分配了,哪些没有被分配,还要尽量减少内存碎片,而且内核的虚拟内存数量很多,必须要考虑一种高效的方法来管理,还有name space cgroup的概念等等,这些种种因素使虚拟内存实现起来非常复杂。

内存映射

  随着内存越来越大,一个本本已经有点不够用了,你开始用多个本本来组织这些记录(虚拟内存),但同时使用多个本本有很不方便。思前想后,你决定这样来设计管理策略:你把所有的本本分为3堆,每堆有10个本本,并且手里有一个单独的本本称为G。把三堆从0到2编号,每一堆里的10个本本也都从0到9编号。你和用户做了个约定,在用户申请内存时,你向它返回一个四位数的数字。第一个数字,代表你手里的本本G的第几条记录,如果是9则是第9条记录,而这条记录会记录下一堆本本中要找的编号。第二个数字代表要找的第二个本本的第几条记录,而这条记录也会记录下一堆中要找的本本的编号。以此类推,最后一个本本会记录要分配的内存的编号。
  这样讲有些抽象,举例来讲。用户申请内存时得到的数字为9625,那么他要这样来找这块的物理内存。首先,查阅你手里的本本里的第9条(第1个数字为9)记录,发现这条记录的值是3。那么要接着去第0堆本本中找第3个本本的第6条(第2个数字为6)记录,发现这条记录的值为4。那么接着去第1堆本本找编号为4的本本中的第2条(第3个数字为2)记录,发现这条记录为7。最后去找第2堆中编号为7的本本中的第5条(第4个数字为5)记录,而这条记录有一个编号比如为1234,那么用户最终拿到编号为1234的物理内存去使用。
在这里插入图片描述

上面查找的过程也就是地址映射的过程,对应到真实的地址映射,手里的本本G相当于全局页目录表pgd ,每一个本本相当于一个页目录表,最后一个本本相当于页表,每一个本本中的数字相当于页目录表中的基址,而分配的数字相当于页目录表的偏移。
在这里插入图片描述

page(页)

  page是系统管理内存的基本单位。系统在初始化的时候把所有的物理内存划分为一个一个的page,一般来说,每个Page对应一块4k大小的物理内存。

page fault

  在真实的系统中,比上面的情况还要再复杂一些。用户拿到虚拟地址后,第一次翻译物理地址的过程会失败,因为这时它还没有自己的物理地址,所以在页目录表中对应的表项为空。查找失败后,会触发page fault,在page fault的中断处理中实际分配page页以及填充物理内存。

文件页

指那些内容需要从文件读取的页,比如mmap,或者进程的text段、data段

匿名页

指那些内容不需从文件读取的页,比如进程的堆、栈分配的内存都是匿名页。
在这里插入图片描述

反向映射

  知道了什么是正向映射后,再来理解反向映射就容易的多了。如前所述,正向映射是根据虚拟内存找到对应的物理内存。那么反向映射就是要根据物理内存找到所有对应的虚拟内存。
  因为page结构体实际也是要占据物理内存的,而内存是很珍贵的,不能简单地在page结构体中增加字段,那会消耗更多的物理内存来保存Page结构体。这为反向映射的实现增加了难度。
在这里插入图片描述

相关结构体

  在讨论相关结构体之前,我们先讨论一些设计相关的问题。我们知道,子进程f在fork后会分配自己的虚拟内存vma,但在写内存之前会短暂地和父进程共享同一块内存,直到子进程读写它自己的内存,操作系统才会给子进程分配独立的内存空间,也就是子进程读写内存之前它自己的vma相关的page还是父进程的page。现在我们考虑这样一个场景:父进程fork出一个子进程,子进程此时并无动作,说明它的内存是和父进程共享的,而父进程此时要调用反向映射,意味着我们的反向映射要根据父进程的Page页找到子进程的vma,再通过vma间接找到子进程的mm_struct从而拿到子进程的页表,从而修改子进程的页表。
  我们接下来带着这个问题阅读下面的代码:如何根据父进程的page找到子进程的vma,且子进程分配内存后,相关的vma不能被父进程找到。

struct vm_area_struct

  这个结构体就是linux中的虚拟内存,对应我们上面提到的本本中的一条条记录,它有很多字段,这里我们只讨论反向映射相关。

struct vm_area_struct {
   
	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap interval tree, or
	 * linkage of vma in the address_space->i_mmap_nonlinear list.
	 */
	union {
   
		struct {
   
			struct rb_node rb;
			unsigned long rb_subtree_last;
		} linear;
		struct list_head nonlinear;
	} shared;

	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 */
	struct list_head anon_vma_chain; /* Serialized by mmap_sem &
					  * page_table_lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */
};

  其中shared是用于文件页的反向映射,shared中的nolinear字段用于非线性映射,这里不讨论。anon_vma_chainanon_vma是用于匿名页的反向映射。
  对于文件vma来说,同一个文件的所有vma都被链接到file->f_mapping->i_mmap树中,反向映射时,只要遍历这棵树,筛选出符和条件的vma即可
对于匿名vma来说,

struct anon_vma

/*
 * The anon_vma heads a list of private "related" vmas, to scan if
 * an anonymous page pointing to this anon_vma needs to be unmapped:
 * the vmas on the list will be related by forking, or by splitting.
 *
 * Since vmas come and go as they are split and merged (particularly
 * in mprotect), the mapping field of an anonymous page cannot point
 * directly to a vma: instead it points to an anon_vma, on whose list
 * the related vmas can be easily linked or unlinked.
 *
 * After unlinking the last vma on the list, we must garbage collect
 * the anon_vma object itself: we're guaranteed no page can be
 * pointing to this anon_vma once its vma list is empty.
 */
struct anon_vma {
   
	struct anon_vma *root;		/* Root of this anon_vma tree */
	struct rw_semaphore rwsem;	/* W: modification, R: walking the list */
	/*
	 * The refcount is taken on an anon_vma when there is no
	 * guarantee that the vma of page tables will exist for
	 * the duration of the operation. A caller that takes
	 * the reference is responsible for clearing up the
	 * anon_vma if they are the last user on release
	 */
	atomic_t refcount;

	/*
	 * NOTE: the LSB of the rb_root.rb_node is set by
	 * mm_take_all_locks() _after_ taking the above lock. So the
	 * rb_root must only be read/written after taking the above lock
	 * to be sure to see a valid next pointer. The LSB bit itself
	 * is serialized by a system wide lock only visible to
	 * mm_take_all_locks() (mm_all_locks_mutex).
	 */
	struct rb_root rb_root;	/* Interval tree of private "related" vmas */
};

  从这个结构体的大一段注释中可以看出,这个结构体把所有与同一个anon_vma相关的vma串在了一起,如果想要unmap这个anon_vma相关的vma,遍历它们即可。并且也简要地说明了为什么设计这个结构:因为vma会不停地随着合并和切分而变化,所以匿名页不能直接指向一个vma。所以设计一个中间结构anon_vma,使匿名页指向这个中间结构anon_vma,而anon_vma保存了所有相关的vma,这样使匿名页间接地保存了所有相关的vma。这样我们也理解了结构体的名称从何而来,把anon_page和vma联系起来的结构体,就叫anon_vma。
其中比较重要的成员是rootrb_root。其中root代表了一颗anon_vma tree,我们下面会讲到这棵树是如何组织的。rb_root链接了所有与这个anon_vma相关的vma,反向映射时只要遍历它即可。

struct anon_vma_chain

/*
 * The copy-on-write semantics of fork mean that an anon_vma
 * can become associated with multiple processes. Furthermore,
 * each child process will have its own anon_vma, where new
 * pages for that process are instantiated.
 *
 * This structure allows us to find the anon_vmas associated
 * with a VMA, or the VMAs associated with an anon_vma.
 * The "same_vma" list contains the anon_vma_chains linking
 * all the anon_vmas associated with this VMA.
 * The "rb" field indexes on an interval tree the anon_vma_chains
 * which link all the VMAs associated with this anon_vma.
 */
struct anon_vma_chain {
   
	struct vm_area_struct *vma;
	struct anon_vma *anon_vma;
	struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */
	struct rb_node rb;			/* locked by anon_vma->rwsem */
	unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
	unsigned long cached_vma_start, cached_vma_last;
#endif
};

  这个结构体最主要的作用,就是把anon_vma和vma连接起来。我们下面会看到,一个anon_vma会关联多个vma,一个vma也会关联多个anon_vma。
same_vma

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值