Linux内核反向映射RMAP:加速数据访问的关键技术

系统 Linux
在计算机系统中,内存管理一直是操作系统的核心功能之一,对于 Linux 系统而言,其内存管理机制随着时间的推移不断演进,从早期的简单形式逐渐发展为如今复杂而高效的体系。

而今天要深入剖析的Linux内核反向映射 RMAP(Reverse Mapping),正是一把能大幅加速数据访问的利刃,它在内存管理的舞台上扮演着举足轻重的角色。从基础概念到复杂的实现机制,RMAP 背后藏着诸多奥秘,接下来,就让我们一同踏上探索之旅,揭开它的神秘面纱 。

一、Linux 内存管理的 “前世今生”

在计算机系统中,内存管理一直是操作系统的核心功能之一,对于 Linux 系统而言,其内存管理机制随着时间的推移不断演进,从早期的简单形式逐渐发展为如今复杂而高效的体系。

早期的计算机系统中,内存管理非常直接和简单。程序直接运行在物理内存上,采用连续分配的方式,将物理内存划分为不同的区域,每个区域分配给一个程序使用。这种方式虽然简单易懂,但存在诸多严重问题,比如进程地址空间无法隔离,一个进程可以随意访问其他进程的内存空间,这严重威胁系统安全;内存使用效率低下,由于需要连续的内存空间,容易产生内存碎片,导致内存浪费;而且程序运行地址不确定,每次运行都要在内存中寻找足够大的空闲区域,增加了程序重定位的复杂性。

为了解决这些问题,虚拟内存的概念应运而生。虚拟内存作为程序和物理内存之间的中间层,为每个进程提供独立的地址空间,实现进程地址空间的隔离,增强系统安全性。在 Linux 系统中,虚拟内存通过分页和分段技术实现虚拟地址到物理地址的映射。分段技术将程序地址空间划分为不同逻辑段,如代码段、数据段、栈段等,每个段在物理内存中可以不连续存储,解决程序运行地址不确定问题,但仍存在外部碎片问题。分页技术则把虚拟内存和物理内存划分为固定大小的页,以页为单位进行映射和管理,大大提高内存利用率,减少内存碎片。

随着 Linux 系统应用场景不断扩展和硬件技术发展,内存管理面临新挑战。特别是在多进程、多线程环境下,如何快速准确地确定物理页面与虚拟页面之间的映射关系,成为提高内存管理效率的关键。在早期 Linux 内核版本中(如 2.4 内核),当需要确定某物理页面是否被某个进程映射时,必须遍历每个进程的页表,这一过程工作量巨大,效率极低。比如在一个拥有大量进程的服务器系统中,若要查找某个物理页面的映射关系,遍历所有进程页表可能需要耗费大量 CPU 时间,严重影响系统性能。

为应对这一挑战,在 Linux 2.5 内核开发期间,反向映射(Reverse Mapping,RMAP)的概念被提出并逐步完善。RMAP 的出现,彻底改变 Linux 内存管理中查找页面映射关系的方式,极大提高内存管理效率,为 Linux 系统在各种复杂环境下稳定高效运行奠定坚实基础。

二、RMAP是什么?

RMAP,即反向映射(Reverse Mapping),是 Linux 内核中用于解决从物理页面快速查找其对应的虚拟地址映射关系的关键机制 ,与传统的从虚拟地址到物理地址的正向映射方向相反,RMAP 建立了从物理页面到虚拟地址空间的反向映射关系。在实际运行中,当系统需要对某个物理页面进行操作时,如回收、迁移或共享,RMAP 能帮助内核迅速定位到所有映射到该物理页面的虚拟地址,极大提高操作效率。

在页面回收场景中,当系统内存不足时,需要将一些长时间未使用的页面从物理内存中回收,释放出空间供其他更急需内存的进程使用。在没有 RMAP 机制之前,要回收一个物理页面,内核必须遍历系统中每个进程的页表,检查该物理页面是否被某个进程映射,这一过程效率极低,因为现代计算机系统中往往运行着大量进程,每个进程又有庞大的页表,遍历所有进程页表的时间开销巨大。而有了 RMAP 后,内核可以直接通过物理页面的相关数据结构,快速找到所有映射该页面的虚拟地址,然后断开这些映射关系,将页面回收,大大提高内存回收效率。

在页面迁移场景中,为了实现内存的高效利用和负载均衡,有时需要将一个物理页面从一个内存区域迁移到另一个内存区域,例如在 NUMA(Non - Uniform Memory Access)架构的系统中,为了让进程更高效地访问内存,可能需要将进程使用的页面迁移到距离其 CPU 更近的内存节点上。在迁移之前,同样需要找到所有映射到该页面的虚拟地址,以便在迁移完成后更新映射关系。如果没有 RMAP,查找这些映射关系的过程会非常耗时,影响系统性能;而RMAP 的存在使得这一查找过程变得快速而准确,确保页面迁移能够顺利进行。

RMAP 机制对于 Linux 系统的内存管理效率提升至关重要,它是解决复杂内存管理问题的核心方案,为系统在各种内存操作场景下的高效运行提供了有力支持。

2.1正向映射

当进程分配内存并发生写操作时,会分配虚拟地址并产生缺页,进而分配物理内存并建立虚拟地址到物理地址的映射关系, 这个叫正向映射。

图片图片

2.2反向映射

反过来, 通过物理页面找到映射它的所有虚拟页面叫反向映射(reverse-mapping, RMAP)。

图片图片

2.3RMAP的背景

用户进程在使用虚拟内存的过程中,从虚拟内存页面映射到物理内存页面时,PTE保留这个记录,page数据结构中的_mapcount记录有多少个用户PTE映射到物理页面。用户PTE是指用户进程地址空间和物理页面建立映射的PTE,不包括内核地址空间映射物理页面时产生的PTE。有的页面需要迁移,有的页面长时间不使用,需要交换到磁盘。在交换之前,必须找出哪些进程使用这个页面,然后解除这些映射的用户PTE。一个物理页面可以同时被多个进程的虚拟内存映射,但是一个虚拟页面同时只能映射到一个物理页面。

在Linux 2.4内核中,为了确定某一个页面是否被某个进程映射,必须遍历每个进程的页表,因此工作量相当大,效率很低。在Linux2.5内核开发期间,提出了反向映射(Reverse Mapping,RMAP)的概念。

三、RMAP 关键数据结构剖析

RMAP的主要目的是从物理页面的page数据结构中找到有哪些映射的用户PTE,这样页面回收模块就可以很快速和高效地把这个物理页面映射的所有用户PTE都解除并回收这个页面。在深入了解 RMAP 的工作原理之前,我们先来剖析一下支撑 RMAP 机制运行的关键数据结构,它们是 RMAP 实现高效反向映射的基石,理解这些数据结构的构成和相互关系,对于掌握 RMAP 的核心思想至关重要。

为了达到这个目的,内核在页面创建时需要建立RMAP的“钩子”,即建立相关的数据结构,RMAP系统中有两个重要的数据结构:一个是anon_vma,简称AV;另一个是anon_vma_chain,简称AVC。

3.1struct anon_vma(AV)

struct anon_vma 是 RMAP 机制中用于管理匿名内存映射区域的关键数据结构,在连接物理页面的 page 数据结构和虚拟内存区域的 vm_area_struct(VMA) 中扮演着核心角色 。当进程创建匿名内存映射(比如通过 malloc 分配内存,在缺页异常时创建的匿名页面)时,anon_vma 就开始发挥作用。

从定义来看,struct anon_vma 包含多个重要字段:

struct anon_vma {
    struct anon_vma *root;         /* Root of this anon_vma tree */
    struct rw_semaphore rwsem;     /* W: modification, R: walking the list */
    atomic_t refcount;             /* 引用计数,记录当前有多少个VMA引用了该anon_vma */
    unsigned degree;               /* Count of child anon_vmas and VMAs which points to this anon_vma */
    struct anon_vma *parent;       /* Parent of this anon_vma */
    struct rb_root_cached rb_root; /* Interval tree of private "related" vmas */
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

其中,refcount 字段用于引用计数,它记录着当前有多少个 VMA 引用了该 anon_vma。当一个新的 VMA 与该 anon_vma 建立关联时,refcount 会增加;反之,当 VMA 与 anon_vma 解除关联时,refcount 减少。当 refcount 变为 0 时,说明没有 VMA 再引用这个 anon_vma,此时就可以对 anon_vma 进行回收。

rb_root 字段是一个红黑树的根节点,通过这个红黑树,anon_vma 可以高效地管理和查找与它相关的 VMA。在实际场景中,当系统需要查找所有引用某个匿名页面的 VMA 时,就可以通过 page 结构中的 mapping 指针找到对应的 anon_vma,然后遍历 anon_vma 的红黑树,快速获取所有相关的 VMA,大大提高查找效率。

3.2struct anon_vma_chain(AVC)

struct anon_vma_chain 是连接 vm_area_struct(VMA) 和 anon_vma 的桥梁,在 RMAP 机制中起着不可或缺的枢纽作用。它主要用于维护 VMA 和 anon_vma 之间的关联关系,使得内核能够方便地从 VMA 找到对应的 anon_vma,反之亦然。

struct anon_vma_chain {
    struct vm_area_struct *vma;         // 指向对应的VMA
    struct anon_vma *anon_vma;         // 指向对应的anon_vma
    struct list_head same_vma;         // 用于链接与同一VMA相关的所有anon_vma_chain节点
    struct rb_node rb;                 // 用于将anon_vma_chain节点插入到anon_vma的红黑树中
    unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
    unsigned long cached_vma_start, cached_vma_last;
#endif
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在进程创建子进程时,子进程会复制父进程的地址空间和页表。此时,anon_vma_chain 就会发挥作用,它会在子进程的 VMA 和父进程的 anon_vma 之间建立连接。具体来说,子进程的每个 VMA 都会创建一个 anon_vma_chain 节点,该节点的 vma 指针指向子进程的 VMA,anon_vma 指针指向父进程的 anon_vma,然后通过 same_vma 链表和 rb 红黑树节点,将这个 anon_vma_chain 节点插入到相应的链表和红黑树中,从而实现子进程 VMA 与父进程 anon_vma 的关联 。这样,当系统需要对某个匿名页面进行操作时,就可以通过 anon_vma_chain 快速找到所有与该页面相关的 VMA,无论是在父进程还是子进程中。

3.3struct vm_area_struct(VMA)

struct vm_area_struct 用于描述进程地址空间中的一段虚拟内存区域,是进程虚拟内存管理的重要数据结构,在 RMAP 机制中也有着关键作用,它记录了虚拟内存区域的起始地址、结束地址、访问权限、所属的内存描述符等重要信息,与 RMAP 相关的字段主要有 anon_vma_chain 和 anon_vma。

struct vm_area_struct {
    unsigned long vm_start;         // 虚拟内存区域的起始地址
    unsigned long vm_end;           // 虚拟内存区域的结束地址
    struct mm_struct *vm_mm;       // 指向所属的内存描述符
    struct vm_area_struct *vm_next; // 指向下一个虚拟内存区域
    pgprot_t vm_page_prot;         // 页面保护标志
    unsigned long vm_flags;        // 虚拟内存区域的标志
    struct list_head anon_vma_chain; // 用于链接anon_vma_chain节点,通过这个链表可以找到与该VMA相关的所有anon_vma_chain
    struct anon_vma *anon_vma;     // 指向该VMA对应的anon_vma
    // 其他字段...
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

在进程运行过程中,当发生缺页异常需要创建新的匿名页面时,内核会为该页面所在的 VMA 分配一个 anon_vma,并通过 anon_vma_chain 将 VMA 和 anon_vma 连接起来。例如,当一个进程调用 malloc 分配内存时,会在进程的地址空间中创建一个新的 VMA 来管理这块内存,同时为这个 VMA 关联一个 anon_vma,并通过 anon_vma_chain 建立两者之间的联系。这样,在后续的内存操作中,如页面回收、迁移等,就可以通过 VMA 的这些字段快速找到相关的 anon_vma 和其他关联信息,从而高效地完成内存管理任务。

四、RMAP的工作流程与原理

4.1匿名页面的创建与 RMAP 初始化

在进程运行过程中,当访问的虚拟地址尚未映射到物理页面时,会触发缺页异常。此时,内核会调用 do_anonymous_page 函数来处理匿名页面的创建。以 do_anonymous_page 函数为例,当进程调用 malloc 分配内存时,如果相应的虚拟地址尚未映射物理页面,就会触发缺页异常,进而调用 do_anonymous_page 函数。在这个函数中,会先后调用 anon_vma_prepare 和 page_add_new_anon_rmap 函数来完成 RMAP 相关的初始化工作。

static vm_fault_t do_anonymous_page(struct vm_fault *vmf) {
    struct vm_area_struct *vma = vmf->vma;
    struct page *page;
    vm_fault_t ret = 0;
    pte_t entry;

    // 准备anon_vma
    if (unlikely(anon_vma_prepare(vma)))
        goto oom;

    // 分配物理页面
    page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
    if (!page)
        goto oom;

    // 添加新的匿名反向映射
    page_add_new_anon_rmap(page, vma, vmf->address, false);

    // 其他处理...
    return ret;

oom:
    return VM_FAULT_OOM;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

anon_vma_prepare 函数主要负责为 VMA 分配和初始化 anon_vma 结构,并建立 anon_vma_chain 连接。在这个函数中,首先尝试查找是否存在可合并的 anon_vma,如果不存在则分配一个新的 anon_vma。然后,通过 anon_vma_chain_alloc 分配一个 anon_vma_chain 结构,并将其与 VMA 和 anon_vma 进行关联。具体来说,它会将 anon_vma_chain 的 vma 指针指向 VMA,anon_vma 指针指向分配的 anon_vma,然后将 anon_vma_chain 添加到 VMA 的 anon_vma_chain 链表和 anon_vma 的红黑树中,这样就建立了 VMA 和 anon_vma 之间的双向关联。

int __anon_vma_prepare(struct vm_area_struct *vma) {
    struct anon_vma *anon_vma, *allocated;
    struct anon_vma_chain *avc;

    // 分配anon_vma_chain
    avc = anon_vma_chain_alloc(GFP_KERNEL);
    anon_vma = find_mergeable_anon_vma(vma);
    allocated = NULL;

    if (!anon_vma) {
        // 分配新的anon_vma
        anon_vma = anon_vma_alloc();
        allocated = anon_vma;
    }

    anon_vma_lock_write(anon_vma);
    spin_lock(&mm->page_table_lock);

    if (likely(!vma->anon_vma)) {
        vma->anon_vma = anon_vma;
        // 链接anon_vma_chain
        anon_vma_chain_link(vma, avc, anon_vma);
    }

    // 解锁相关锁
    spin_unlock(&mm->page_table_lock);
    anon_vma_unlock_write(anon_vma);

    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

page_add_new_anon_rmap 函数则将新分配的物理页面与 VMA 建立反向映射关系。它会设置页面的 mapping 字段指向 anon_vma,并计算页面在 VMA 中的索引值存储在 index 字段中。具体实现中,先设置页面的 SwapBacked 标志,表示该页面可交换到磁盘。然后根据页面是否为复合页(如大页),设置 mapcount 字段。接着,通过 __page_set_anon_rmap 函数将 anon_vma 加上 PAGE_MAPPING_ANON 后赋值给页面的 mapping 字段,并计算页面在 VMA 中的线性索引值赋值给 index 字段,从而完成物理页面与 VMA 的反向映射关联 。

void page_add_new_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address, bool compound) {
    int nr = compound? hpage_nr_pages(page) : 1;

    VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);

    __SetPageSwapBacked(page);

    if (compound) {
        VM_BUG_ON_PAGE(!PageTransHuge(page), page);
        atomic_set(compound_mapcount_ptr(page), 0);
        __inc_node_page_state(page, NR_ANON_THPS);
    } else {
        VM_BUG_ON_PAGE(PageTransCompound(page), page);
        atomic_set(&page->mapcount, 0);
    }

    __mod_node_page_state(page_pgdat(page), NR_ANON_MAPPED, nr);
    __page_set_anon_rmap(page, vma, address, 1);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

4.2子进程创建时的 RMAP 处理

当父进程通过 fork 创建子进程时,子进程需要复制父进程的地址空间及页表。在这个过程中,dup_mmap 函数负责复制父进程的进程地址空间,遍历父进程的 VMA 链表,为每个 VMA 创建一个新的 VMA 数据结构,并将父进程 VMA 中的相关信息复制到子进程的 VMA 中。

static __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm) {
    struct vm_area_struct *mpnt, *tmp;
    int retval;

    for (mpnt = oldmm->map; mpnt; mpnt = mpnt->vm_next) {
        // 创建临时VMA数据结构tmp,把父进程VMA复制到子进程刚创建的tmp中
        tmp = vm_area_dup(mpnt);
        if (!tmp) {
            retval = -ENOMEM;
            goto free_vmas;
        }

        tmp->vm_mm = mm;

        // 为子进程创建相应anon_vma数据结构并添加到红黑树
        if (anon_vma_fork(tmp, mpnt)) {
            __vma_link_rb(mm, tmp, rb_link, rb_parent);
        }

        // 复制父进程的PTE到子进程
        if (!(tmp->vm_flags & VM_WIPEONFORK)) {
            retval = copy_page_range(tmp, mpnt);
            if (retval)
                goto unlink_vma;
        }
    }

    return 0;

unlink_vma:
    __vma_unlink_rb(mm, tmp);
free_vmas:
    // 释放相关资源
    return retval;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

anon_vma_fork 函数在子进程创建过程中起着关键作用,负责为子进程的 VMA 创建相应的 anon_vma 数据结构,并建立子进程 VMA 与父进程 anon_vma 之间的关联。如果父进程的 VMA 没有关联的 anon_vma,则直接返回。否则,尝试克隆父进程的 anon_vma 到子进程。如果克隆失败,则分配一个新的 anon_vma 给子进程,并将其与子进程的 VMA 通过 anon_vma_chain 连接起来,同时设置相关的父子关系和引用计数 。

int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma) {
    struct anon_vma_chain *avc;
    struct anon_vma *anon_vma;
    int error;

    if (!pvma->anon_vma)
        return 0;

    error = anon_vma_clone(vma, pvma);
    if (vma->anon_vma)
        return 0;

    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;
    get_anon_vma(anon_vma->root);

    vma->anon_vma = anon_vma;
    // 链接anon_vma_chain
    anon_vma_chain_link(vma, avc, anon_vma);

    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

在实际场景中,假设父进程有一个 VMA 用于管理堆内存,当通过 fork 创建子进程时,子进程会复制父进程的这个 VMA 结构,并通过 anon_vma_fork 函数建立与父进程 anon_vma 的关联。这样,在后续内存操作中,系统可以通过 RMAP 机制统一管理父子进程中与该 VMA 相关的匿名页面,实现内存的高效利用和共享。

4.3页面回收与迁移时的 RMAP 应用

当系统内存不足时,kswapd 内核线程会启动页面回收机制,查找可以回收的页面。对于匿名页面,kswapd 会利用 RMAP 机制来找到所有映射该页面的 PTE 并断开映射。具体来说,kswapd 会调用 try_to_unmap 函数,该函数是反向映射的核心函数之一,它会遍历页面的反向映射关系,找到所有映射该页面的 VMA,并调用 try_to_unmap_one 函数来断开每个 VMA 中对该页面的映射。

int try_to_unmap(struct page *page, enum ttu_flags flags) {
    int ret;
    struct rmap_walk_control rwc = {
      .rmap_one = try_to_unmap_one,
      .arg = (void *)flags,
      .done = page_not_mapped,
      .anon_lock = page_lock_anon_vma_read,
    };

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

    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;

    return ret;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

在页面迁移场景中,当需要将一个物理页面从一个内存区域迁移到另一个内存区域时,同样需要利用 RMAP 机制。例如在 NUMA 架构系统中,为了优化内存访问性能,可能需要将某个进程使用的页面迁移到距离其 CPU 更近的内存节点上。在迁移过程中,首先通过 RMAP 找到所有映射该页面的 PTE,然后将这些 PTE 标记为无效,并在目标内存区域分配新的物理页面。迁移完成后,再更新 PTE 的映射关系,指向新的物理页面 。整个过程中,RMAP 机制确保了在页面迁移前后,所有相关进程的页表能够正确更新,保证进程对内存的正常访问。

五、反向映射RMAP应用

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

  1. kswapd内核线程回收页面需要断开所有映射了该匿名页面的用户PTE页表项。
  2. 页面迁移时,需要断开所有映射到匿名页面的用户PTE页表项。

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

/**
 * try_to_unmap - try to remove all page table mappings to a page
 * @page: the page to get unmapped
 * @flags: action and flags
 *
 * Tries to remove all the page table entries which are mapping this
 * page, used in the pageout path.  Caller must hold the page lock.
 * Return values are:
 *
 * SWAP_SUCCESS    - we succeeded in removing all mappings------------成功解除了所有映射的PTE。
 * SWAP_AGAIN    - we missed a mapping, try again later---------------可能错过了一个映射的PTE,需要重来一次。
 * SWAP_FAIL    - the page is unswappable-----------------------------失败
 * SWAP_MLOCK    - page is mlocked.-----------------------------------页面被锁住了
 */
int try_to_unmap(struct page *page, enum ttu_flags flags)
{
    int ret;
    struct rmap_walk_control rwc = {
        .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);

    /*
     * During exec, a temporary VMA is setup and later moved.
     * The VMA is moved under the anon_vma lock but not the
     * page tables leading to a race where migration cannot
     * find the migration ptes. Rather than increasing the
     * locking requirements of exec(), migration skips
     * temporary VMAs until after exec() 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;
    return ret;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

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

int rmap_walk(struct page *page, struct rmap_walk_control *rwc)
{
    if (unlikely(PageKsm(page)))
        return rmap_walk_ksm(page, rwc);
    else if (PageAnon(page))
        return rmap_walk_anon(page, rwc);
    else
        return rmap_walk_file(page, rwc);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

面以匿名页面的unmap为例:

static int rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc)
{
    struct anon_vma *anon_vma;
    pgoff_t pgoff;
    struct anon_vma_chain *avc;
    int ret = SWAP_AGAIN;

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

    pgoff = page_to_pgoff(page);
    anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff, pgoff) {------遍历anon_vma->rb_root红黑树中的AVC,从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;

        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);
    return ret;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

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

六、补充:其他反向映射

6.1KSM反向映射

KSM(kernel shared memory)是一种内存共享机制,启用 ksm后,ksm守护进程会定期扫描用户内存区域,并合并具有相同内存的匿名物理页面以减少页面的冗余。

和“多个子进程”不一样的是,映射到同一个物理页面, ksm 在不同进程的虚拟地址空间的虚拟地址肯定是不一样的,因此就不能使用 page->index 来表示虚拟页号。

对于ksm 页面,内核维护了一个结构体 rmap_item,用它来保存同一物理页面在不同的虚拟地址空间信息;物理页的 mapping 指向了一个 struct stable_node 结构体,通过 stable_node->hlist 将 rmap_item 连接起来。

rmap_item中维护了page对应的虚拟地址address及anon_vma结构。(函数实现:mm\rmap.c:page_referenced -> rmap_walk ->rmap_walk_ksm);

图片图片

6.2文件页的反向映射

文件页的反向映射是指在操作系统中,将虚拟内存地址与物理内存地址之间进行映射的过程。它允许操作系统跟踪每个虚拟页到物理页的对应关系。

通常,操作系统使用页表来管理虚拟内存和物理内存之间的映射关系。每个进程都有自己独立的页表,通过查阅页表,操作系统可以将进程的虚拟地址转换为对应的物理地址。

反向映射则是根据给定的物理内存地址,找到对应的虚拟内存地址。这样可以在需要时快速找到一个物理页面所属的进程和虚拟地址。

实现反向映射通常需要一些数据结构支持,比如逆向页表或者其他类似结构。这样,在需要时就可以从给定的物理地址追溯到相应的虚拟地址。

图片图片

物理页的 mapping 会指向文件对应的 address_space,address_space 的i_mmap 保存了所有的vma。物理页的所有 vma,很容易找到,那不同vma的虚拟地址呢?

物理页的index 表示物理页在文件内的offset(以page size为单位),vma的vm_pgoff 表示 vma 区域在文件中的偏移量。那么,在不同的vma的虚拟地址= page->index - vma->vm_pgoff + vma->vm_start 。

图片图片

知道了 vma和 虚拟地址,就可以解除pte映射了,(函数实现:mm\rmap.c:page_referenced -> rmap_walk ->rmap_walk_file)。

责任编辑:武晓燕 来源: 深度Linux
相关推荐

2020-11-20 07:55:55

Linux内核映射

2018-01-03 00:38:20

大数据Hadoop分布式文件系统

2015-09-11 13:54:51

大数据关键技术

2021-03-03 09:32:21

大数据关键技术数据存储

2018-06-14 09:38:53

Linux多核编程

2025-02-17 09:00:00

DeepSeek人工智能AI

2023-09-20 20:11:07

Java

2018-05-20 15:43:50

2018-05-19 00:13:08

2018-11-21 14:44:33

数据库容器数据架构

2018-03-09 12:00:02

数字化数据库容器

2018-12-04 15:32:09

数据处理大数据数据分析

2011-03-21 15:29:46

2015-05-25 17:38:38

4GTD-LTE

2013-07-31 09:19:08

4G网络4G牌照4G

2016-11-17 18:19:13

VR直播

2015-09-18 16:42:53

软件定义数据中心

2022-04-15 15:03:42

云计算容器Linux

2021-05-17 14:57:22

NFV虚拟化数据
点赞
收藏

51CTO技术栈公众号