专栏首页LINUX阅码场图解内存匿名反向映射reverse mapping

图解内存匿名反向映射reverse mapping

反向映射的目的是为了找到所有映射到某一个页面的页表项,从而可以对目标页做一些操作,比如切断映射。

反向映射一直是一个非常神奇的存在,今天我们就好好探索一下这个知识点。

创建

在反向匿名映射中除了page struct,一共有三个相关的数据结构:

  • vm_area_struct
  • anon_vma
  • anon_vma_chain

第一个数据结构我们已经见过了,是一个老朋友。而后两者就是为了构造反向匿名映射而新生的。我们先来看看这两个新的数据结构的样子。

anon_vma

    anon_vma
    +----------------------------+
    |root                        |  = self
    |parent                      |  = self
    |    (struct anon_vma*)      |
    |refcount                    |  = 1
    |    (atomic_t)              |
    |degree                      |  = 1
    |    (unsigned)              |
    +----------------------------+

这个结构由anon_vma_alloc()函数统一生成,上图中也显示了创造出来时候的样子。从这里看,也就是个带有上下级关系的这么一个结构。

anon_vma_chain

    anon_vma_chain
    +----------------------------+
    |vma                         |
    |    (struct vm_area_struct*)|
    |anon_vma                    |
    |    (struct anon_vma*)      |
    |                            |
    |rb                          |
    |    (struct rb_node)        |
    |same_vma                    |
    |    (struct list_head)      |
    +----------------------------+

这个结构由anon_vma_chain_alloc()统一创建,貌似创建完了也不需要初始化,拿来后面就直接用了。

组合

到这里,大家应该感觉怪怪的,都不知道这些东西是个啥。别急,我把这些东西组合起来,可能你就会有一些感觉了。

在这里,我们把这三个重要的数据结构之间的组合关系展现给大家。当然这只是最简单的组合关系,目的是为了让大家能有一个感性的认识。

  • anon_vma_chain链接了anon_vma和vma
  • vma则会有指针指向自己的anon_vma

空口无凭,眼见为实。那为什么会长成这样的呢?接下来我们就来看看在内核中我们是如何将这些数据结构链接起来的。

链接

上一节的最后,我们看到了三个重要的数据结构通过链表和树连接在了一起,这一节我们就来看看他们是怎么连接起来的。

往简单了讲,要连接这三个重要的数据结构,都靠一个函数:anon_vma_chain_link(vma, avc, 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);
    }

你对照这上面的图一看,和图上显示的一摸一样没有任何多余的步骤。

但是,关键的但是来了,如果你以为一切就这这么简单,那就too young too simple了啊。

接下来我们将从anon_vma_chain_link函数被调用的关系入手,去看看在实际运行中究竟会演化出什么样的变化来。

do_anonymous_page

首先出场的是函数do_anonymous_page,这个函数是在匿名页缺页中断时会调用的函数。

    do_anonymous_page(vmf)
        __anon_vma_prepare(vma)
            avc = anon_vma_chain_alloc()
            anon_vma = find_mergeable_anon_vma(vma)
            anon_vma = anon_vma_alloc()
            vma->anon_vma = anon_vma
            anon_vma_chain_link(vma, avc, anon_vma)

从上面的流程可以看出,当发生缺页中断时,内核会给对应的vma构造anon_vma,并且利用avc去链接这两者。这种可以说是系统中最简单的例子,也是上图中显示的情况。

细心的人可能已经看到了,上面有一种情况是find_mergeable_anon_vma。如果这个函数返回一个可以重用的anon_vma,那么内核就可以利用原有的anon_vma了。此时这个图我们可以画成这样。

             .......................      *************************
             .                     .      *                       *
     av      v                 avc v      v                vma    v
     +-----------+             +-------------+             +-------------+
     |           |<------------|anon_vma  vma|------------>|             |
     |           |<-           |             |             |             |
     +-----------+  \          +-------------+             +-------------+
             ^                     ^      ^                       ^
             .       \             .      *                       *
             .                     .      *************************
             .        \            .
             .                     .
             .         \           .      *************************
             .                     .      *                       *
             .          \      avc v      v                vma    v
             .                 +-------------+             +-------------+
             .           ------|anon_vma  vma|------------>|             |
             .                 |             |             |             |
             .                 +-------------+             +-------------+
             .                     ^      ^                       ^
             .                     .      *                       *
             .......................      *************************

其实此处我画得不够精确,av 和 avc之间应当是树的关系,而不是现在显示的链表的关系。但是我想意思已经表达清楚,即在一个进程中多个vma可以共享同一个anon_vma作为匿名映射的节点。

anon_vma_fork

看过了在单个进程中的情况,接下来我们来看看创建一个子进程时如何调整这个数据结构。这个过程由anon_vma_fork处理。

    anon_vma_fork(vma, pvma)
        anon_vma_clone(vma, pvma)
        anon_vma = anon_vma_alloc()
        avc = anon_vma_chain_alloc()
        anon_vma->root = pvma->anon_vma->root
        anon_vma->parent = pvma->anon_vma
        vma->anon_vma = anon_vma
        anon_vma_chain_link(vma, avc, anon_vma)

这个函数很有意思,我还真是花了些时间去理解它。最开始有点看不清,所以我干脆退回到最简单的状态,也就是当前进程是根进程的时候。此时我才大致的了解了一点fork时究竟发生了什么。

话不多说,还是用一个图来表达

             .......................      *************************
             .                     .      *                       *
     av      v                 avc v      v                vma    v
     +-----------+             +-------------+             +-------------+
 P   |           |<------------|anon_vma  vma|------------>|             |
     |           |<----+       |             |             |             |
     +-----------+      \      +-------------+             +-------------+
             ^                     ^      ^                       ^
             .           \         .      *                       *
             .                     .      *************************
             .            \        .
             .                     .
             .             \       .
             .                     .
             .              \      .      *************************
             .                     .      *                       *
             .               \ avc v      v                       *
             .                 +-------------+                    *
             .                \|anon_vma  vma|\                   *
             .                 |             |                    *
             .                 +-------------+  \                 *
             .                    ^       ^                       *
             .                    .       *       \               *
             ......................       *                       *
                                          *         \             *
                                          *                       *
                                          *           \           *
             .......................      *                       *
             .                     .      *             \         *
     av      v                 avc v      v              \ vma    v
     +-----------+             +-------------+            >+-------------+
 C1  |           |<------------|anon_vma  vma|------------>|             |
     |           |             |             |             |             |
     +-----------+             +-------------+             +-------------+
             ^                     ^      ^                       ^
             .                     .      *                       *
             .......................      *************************

P是父进程,C1是他的一个子进程。当发生fork时,page->mapping没有发生改变,所以依然需要能够从父进程的anon_vma上搜索到对应的页表。此时就得在父进程的rb_root树中保留一个子进程的avc。同时子进程又拥有自己的一套anon_vma。

可以说这个真的是非常有意思的。

对了,代码中还有一个函数anon_vma_clone,在这里我就不展开了。留给大家下来思考一下下。

使用

好了,到了这里我们已经拥有了一个非常强悍的武器 – 匿名反向映射。有了他我们就可以指哪打哪了。

内核也已经给我们准备好了扣动这个核武器的板机 – rmap_walk_anon。

    rmap_walk_anon(page, rwc, true/false)
        anon_vma = page_anon_vma(page), get anon_vma from page->mapping
        pgoff_start = page_to_pgoff(page);
            return page_to_index(page)
        pgoff_end = pgoff_start + hpage_nr_pages(page) - 1;
        anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff_start, pgoff_end)
        rwc->rmap_one(page, vma, address, rwc->arg) -> do the real work

有了上面的基础知识,我想看这段代码就不难了。还记得上面看到过的那个rb_root么?对了,我们就是沿着这颗红黑树找到的vma,然后再找到了页表。

嗯,一切都感觉这么的完美。

本文分享自微信公众号 - Linux阅码场(LinuxDev),作者:理查德

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 打造用户态存储利器,基于SPDK的存储引擎Blobstore & BlobFS

    Blobstore是位于SPDK bdev之上的Blob管理层,用于与用户态文件系统Blobstore Filesystem (BlobFS)集成,从而...

    Linux阅码场
  • From High Ceph Latency to Kernel Patch with eBPF/BCC

    There are a lot of tools for debugging kernel and userspace programs in Linux. M...

    Linux阅码场
  • 宋宝华:一个简单的python脚本看透Linux程序对库的依赖

    在下今天写了一个小小的python程序,可以在完全不看源代码的情况下,分析a如果调用b.so的时候,会引用b.so的哪些函数,它的用法如下:

    Linux阅码场
  • 网站建设的完整流程和步骤,新手必看

    现在互联网已经非常普及,网站建设的需求也是越来越多,那么网站建设怎么做呢?下面小熊优化的小编就给大家说一下网站建设的流程:

    用户4831957
  • POJ-2356 Find a multiple(DFS,抽屉原理)

    Find a multiple Time Limit: 1000MS Memory Limit: 65536K Total Submissio...

    ShenduCC
  • 组合数学-抽屉原理

    抽屉原理又称鸽巢原理:把 n+1个物品放进 n个盒子里,那么至少有一个盒子包含两个及以上的物品。

    唔仄lo咚锵
  • 如何使用CRM Document Template Profile

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • 3.1.2 使用绘图API绘制Contour的思路

    A week or so back I wrote about a package I ported/modified to create the Delaun...

    周星星9527
  • ZOJ 3713 In 7-bit

    Very often, especially in programming contests, we treat a sequence of non-white...

    ShenduCC
  • .net下模拟不同身份登陆以获取不同权限

    .net下模拟不同身份登陆以获取不同权限     作者:佚名 时间:-- : 出处:互连网 责编:chinaitpower                   ...

    阿新

扫码关注云+社区

领取腾讯云代金券