Linux内存反向映射

数据结构

AV & AVC & VMA

struct anon_vma { //AV是per VMA的
    struct anon_vma *root; //指向祖宗(root)进程的anon_vma
    struct anon_vma *parent; //指向父进程的anon_vma
    struct rw_semaphore rwsem; /* W: modification, R: walking the list */
    atomic_t refcount; //引用计数
    unsigned degree; //Count of child anon_vmas and VMAs which points to this anon_vma.
    struct rb_root rb_root;	/* Interval tree of private "related" vmas */
}
/* AVC有两种功能:
 * 1、连接本进程的VMA和AV,该种情况下AVC是per VMA的
 * 2、连接父(所有前辈进程)进程的AV与子(孙)进程的VMA,该种情况数量为进程的前辈进程数量
 */
struct anon_vma_chain {
	struct vm_area_struct *vma;  //指向VMA
	struct anon_vma *anon_vma; //指向AV
	struct list_head same_vma; //把自己挂在VMA->anon_vma_chain链表上
	struct rb_node rb;			//把自己挂在anon_vma->rb_root红黑树上
	unsigned long rb_subtree_last;
};
struct vm_area_struct {
    struct list_head anon_vma_chain; //VMA和AVC是一对多,把AVC穿成链表
    struct anon_vma *anon_vma; //VMA和AV是一对一的关系
}

映射产生的过程

父进程产生匿名页面

用户态malloc分配虚拟内存->用户进程写内存->内核发生缺页异常->do_anonymous_page()

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
		unsigned long address, pte_t *page_table, pmd_t *pmd,
		unsigned int flags)
{
    /* 保证vma有一个AV对应它,common case是已经有了,但没有的话就要分配并建立AVC VMA AV三者关系 */
    anon_vma_prepare(vma);
    /* 申请页面的动作 */
    page = alloc_zeroed_user_highpage_movable(vma, address);
    /* 设置该页面的匿名映射 */
    page_add_new_anon_rmap(page, vma, address);
}

准备VMA的AV和AVC

int anon_vma_prepare(struct vm_area_struct *vma) {
    struct anon_vma *anon_vma = vma->anon_vma;
	struct anon_vma_chain *avc;
    if (unlikely(!anon_vma)) {
        avc = anon_vma_chain_alloc(GFP_KERNEL);
        anon_vma = find_mergeable_anon_vma(vma);
        anon_vma = anon_vma_alloc();//如果find_mergeable_av失败
        vma->anon_vma = anon_vma; //建立三者关系
        anon_vma_chain_link(vma, avc, anon_vma);
        anon_vma->degree++; //Count of child anon_vmas and VMAs which points to this anon_vma.
    }
}
static void anon_vma_chain_link(struct vm_area_struct *vma,
				struct anon_vma_chain *avc,
				struct anon_vma *anon_vma)
{
	avc->vma = vma;
	avc->anon_vma = anon_vma;
	list_add(&avc->same_vma, &vma->anon_vma_chain);
	anon_vma_interval_tree_insert(avc, &anon_vma->rb_root);
}

设置该页面的匿名映射

/**
 * page_add_new_anon_rmap - add pte mapping to a new anonymous page
 * @page:	the page to add the mapping to
 * @vma:	the vm area in which the mapping is added
 * @address:	the user virtual address mapped
 */
void page_add_new_anon_rmap(struct page *page,
	struct vm_area_struct *vma, unsigned long address)
{
    /* 设置这个页可以交换到磁盘 */
	SetPageSwapBacked(page);
    /* 省略…… */
    /* 设置这个页面为匿名映射 */
	__page_set_anon_rmap(page, vma, address, 1);
}
/**
 * @exclusive:	the page is exclusively owned by the current process
                page被当前进程独占
 */
static void __page_set_anon_rmap(struct page *page,
	struct vm_area_struct *vma, unsigned long address, int exclusive)
{
	struct anon_vma *anon_vma = vma->anon_vma;
    /* 如果已经是匿名页 *//* 不理解 */
	if (PageAnon(page))
		return;
	/*
	 * If the page isn't exclusively mapped into this vma,
	 * we must use the _oldest_ possible anon_vma for the
	 * page mapping!
	 *//* 不理解 */
	if (!exclusive)
		anon_vma = anon_vma->root;

    /* 重点 */
	anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
	page->mapping = (struct address_space *) anon_vma;
    /* 计算address在vma中的第几个页面 */
	page->index = linear_page_index(vma, address);
}

父进程产生匿名页面时的状态
(./rmap/statusWhenProcessDoAnonPage.jpg)]
Linux内存反向映射_第1张图片

总结:父进程产生匿名页时的反向映射操作,首先申请了AV和AVC,构建VMA、AV、AVC三者的关系,然后申请一个页面,将AV的地址(利用最低位表示该page是一个anon_page)设置到page->mapping中,将address在VMA中的第几个页面的信息设置在page->index中。
此时因为父进程还没有子进程,或者说之前fork的时候该page还没有分配,就不必处理父子进程之间复杂的AVC指针问题。

父进程fork子进程

父进程在fork()创建子进程时,子进程会复制父进程的VMA数据结构内容,并且复制父进程的PTE内容到子进程的页表中,实现父子进程共享页表。多个不同子进程的虚拟页面会同时映射到同一个物理页面。
另外,多个不相干的进程的虚拟页面可以通过KSM机制映射到同一个物理页面中,暂不讨论该技术

父进程fork->do_fork()->copy_process()->copy_mm()->dup_mm()->dup_mmap()

/* 复制父进程的地址空间 */
static __latent_entropy int dup_mmap(struct mm_struct *mm,
					struct mm_struct *oldmm)
{
    /* 遍历父进程的所有VMA */
    for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
        /* 新建一个VMA,把父进程VMA的信息都复制到其中 */
        tmp = vm_area_dup(mpnt); /* 1 */
        tmp->vm_mm = mm; //该VMA属于哪个mm_struct

        /* 建立子进程反向映射所需的所有关系 */
        anon_vma_fork(tmp, mpnt);/* 2 */

        /* 把tmp添加到子进程的红黑树中 */
        __vma_link_rb(mm, tmp, rb_link, rb_parent);

        /* 复制父进程的PTE到子进程页表中 */
        copy_page_range(mm, oldmm, mpnt);
    }
}
/* 1 */
struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig)
{
	struct vm_area_struct *new = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
	if (new) {
		*new = *orig; //可以这样赋值结构体么?
		INIT_LIST_HEAD(&new->anon_vma_chain);
	}
	return new;
}
/* 2 */
/* vma->子进程的VMA, pvma->表示父进程的VMA */
int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma) {
	struct anon_vma_chain *avc;
	struct anon_vma *anon_vma;

    /* 通过建立多个AVC枢纽,来建立新进程与所有前辈进程的联系 */
    anon_vma_clone(vma, pvma);

    /* 建立子进程的AV和AVC,上一步只需要子进程的VMA即可 */
    anon_vma = anon_vma_alloc();
    avc = anon_vma_chain_alloc(GFP_KERNEL);

    /* 子进程与前辈进程的关系,该关系只能通过子进程找前辈进程,上面的可以通过前辈进程找子进程 */
    anon_vma->root = pvma->anon_vma->root;
	anon_vma->parent = pvma->anon_vma;

    /* 建立子进程内部的VMA、AV、AVC联系 */
    anon_vma_chain_link(vma, avc, anon_vma);
}
int anon_vma_clone(struct vm_area_struct *dst, struct vm_area_struct *src) {
    struct anon_vma_chain *avc, *pavc;
    /* 遍历父进程VMA下AVC链表 */
    list_for_each_entry_reverse(pavc, &src->anon_vma_chain, same_vma) {
        /* 创建AVC枢纽 */
        avc = anon_vma_chain_alloc(GFP_NOWAIT | __GFP_NOWARN);
        /* 总结:AVC枢纽用于连接父进程的AV和子进程的VMA
         * AVC枢纽->vma = 子进程VMA
         * AVC枢纽加入子进程VMA的链表中
         * AVC枢纽->AV = 父进程AV
         * AVC枢纽加入父进程AV的红黑树中
         */
        anon_vma_chain_link(dst, avc, pavc->anon_vma);
    }
    /* 遍历的意义:考虑如下情况
    * 若父进程A fork 子进程B,子进程B fork 孙进程C
    * 父进程A的VMA0中AVC链表上有AVC0
    * 子进程B的VMA1中AVC链表上有AVC0和AVC枢纽(称为AVC_x01)
    * 当fork孙进程C的时候,遍历子进程B的VMA1的AVC链表,通过pavc->anon_vma找到进程A和进程B的AV,依次建立孙进程C与父进程A、子进程B的联系
    */
}

父进程fork时RMAP机制的流程
(./rmap/statusWhenProcessFork.jpg)]
Linux内存反向映射_第2张图片

举个栗子
(./rmap/rmapExample.jpg)]
Linux内存反向映射_第3张图片

子进程发生写时复制

前文叙述的父进程产生匿名页面最终会在do_anonymous_page()中申请页并绑定rmap的关系
子进程发生写时复制时会在do_wp_page()函数中处理
(./rmap/MissPageHandle.jpg)]
Linux内存反向映射_第4张图片

->缺页中断发生
    ->handle_pte_fault()
        ->do_wp_page()
            ->wp_page_copy()
                ->分配一个新的匿名页面
                    ->page_add_new_anon_rmap()      前文分析过
                        ->__page_set_anon_rmap()

RAMP应用——页面回收

struct rmap_walk_control {
	void *arg;
	/* 表示具体断开某个VMA上映射的PTE */
	bool (*rmap_one)(struct page *page, struct vm_area_struct *vma,
					unsigned long addr, void *arg);
    /* 判断一个页是否断开成功 */
	int (*done)(struct page *page);
    /* 实现一个锁机制 */
	struct anon_vma *(*anon_lock)(struct page *page);
    /* 表示跳过无效VMA */
	bool (*invalid_vma)(struct vm_area_struct *vma, void *arg);
};
bool try_to_unmap(struct page *page, enum ttu_flags flags)
{
	struct rmap_walk_control rwc = {
		.rmap_one = try_to_unmap_one,
		.arg = (void *)flags,
		.done = page_mapcount_is_zero,
		.anon_lock = page_lock_anon_vma_read,
	};

    /* 重要 */
    rmap_walk(page, &rwc);

    /* page->_mapcount == -1表示所有映射到这个页面的PTE都已经解除完毕 */
	return !page_mapcount(page) ? true : false;
}

以匿名页为例,重点关注如何使用remap机制,通过page找到VMA的

static void rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc,
		bool locked)
{
	struct anon_vma *anon_vma;
	pgoff_t pgoff_start, pgoff_end;
	struct anon_vma_chain *avc;

	if (locked) {
		anon_vma = page_anon_vma(page);
	} else {
        /* 若此时未加锁,则加锁 */
		anon_vma = rmap_walk_anon_lock(page, rwc);
	}

	pgoff_start = page_to_pgoff(page);
	pgoff_end = pgoff_start + hpage_nr_pages(page) - 1;
    /* 遍历anon_vma->rb_root红黑树,得到AVC */
	anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff_start, pgoff_end) {
        /* 通过AVC拿到VMA */
		struct vm_area_struct *vma = avc->vma;
		unsigned long address = vma_address(page, vma);

		if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
			continue;

        /* 解除用户PTE */
		if (!rwc->rmap_one(page, vma, address, rwc->arg))
			break;
		if (rwc->done && rwc->done(page))
			break;
	}

	if (!locked)
		anon_vma_unlock_read(anon_vma);
}

疑问

  • KSM是怎么实现反向映射的?

参考资料

http://www.wowotech.net/memory_management/reverse_mapping.html

你可能感兴趣的:(Linux内核,linux)