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的关系。
do_anonymous_page()会分配匿名页面;do_wp_page()发生写时复制COW时也会产生一个新的匿名页面。
RMAP反向映射系统中有两个重要的数据结构:一个是structanon_vma,简称AV;一个是structanon_vma_chain,简称AVC。
structanon_vma_chain数据结构是链接父子进程的枢纽:
下面分析如何建立AV、AVC、VMA之间的关系:
至此已经建立structvm_area_struct、structanon_vma、structanon_vma_chain三者之间的链接,并插入相应链表、红黑树中。
从AVC可以轻松找到VMA和AV;AV可以通过红黑树找到AVC,然后发现所有红黑树中的AV;VMA可以直接找到AV,也可以通过AVC链表找到AVC。
调用alloc_zeroed_user_highpage_movable分配物理内存之后,调用page_add_new_anon_rmap建立PTE映射关系。
结合上图,总结如下:
父进程每个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红黑树中。
多个不同子进程中的虚拟页面会同时映射到同一个物理页面,另外多个不相干进程虚拟页面也可以通过KSM机制映射到同一个物理页面。
fork()系统调用实现在kernel/fork.c中,在dup_mmap()中复制父进程的地址空间和父进程的PTE页表项:
在do_wp_page()函数中处理COW场景情况:子进程和父进程共享的匿名页面,子进程的VMA发生COW。
kswapd内核线程回收页面需要断开所有映射了该匿名页面的用户PTE页表项。
页面迁移时,需要断开所有映射到匿名页面的用户PTE页表项。
try_to_unmap()是反向映射的核心函数,内核中其他模块会调用此函数来断开一个页面的所有映射:
内核中有三种页面需要unmap操作,即KSM页面、匿名页面、文件映射页面:
下面以匿名页面的unmap为例:
structrmap_walk_control中的rmap_one实现是try_to_unmap_one,最终调用page_remove_rmap()和page_cache_release()来断开PTE映射关系。
关键词:RMAP、VMA、AV、AVC。
所谓反向映射是相对于从虚拟地址到物理地址的映射,反向映射是从物理页面到虚拟地址空间VMA的反向映射。
RMAP能否实现的基础是通过structanon_vma、structanon_vma_chain和sturctvm_area_struct建立了联系,通过物理页面反向查找到VMA。
用户在使用虚拟内存过程中,PTE页表项中保留着虚拟内存页面映射到物理内存页面的记录。
一个物理页面可以同时被多个进程的虚拟地址内存映射,但一个虚拟页面同时只能有一个物理页面与之映射。
不同虚拟页面同时映射到同一物理页面是因为子进程克隆父进程VMA,和KSM机制的存在。
如果页面要被回收,就必须要找出哪些进程在使用这个页面,然后断开这些虚拟地址到物理页面的映射。
匿名页面实际的断开映射操作在
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映射关系。
相关文章推荐
- linux用户空间内存管理:内存映射和需求分页(缺页中断)
- 进程所用内存(包括页表、vma、映射内存)的释放过程 (基于linux2.6.11.12)
- Linux内存管理之高端内存映射(转贴)
- kmalloc分配物理内存与高端内存映射--Linux内存管理(十八)
- Linux 内存管理 -- 高端内存的映射方式
- Linux 匿名页的反向映射
- Linux 2.6 中的页面回收与反向映射
- Linux内存管理之mmap详解 一. mmap系统调用 1. mmap系统调用 mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,
- Linux 内存管理 -- 高端内存的映射方式
- Linux内存管理之地址映射
- [转]LINUX内存管理 - 高端内存的映射方式
- linux高端内存管理之永久内核映射
- Linux驱动学习12(初步认识内存管理)
- 高端内存映射之vmalloc分配内存中不连续的页--Linux内存管理(十九)
- linux下内存管理之地址映射
- Linux 2.6 中的页面回收与反向映射
- Linux内存管理 (2)页表的映射过程
- Linux的页面回收与反向映射机制
- Linux内存管理之高端内存映射
- linux内存源码分析 - 内存回收(匿名页反向映射)