您的位置:首页 > 运维架构 > Linux

Linux内存管理 (12)反向映射RMAP

2018-02-05 19:07 218 查看
专题:Linux内存管理专题

关键词:RMAP、VMA、AV、AVC。

所谓反向映射是相对于从虚拟地址到物理地址的映射,反向映射是从物理页面到虚拟地址空间VMA的反向映射。

RMAP能否实现的基础是通过structanon_vma、structanon_vma_chain和sturctvm_area_struct建立了联系,通过物理页面反向查找到VMA。

用户在使用虚拟内存过程中,PTE页表项中保留着虚拟内存页面映射到物理内存页面的记录。

一个物理页面可以同时被多个进程的虚拟地址内存映射,但一个虚拟页面同时只能有一个物理页面与之映射。

不同虚拟页面同时映射到同一物理页面是因为子进程克隆父进程VMA,和KSM机制的存在。

如果页面要被回收,就必须要找出哪些进程在使用这个页面,然后断开这些虚拟地址到物理页面的映射。

匿名页面实际的断开映射操作在rmap_walk_anon中进行的,可以看出从structpage、到structanon_vma、到structanon_vma_chain、到structvm_area_struct的关系。

1.父进程分配匿名页面

父进程为自己的进程地址空间VMA分配物理内存时,通常会产生匿名页面。

do_anonymous_page()会分配匿名页面;do_wp_page()发生写时复制COW时也会产生一个新的匿名页面。

staticintdo_anonymous_page(structmm_struct*mm,structvm_area_struct*vma,
unsignedlongaddress,pte_t*page_table,pmd_t*pmd,
unsignedintflags)
{
...
/*Allocateourownprivatepage.*/
if(unlikely(anon_vma_prepare(vma)))------------------------------为进程地址空间准备structanon_vma数据结构和structanon_vma_chain链表。
gotooom;
page=alloc_zeroed_user_highpage_movable(vma,address);----------从HIGHMEM区域分配一个zeroed页面
if(!page)
gotooom;
...
inc_mm_counter_fast(mm,MM_ANONPAGES);
page_add_new_anon_rmap(page,vma,address);-----------------------
mem_cgroup_commit_charge(page,memcg,false);
lru_cache_add_active_or_unevictable(page,vma);
...
}


RMAP反向映射系统中有两个重要的数据结构:一个是structanon_vma,简称AV;一个是structanon_vma_chain,简称AVC。

structanon_vma{
structanon_vma*root;/*Rootofthisanon_vmatree*/----------------指向anon_vma数据机构中的根节点
structrw_semaphorerwsem;/*W:modification,R:walkingthelist*/------保护anon_vma中链表的读写信号量
/*
*Therefcountistakenonananon_vmawhenthereisno
*guaranteethatthevmaofpagetableswillexistfor
*thedurationoftheoperation.Acallerthattakes
*thereferenceisresponsibleforclearingupthe
*anon_vmaiftheyarethelastuseronrelease
*/
atomic_trefcount;------------------------------------------------------------对anon_vma的引用计数

/*
*Countofchildanon_vmasandVMAswhichpointstothisanon_vma.
*
*Thiscounterisusedformakingdecisionaboutreusinganon_vma
*insteadofforkingnewone.Seecommentsinfunctionanon_vma_clone.
*/
unsigneddegree;

structanon_vma*parent;/*Parentofthisanon_vma*/--------------------指向父anon_vma数据结构

/*
*NOTE:theLSBoftherb_root.rb_nodeissetby
*mm_take_all_locks()_after_takingtheabovelock.Sothe
*rb_rootmustonlyberead/writtenaftertakingtheabovelock
*tobesuretoseeavalidnextpointer.TheLSBbititself
*isserializedbyasystemwidelockonlyvisibleto
*mm_take_all_locks()(mm_all_locks_mutex).
*/
structrb_rootrb_root;/*Intervaltreeofprivate"related"vmas*/-----红黑树根节点
}


structanon_vma_chain数据结构是链接父子进程的枢纽:

structanon_vma_chain{
structvm_area_struct*vma;-----------------------------------------------指向VMA
structanon_vma*anon_vma;------------------------------------------------指向anon_vma数据结构,可以指向父进程或子进程的anon_vma数据结构。
structlist_headsame_vma;/*lockedbymmap_sem&page_table_lock*/---链表节点,通常把anon_vma_chain添加到vma->anon_vma_chain链表中。
structrb_noderb;/*lockedbyanon_vma->rwsem*/-------------红黑树节点,通常把anon_vma_chain添加到anon_vma->rb_root的红黑树中。
unsignedlongrb_subtree_last;
#ifdefCONFIG_DEBUG_VM_RB
unsignedlongcached_vma_start,cached_vma_last;
#endif
}


下面分析如何建立AV、AVC、VMA之间的关系:

intanon_vma_prepare(structvm_area_struct*vma)
{
structanon_vma*anon_vma=vma->anon_vma;--------------vma->anon_vma指向structanon_vma数据结构。
structanon_vma_chain*avc;

might_sleep();
if(unlikely(!anon_vma)){
structmm_struct*mm=vma->vm_mm;
structanon_vma*allocated;

avc=anon_vma_chain_alloc(GFP_KERNEL);------------分配一个structanon_vma_chain结构。
if(!avc)
gotoout_enomem;

anon_vma=find_mergeable_anon_vma(vma);-----------是否可以和前后vma合并
allocated=NULL;
if(!anon_vma){
anon_vma=anon_vma_alloc();-------------------如果无法合并,则重新分配一个结构体
if(unlikely(!anon_vma))
gotoout_enomem_free_avc;
allocated=anon_vma;
}

anon_vma_lock_write(anon_vma);
/*page_table_locktoprotectagainstthreads*/
spin_lock(&mm->page_table_lock);
if(likely(!vma->anon_vma)){
vma->anon_vma=anon_vma;-------------------------建立structvm_area_struct和structanon_vma关联
anon_vma_chain_link(vma,avc,anon_vma);----------建立structanon_vma_chain和其他结构体的关系。
/*vmareferenceorself-parentlinkfornewroot*/
anon_vma->degree++;
allocated=NULL;
avc=NULL;
}
spin_unlock(&mm->page_table_lock);
anon_vma_unlock_write(anon_vma);

if(unlikely(allocated))
put_anon_vma(allocated);
if(unlikely(avc))
anon_vma_chain_free(avc);
}
return0;

out_enomem_free_avc:
anon_vma_chain_free(avc);
out_enomem:
return-ENOMEM;
}


至此已经建立structvm_area_struct、structanon_vma、structanon_vma_chain三者之间的链接,并插入相应链表、红黑树中。

从AVC可以轻松找到VMA和AV;AV可以通过红黑树找到AVC,然后发现所有红黑树中的AV;VMA可以直接找到AV,也可以通过AVC链表找到AVC。



staticvoidanon_vma_chain_link(structvm_area_struct*vma,
structanon_vma_chain*avc,
structanon_vma*anon_vma)
{
avc->vma=vma;--------------------------------------------建立structanon_vma_chain和structvm_area_struct关联
avc->anon_vma=anon_vma;----------------------------------建立structanon_vma_chain和structanon_vma关联
list_add(&avc->same_vma,&vma->anon_vma_chain);------------将AVC添加到structvm_area_struct->anon_vma_chain链表中。
anon_vma_interval_tree_insert(avc,&anon_vma->rb_root);----将AVC添加到structanon_vma->rb_root红黑树中。
}


调用alloc_zeroed_user_highpage_movable分配物理内存之后,调用page_add_new_anon_rmap建立PTE映射关系。

voidpage_add_new_anon_rmap(structpage*page,
structvm_area_struct*vma,unsignedlongaddress)
{
VM_BUG_ON_VMA(address<vma->vm_start||address>=vma->vm_end,vma);
SetPageSwapBacked(page);----------------------------------------------------------设置PG_SwapBacked表示这个页面可以swap到磁盘。
atomic_set(&page->_mapcount,0);/*incrementcount(startsat-1)*/-------------设置_mapcount引用计数为0
if(PageTransHuge(page))
__inc_zone_page_state(page,NR_ANON_TRANSPARENT_HUGEPAGES);
__mod_zone_page_state(page_zone(page),NR_ANON_PAGES,-----------------------------增加页面所在zone的匿名页面计数
hpage_nr_pages(page));
__page_set_anon_rmap(page,vma,address,1);--------------------------------------设置这个页面位匿名映射
}

staticvoid__page_set_anon_rmap(structpage*page,
structvm_area_struct*vma,unsignedlongaddress,intexclusive)
{
structanon_vma*anon_vma=vma->anon_vma;

BUG_ON(!anon_vma);

if(PageAnon(page))---------------------------------------------------------------判断当前页面是否是匿名页面PAGE_MAPPING_ANON
return;

/*
*Ifthepageisn'texclusivelymappedintothisvma,
*wemustusethe_oldest_possibleanon_vmaforthe
*pagemapping!
*/
if(!exclusive)
anon_vma=anon_vma->root;

anon_vma=(void*)anon_vma+PAGE_MAPPING_ANON;
page->mapping=(structaddress_space*)anon_vma;------------------------------mapping指定页面所在的地址空间,这里指向匿名页面的地址空间数据结构structanon_vma。
page->index=linear_page_index(vma,address);
}


结合上图,总结如下:

父进程每个VMA都有一个anon_vma数据结构,vma->anon_vma指向。

和VMA相关的物理页面page->mapping都指向anon_vma。

AVC数据结构anon_vma_chain->vma指向VMA,anon_vma_chain->anon_vma指向AV。

AVC添加到VMA->anon_vma_chain链表中。

AVC添加到AV->anon_vma红黑树中。

2.父进程创建子进程

父进程通过fork系统调用创建子进程时,子进程会复制父进程的进程地址空间VMA数据结构作为自己的进程地址空间,并且会复制父进程的PTE页表项内容到子进程的页表中,实现父子进程共享页表。

多个不同子进程中的虚拟页面会同时映射到同一个物理页面,另外多个不相干进程虚拟页面也可以通过KSM机制映射到同一个物理页面。

fork()系统调用实现在kernel/fork.c中,在dup_mmap()中复制父进程的地址空间和父进程的PTE页表项:

staticintdup_mmap(structmm_struct*mm,structmm_struct*oldmm)
{
structvm_area_struct*mpnt,*tmp,*prev,**pprev;
structrb_node**rb_link,*rb_parent;
intretval;
unsignedlongcharge;

uprobe_start_dup_mmap();
down_write(&oldmm->mmap_sem);
flush_cache_dup_mm(oldmm);
uprobe_dup_mmap(oldmm,mm);
/*
*Notlinkedinyet-nodeadlockpotential:
*/
down_write_nested(&mm->mmap_sem,SINGLE_DEPTH_NESTING);

mm->total_vm=oldmm->total_vm;
mm->shared_vm=oldmm->shared_vm;
mm->exec_vm=oldmm->exec_vm;
mm->stack_vm=oldmm->stack_vm;

rb_link=&mm->mm_rb.rb_node;
rb_parent=NULL;
pprev=&mm->mmap;
retval=ksm_fork(mm,oldmm);
if(retval)
gotoout;
retval=khugepaged_fork(mm,oldmm);
if(retval)
gotoout;

prev=NULL;
for(mpnt=oldmm->mmap;mpnt;mpnt=mpnt->vm_next){-------------------for循环遍历父进程的进程地址空间VMA。
structfile*file;

if(mpnt->vm_flags&VM_DONTCOPY){
vm_stat_account(mm,mpnt->vm_flags,mpnt->vm_file,
-vma_pages(mpnt));
continue;
}
charge=0;
if(mpnt->vm_flags&VM_ACCOUNT){
unsignedlonglen=vma_pages(mpnt);

if(security_vm_enough_memory_mm(oldmm,len))/*sic*/
gotofail_nomem;
charge=len;
}
tmp=kmem_cache_alloc(vm_area_cachep,GFP_KERNEL);
if(!tmp)
gotofail_nomem;
*tmp=*mpnt;--------------------------------------------------------复制父进程地址空间VMA到刚创建的子进程tmp中。
INIT_LIST_HEAD(&tmp->anon_vma_chain);
retval=vma_dup_policy(mpnt,tmp);
if(retval)
gotofail_nomem_policy;
tmp->vm_mm=mm;
if(anon_vma_fork(tmp,mpnt))----------------------------------------为子进程创建相应的anon_vma数据结构。
gotofail_nomem_anon_vma_fork;
tmp->vm_flags&=~VM_LOCKED;
tmp->vm_next=tmp->vm_prev=NULL;
file=tmp->vm_file;
...
__vma_link_rb(mm,tmp,rb_link,rb_parent);--------------------------把VMA添加到子进程红黑树中。
rb_link=&tmp->vm_rb.rb_right;
rb_parent=&tmp->vm_rb;

mm->map_count++;
retval=copy_page_range(mm,oldmm,mpnt);---------------------------复制父进程的PTE页表项到子进程页表中。

if(tmp->vm_ops&&tmp->vm_ops->open)
tmp->vm_ops->open(tmp);

if(retval)
gotoout;
}
...
}


3.子进程发生COW

如果子进程的VMA发生COW,那么会使用子进程VMA创建的anon_vma数据结构,即page->mmaping指针指向子进程VMA对应的anon_vma数据结构。

在do_wp_page()函数中处理COW场景情况:子进程和父进程共享的匿名页面,子进程的VMA发生COW。

->发生缺页中断
->handle_pte_fault
->do_wp_page
->分配一个新的匿名页面
->__page_set_anon_rmap使用子进程的anon_vma来设置page->mapping


4.RMAP应用

内核中通过structpage找到所有映射到这个页面的VMA典型场景有:

kswapd内核线程回收页面需要断开所有映射了该匿名页面的用户PTE页表项。

页面迁移时,需要断开所有映射到匿名页面的用户PTE页表项。

try_to_unmap()是反向映射的核心函数,内核中其他模块会调用此函数来断开一个页面的所有映射:

/**
*try_to_unmap-trytoremoveallpagetablemappingstoapage
*@page:thepagetogetunmapped
*@flags:actionandflags
*
*Triestoremoveallthepagetableentrieswhicharemappingthis
*page,usedinthepageoutpath.Callermustholdthepagelock.
*Returnvaluesare:
*
*SWAP_SUCCESS-wesucceededinremovingallmappings------------成功解除了所有映射的PTE。
*SWAP_AGAIN-wemissedamapping,tryagainlater---------------可能错过了一个映射的PTE,需要重来一次。
*SWAP_FAIL-thepageisunswappable-----------------------------失败
*SWAP_MLOCK-pageismlocked.-----------------------------------页面被锁住了
*/
inttry_to_unmap(structpage*page,enumttu_flagsflags)
{
intret;
structrmap_walk_controlrwc={
.rmap_one=try_to_unmap_one,--------------------------------具体断开某个VMA上映射的pte
.arg=(void*)flags,
.done=page_not_mapped,-------------------------------------判断一个页面是否断开成功的条件
.anon_lock=page_lock_anon_vma_read,------------------------锁
};

VM_BUG_ON_PAGE(!PageHuge(page)&&PageTransHuge(page),page);

/*
*Duringexec,atemporaryVMAissetupandlatermoved.
*TheVMAismovedundertheanon_vmalockbutnotthe
*pagetablesleadingtoaracewheremigrationcannot
*findthemigrationptes.Ratherthanincreasingthe
*lockingrequirementsofexec(),migrationskips
*temporaryVMAsuntilafterexec()completes.
*/
if((flags&TTU_MIGRATION)&&!PageKsm(page)&&PageAnon(page))
rwc.invalid_vma=invalid_migration_vma;

ret=rmap_walk(page,&rwc);

if(ret!=SWAP_MLOCK&&!page_mapped(page))
ret=SWAP_SUCCESS;
returnret;
}


内核中有三种页面需要unmap操作,即KSM页面、匿名页面、文件映射页面:

intrmap_walk(structpage*page,structrmap_walk_control*rwc)
{
if(unlikely(PageKsm(page)))
returnrmap_walk_ksm(page,rwc);
elseif(PageAnon(page))
returnrmap_walk_anon(page,rwc);
else
returnrmap_walk_file(page,rwc);
}


下面以匿名页面的unmap为例:

staticintrmap_walk_anon(structpage*page,structrmap_walk_control*rwc)
{
structanon_vma*anon_vma;
pgoff_tpgoff;
structanon_vma_chain*avc;
intret=SWAP_AGAIN;

anon_vma=rmap_walk_anon_lock(page,rwc);-----------------------------------获取页面page->mapping指向的anon_vma数据结构,并申请一个读者锁。
if(!anon_vma)
returnret;

pgoff=page_to_pgoff(page);
anon_vma_interval_tree_foreach(avc,&anon_vma->rb_root,pgoff,pgoff){------遍历anon_vma->rb_root红黑树中的AVC,从AVC得到相应的VMA。
structvm_area_struct*vma=avc->vma;
unsignedlongaddress=vma_address(page,vma);

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

ret=rwc->rmap_one(page,vma,address,rwc->arg);-----------------------实际的断开用户PTE页表项操作。
if(ret!=SWAP_AGAIN)
break;
if(rwc->done&&rwc->done(page))
break;
}
anon_vma_unlock_read(anon_vma);
returnret;
}


structrmap_walk_control中的rmap_one实现是try_to_unmap_one,最终调用page_remove_rmap()和page_cache_release()来断开PTE映射关系。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: