当前位置: 首页 > article >正文

linux内存的反向映射

内存的反向映射

  • 前言
  • 文件页的反向映射
  • 匿名页的反向映射
    • 当VMA和VA首次相遇
    • 在fork的时候,匿名映射的VMA经历了什么
    • 构建三层大厦
    • page frame是如何加入“大厦”中
    • 为何建立如此复杂的“大厦”?
    • 页面回收的时候,如何unmap一个page frame的所有的映射?

前言

逆向映射是指在已知page frame的情况下(可能是PFN、可能是指向page descriptor的指针,也可能是物理地址,内核有各种宏定义用于在它们之间进行转换),找到映射到该物理页面的虚拟页面们。由于一个page frame可以在多个进程之间共享,因此逆向映射的任务是把分散在各个进程地址空间中的所有的page table entry全部找出来。之所以建立逆向映射机制主要是为了方便页面回收。

文件页的反向映射

对于文件映射页面,其struct page中有一个成员mapping指向一个struct address_space,address_space是和文件相关的,它保存了文件page cache相关的信息。当然,我们这个场景主要关注一个叫做i_mmap的成员。一个文件可能会被映射到多个进程的多个VMA中,所有的这些VMA都被挂入到i_mmap指向的Priority search tree中。
在这里插入图片描述
当然,我们最终的目标是PTEs,下面这幅图展示了如何从VMA和struct page中的信息导出该page frame的虚拟地址的:
在这里插入图片描述
而在linux kernel中,函数vma_address可以完成这个功能:

static inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
	unsigned long address;
    pgoff_t pgoff = page->index;
    address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
    return address;
}

对于file mapped page,page->index表示的是映射到文件内的偏移(page为单位),而vma->vm_pgoff表示的是该VMA映射到文件内的偏移(page为单位),因此,通过vma->vm_pgoff和page->index可以得到该page frame在VMA中的地址偏移,再加上vma->vm_start就可以得到该page frame的虚拟地址。有了虚拟地址和地址空间(vma->vm_mm),我们就可以通过各级页表找到该page对应的pte entry。

i_mmap指向的Priority search tree类似如下,每个节点包含了vma的起始、大小和结束信息。
在这里插入图片描述
文件页查找过程可以参考rmap_walk_file函数实现:

2338  * Find all the mappings of a page using the mapping pointer and the vma chains
2339  * contained in the address_space struct it points to.
2340  *
2341  * When called from page_mlock(), the mmap_lock of the mm containing the vma
2342  * where the page was found will be held for write.  So, we won't recheck
2343  * vm_flags for that VMA.  That should be OK, because that vma shouldn't be
2344  * LOCKED.
2345  */
2346 static void rmap_walk_file(struct page *page, struct rmap_walk_control *rwc,
2347         bool locked)
2348 {
2349     struct address_space *mapping = page_mapping(page);
2350     pgoff_t pgoff_start, pgoff_end;
2351     struct vm_area_struct *vma;
2352 
2353     /* 
2354      * The page lock not only makes sure that page->mapping cannot
2355      * suddenly be NULLified by truncation, it makes sure that the
2356      * structure at mapping cannot be freed and reused yet,
2357      * so we can safely take mapping->i_mmap_rwsem.
2358      */
2359     VM_BUG_ON_PAGE(!PageLocked(page), page);
2360                     
2361     if (!mapping)
2362         return;   
2363 
2364     pgoff_start = page_to_pgoff(page);
2365     pgoff_end = pgoff_start + thp_nr_pages(page) - 1;
2366     if (!locked)
2367         i_mmap_lock_read(mapping);
2368     vma_interval_tree_foreach(vma, &mapping->i_mmap,
2369             pgoff_start, pgoff_end) {
2370         unsigned long address = vma_address(page, vma);
2371 
2372         VM_BUG_ON_VMA(address == -EFAULT, vma);
2373         cond_resched();
2374 
2375         if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
2376             continue; 
2377 
2378         if (!rwc->rmap_one(page, vma, address, rwc->arg))
2379             goto done;
2380         if (rwc->done && rwc->done(page))
2381             goto done;
2382     }
2383     
2384 done:
2385     if (!locked)
2386         i_mmap_unlock_read(mapping);
2387 }

匿名页的反向映射

和file mapped类似,anonymous page也是通过VMA来寻找page frame对应的pte entry。
为了节省内存,我们复用了struct page中的mapping指针:一个page frame如果是file mapped,其mapping指针指向对应文件的address_space数据结构。如果是anonymous page,那么mapping指针指向anon_vma数据结构。
通过struct page中的mapping成员我们可以获得该page映射相关的信息,总结如下:
(1) 等于NULL,表示该page frame不再内存中,而是被swap out到磁盘去了。

(2) 如果不等于NULL,并且least signification bit等于1,表示该page frame是匿名映射页面,mapping指向了一个anon_vma的数据结构。

(3) 如果不等于NULL,并且least signification bit等于0,表示该page frame是文件映射页面,mapping指向了一个该文件的address_space数据结构。

通过anon_vma数据结构,我们可以得到映射到该page的所有的VMA,至此,匿名映射和file mapped汇合,进一步解决的问题仅仅是如何从VMA到pte entry而已。

linux为每一个进程创建一个anon_vma结构并通过各种数据结构把父子进程的anon_vma(后面简称AV)以及VMA链接在一起。为了链接anon_vma,内核引入了一个新的结构,称为anon_vma_chain(后面简称AVC)

struct anon_vma_chain {

        struct vm_area_struct *vma;――指向该AVC对应的VMA

        struct anon_vma *anon_vma;――指向该AVC对应的AV

        struct list_head same_vma; ――链接入VMA链表的节点

        struct rb_node rb;―――链接入AV红黑树的节点

        unsigned long rb_subtree_last;

};

AVC是一个神奇的结构,每个AVC都有其对应的VMA和AV。所有指向相同VMA的AVC会被链接到一个链表中,链表头就是VMA的anon_vma_chain成员。而一个AV会管理若干的VMA,所有相关的VMA(其子进程或者孙进程)都挂入红黑树,根节点就是AV的rb_root成员。

AV、AVC和VMA的“大厦”搭建过程如下

当VMA和VA首次相遇

当该进程的匿名映射VMA通过page fault分配第一个page frame的时候,内核会构建下图所示的数据关系:
在这里插入图片描述
上图中的AV0就是该进程的anon_vma,由于它是一个顶级结构,因此它的root和parent都是指向了自己。AV这个数据结构当然为了管理VMA了,不过新机制中,这是通过AVC进行中转的。上图中的AVC0搭建了该进程VMA和AV之间的桥梁,分别有指针指向了VMA0和AV0,此外,AVC0插入到AV的红黑树,同时也会插入到VMA的链表中。

对于这个新分配的page frame而言,它会mapping到VMA对应的某个虚拟地址上去,为了维护逆向映射的关系,struct page中的mapping指向了AV0,index成员指向了该page在整个VMA0中的偏移。

VMA0中随后可能会有若干的page frame被mapping到该VMA的某个虚拟页面,不过上面的结构不会变化,只不过每一个page中的mapping都指向了上图中的AV0。另外,上图中那个虚线绿色block的AVC0其实等于那个绿色实线的AVC0 block,也就是说这时候该VMA只有一个anon_vma_chain,即AVC0,上图只是方便表示该AVC也会被挂入VMA的链表,挂入anon_vma的红黑树而已。

如果想参考相关的代码可以仔细看看do_anonymous_page或者do_cow_fault。

在fork的时候,匿名映射的VMA经历了什么

一旦fork,那么子进程会copy父进程的VMA(参考函数dup_mmap),子进程会有自己的VMA,同时也会分配自己的AV,然后建立父子进程之间的VMA、VA的“大厦”,主要的步骤如下:
(1) 调用anon_vma_clone函数,建立子进程VMA和“父进程们”VA的关系

(2) 建立子进程VMA和子进程VA的关系

怎样叫做建立VMA和VA的关系?其实就是anon_vma_chain_link函数的调用过程,步骤如下:

  • 分配一个AVC结构,成员指针指向对应的VMA和VA

  • 将该AVC加入VMA链表

  • 将该AVC加入VA红黑树

我们一开始先别把事情搞得太复杂,先看看一个全新进程fork子进程的场景。这时候,内核会构建下图所示的数据关系:
在这里插入图片描述
首先看看如何建立子进程VMA1和父进程AV0的关系,这里需要遍历VMA0的anon_vma_chain链表,当然现在这个链表只有一个AVC0(link到AV0),为了建立和父进程的联系,我们分配了AVC_x01,它是一个桥梁,连接了父子进程。(注:AVC_x01中的x表示连接,01表示连接level 0和level 1)。通过这个桥梁,父进程可以找到子进程的VMA(因为AVC_x01插入AV0的红黑树中),而子进程也可以找到父进程的AV(因为AVC_x01插入VMA1的链表中)。

当然,自己的anon_vma也需要创建。在上图中,AV1就是子进程的anon_vma,同时分配一个AVC1来连接该子进程的VMA1和AV1,并调用anon_vma_chain_link函数将AVC1插入VMA1的链表和AV1的红黑树中。

父进程也会创建其他新的子进程,新创建的子进程的层次和VMA1、VA1的类似,这里就不描述了。不过需要注意的是:父进程每创建一个子进程,AV0的红黑树中会增加每一个起“桥梁”作用的AVC,以此连接到子进程的VMA。

构建三层大厦

上一节描述了父进程创建子进程的情况,如果子进程再次fork,那么整个VMA-VA的大厦将形成三层结构,具体如下图所示:
在这里插入图片描述
当然,首先要进行的仍然是建立孙进程VMA和“父进程们”VA的关系,这里的“父进程们”其实是泛指孙进程的上层的那些进程们。对于这个场景,“父进程们”指的就是上图中的A进程和B进程。如何建立?在fork的时候,我们进行VMA的拷贝:即分配VMA2并以VMA1为原型copy到VMA2中。Copy是沿着VMA1的AVC链表进行的,该链表有两个元素:AVC1和 AVC_x01,分别和父进程A和子进程B的AV关联。因此,在孙进程C中,我们会分配AVC_x02和AVC_x12两个AVC,并建立level 2层和level 0层以及level 1层之间的关系。

同样的,自己level的anon_vma也需要创建。在上图中,AV2就是孙进程C的anon_vma,同时分配一个AVC2来连接该孙进程的VMA2和AV2,并调用anon_vma_chain_link函数将AVC2插入VMA2的链表和AV2的红黑树中。

AV2中的root指向root AV,也就是进程A的AV。Parent成员指向其B进程(C的父进程)的AV。通过Parent这样的指针,不同level的AV建立了父子关系,而通过root指针,每一个level的AV都可以寻找找到root AV。

page frame是如何加入“大厦”中

前面几个小节重点讨论了hierarchy AV的结构是如何搭建起来的,也就是描述fork的过程中,父子进程的VMA、AVC和AV是如何联系的。本小节我们将一起来看看父子进程之一访问页面,发生了page fault的处理过程。

这个处理过程有两个场景

  • 一个是父子进程都没有page frame,这时候,内核代码会调用do_anonymous_page分配page frame并调用page_add_new_anon_rmap函数建立该page和对应VMA的关系。
  • 第二个场景复杂一点,是父子共享匿名页面的场景,当发生write fault的时候,也是分配page frame并调用page_add_new_anon_rmap函数建立该page和对应VMA的关系,具体代码位于do_wp_page函数。

无论哪一个场景,最终都是将该page的mapping成员指向了该进程所属的AV结构。

为何建立如此复杂的“大厦”?

Page、VMA、VAC、VA组成了如此复杂的层次结构到底是为什么呢?下面用一个实际的场景来说明这个“大厦”的功能。

在这里插入图片描述
我们通过下面的步骤建立起上图的结构:

(1) P进程的某个VMA中有两类页面: 一类是有真实的物理页面的,另外一类是还没有配备物理页面的。上图中,我们分别跟踪有物理页面的A以及还没有分配物理页面的B。

(2) P进程fork了P1和P2

(3) P1进程fork了P12进程

(4) P1进程访问了A页面,分配了page frame2

(5) P12进程访问了B页面,分配了page frame3

(6) P2进程访问了B页面,分配了page frame1

(7) P2进程fork了P21进程

经过上面的这一些动作之后,我们来看看page frame共享的情况:对于P进程的page frame(是指该page 的mapping成员指向P进程的AV,即上图中的AV_P)而言,他可能会被任何一个level的的子进程VMA中的page所有共享,因此AV_P需要包括其子进程、孙进程……的所有的VMA。而对于P1进程而言,AV_P1则需要包括P1子进程、孙进程……的所有的VMA,有一点可以确认:至少父进程P和兄弟进程P2的VMA不需要包括在其中。

页面回收的时候,如何unmap一个page frame的所有的映射?

搭建了那么复杂的数据结构大厦就是为了应用,我们一起看看页面回收的场景。这个场景需要通过page frame找到所有映射到该物理页面的VMAs。有了前面的铺垫,这并不复杂,通过struct page中的mapping成员可以找到该page对应的AV,在该AV的红黑树中,包含了所有的可能共享匿名页面的VMAs。遍历该红黑树,对每一个VMA调用try_to_unmap_one函数就可以解除该物理页帧的所有映射。
每一个进程都有自己特有的anon_vma对象,每一个进程的page都指向自己特有的anon_vma对象。这大大降低了临界区的长度。
文件页查找过程可以参考rmap_walk_anon函数实现:

2279 /*
2280  * rmap_walk_anon - do something to anonymous page using the object-based
2281  * rmap method
2282  * @page: the page to be handled
2283  * @rwc: control variable according to each walk type
2284  *
2285  * Find all the mappings of a page using the mapping pointer and the vma chains
2286  * contained in the anon_vma struct it points to.
2287  *
2288  * When called from page_mlock(), the mmap_lock of the mm containing the vma
2289  * where the page was found will be held for write.  So, we won't recheck
2290  * vm_flags for that VMA.  That should be OK, because that vma shouldn't be
2291  * LOCKED.
2292  */
2293 static void rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc,
2294         bool locked)
2295 {
2296     struct anon_vma *anon_vma;
2297     pgoff_t pgoff_start, pgoff_end;
2298     struct anon_vma_chain *avc;
2299 
2300     if (locked) {
2301         anon_vma = page_anon_vma(page);
2302         /* anon_vma disappear under us? */
2303         VM_BUG_ON_PAGE(!anon_vma, page);
2304     } else {
2305         anon_vma = rmap_walk_anon_lock(page, rwc);
2306     }
2307     if (!anon_vma)
2308         return;
2309             
2310     pgoff_start = page_to_pgoff(page);
2311     pgoff_end = pgoff_start + thp_nr_pages(page) - 1;
2312     anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root,
2313             pgoff_start, pgoff_end) {
2314         struct vm_area_struct *vma = avc->vma;
2315         unsigned long address = vma_address(page, vma);
2316 
2317         VM_BUG_ON_VMA(address == -EFAULT, vma);
2318         cond_resched();
2319 
2320         if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
2321             continue;
2322 
2323         if (!rwc->rmap_one(page, vma, address, rwc->arg))
2324             break;
2325         if (rwc->done && rwc->done(page))
2326             break;
2327     }
2328 
2329     if (!locked)
2330         anon_vma_unlock_read(anon_vma);
2331 }

参考: 郭健: Linux内存逆向映射(reverse mapping)技术的前世今生


http://www.kler.cn/a/4215.html

相关文章:

  • STM32的集成开发环境STM32CubeIDE安装
  • Python爬虫学习前传 —— Python从安装到学会一站式服务
  • Flask学习入门笔记
  • 图数据库 | 18、高可用分布式设计(中)
  • 网络科技有限公司网络设计
  • Linux第二课:LinuxC高级 学习记录day04
  • 基于gtid的备份_恢复_注意事项_mysqldump_skip-gtids_include-gtids_exclude-gtids
  • ThreadLocal详解
  • 人工智能交互革命:探索ChatGPT的无限可能 第4章 ChatGPT-智能客服
  • Git(一)
  • es-head插件插入查询以及条件查询(五)
  • Spring Cloud Alibaba 多租户saas企业开发架构技术选型和设计方案
  • 程序员面试最反感的这件事,很多公司都会做
  • anaconda如何改变虚拟环境安装路径
  • Java分布式锁面试题
  • 92年程序员发帖晒薪资称自己很迷茫,网友:老弟你可以了
  • 编写猫咪相册应用 HTML
  • 笔记本网卡wireless-AC-9462驱动设备问题,搜索不到无线网络,wifi界面消失
  • mac-m1-docker安装nacos异常
  • 2023年总结的web前端学习路线分享(学习导读)
  • Linux 0.11
  • 高速下载Ubuntu系统【清华大学开源软件镜像站】(ubuntu-22.04.1-desktop-amd64.iso)
  • FCN网络解析
  • MATLAB | R2023a更新了哪些好玩的东西
  • 微信小程序性能测试——小程序运行时性能测试
  • Qt编写ffmpeg本地摄像头显示(16路本地摄像头占用3.2%CPU)