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)]
总结:父进程产生匿名页时的反向映射操作,首先申请了AV和AVC,构建VMA、AV、AVC三者的关系,然后申请一个页面,将AV的地址(利用最低位表示该page是一个anon_page)设置到page->mapping中,将address在VMA中的第几个页面的信息设置在page->index中。
此时因为父进程还没有子进程,或者说之前fork的时候该page还没有分配,就不必处理父子进程之间复杂的AVC指针问题。
父进程在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)]
举个栗子
(./rmap/rmapExample.jpg)]
前文叙述的父进程产生匿名页面最终会在do_anonymous_page()中申请页并绑定rmap的关系
当子进程发生写时复制时会在do_wp_page()函数中处理
(./rmap/MissPageHandle.jpg)]
->缺页中断发生
->handle_pte_fault()
->do_wp_page()
->wp_page_copy()
->分配一个新的匿名页面
->page_add_new_anon_rmap() 前文分析过
->__page_set_anon_rmap()
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);
}
http://www.wowotech.net/memory_management/reverse_mapping.html