[内核内存] 反向映射详解

文章目录

  • 1 匿名页反向映射
    • 1.1 匿名页反向映射关键数据结构
    • 1.2 linux匿名页的反向映射机制
    • 1.3 进程创建时反向映射相关结构体间的关系
    • 1.4 linux为匿名页的VMA分配AV的流程
      • do_anonymous_page函数(缺页异常匿名页处理方式)
        • anon_vma_prepare函数(关联匿名页的VMA和AV)
        • page_add_new_anon_rmap函数(关联匿名页描述符struct page和AV)
    • 1.5 linux子进程创建时匿名页的写时复制(COW)机制
  • 2 文件页反向映射
  • 3 KSM页面反向映射
    • 3.1 什么是KSM页面
    • 3.2 KSM页面反向映射
  • linux Rmap Walk机制

linux os在开启MMU后cpu访问的地址是虚拟地址,cpu要访问一个虚拟地址时会通过mmu将虚拟地址转化为物理地址,该过程称为正向映射。而反向映射(RMAP), 是通过物理地址找到对应的物理页,然后找到共享该物理页的所有VMA。

物理页面描述符struct page中与反向映射有关的两个关键成员是mapping和__mapcount:

struct page {
……
……
atomic_t _mapcount;
union {
……
struct {
……
struct address_space *mapping;
};
……
……
};
  • _mapcount表示共享该物理页面的页表现数目,即有多少个进程页表的pte映射到该物理页面。该值初始值为-1,每增减一个pte映射该值+1
  • mapping用于区分物理页面的类型(文件页,匿名页其他页)
    • mapping不为NULL,且第一位置位,该页为匿名页,mapping指向ano_vma结构
    • mapping不为NULL,且第一位为0,该页为文件页,mapping指向address_space结构
    • mapping为NULL其他页

文件页和匿名页采用了不同的数据结构和机制处理与页面相关的虚拟内存域。下面主要介绍匿名页和文件页反向映射相关的数据结构和实现机制,还会简单介绍一下虚拟内存技术中KSM的机制和KSM页的反向映射原理。

1 匿名页反向映射

1.1 匿名页反向映射关键数据结构

匿名页的反向映射有3个关键的数据结构:

  • struct vm_area_struct(简称VMA)在进程虚拟地址空间有过介绍,描述进程一段的虚拟地址空间,该结构体中与反向映射下关的成员如下所示:

    ///include/linux/mm_types.h
    /*
     *用于描绘进程地址空间中的一段区域
     */
    struct vm_area_struct {
    	...
        ...
        /*
    	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
    	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
    	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
    	 * or brk vma (with NULL file) can only be in an anon_vma list.
    	 */
    	struct list_head anon_vma_chain; /* Serialized by mmap_sem &
    					  * page_table_lock */
    	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */
    	...
        ...
    }
    
  • struct ano_vma(简称AV)管理匿名页面映射的所有VMA,匿名页面的struct page的mapping成员指向该结构体。该结构体设计出来就是为了反向映射服务

    // /include/linux/rmap.h
    /*
     *(1)对于一个页框,若该页为匿名页,则其struct page中的mapping指向AV(anon_vma的简称)
     *(2)AV结构体用于管理匿名页对应的所有VMAs.可以找到页对应的AV,然后再遍历AV的rb_root查询到该页框所有的
     *     VMA。
     */
    struct anon_vma {
    	struct anon_vma *root;		/* Root of this anon_vma tree */
    	struct rw_semaphore rwsem;	/* W: modification, R: walking the list */
    	/*
    	 * The refcount is taken on an anon_vma when there is no
    	 * guarantee that the vma of page tables will exist for
    	 * the duration of the operation. A caller that takes
    	 * the reference is responsible for clearing up the
    	 * anon_vma if they are the last user on release
    	 */
    	atomic_t refcount;
    
    	/*
    	 * Count of child anon_vmas and VMAs which points to this anon_vma.
    	 *
    	 * This counter is used for making decision about reusing anon_vma
    	 * instead of forking new one. See comments in function anon_vma_clone.
    	 */
    	unsigned degree;
    
    	struct anon_vma *parent;	/* Parent of this anon_vma */
    
    	/*
    	 * NOTE: the LSB of the rb_root.rb_node is set by
    	 * mm_take_all_locks() _after_ taking the above lock. So the
    	 * rb_root must only be read/written after taking the above lock
    	 * to be sure to see a valid next pointer. The LSB bit itself
    	 * is serialized by a system wide lock only visible to
    	 * mm_take_all_locks() (mm_all_locks_mutex).
    	 */
    
    	/* Interval tree of private "related" vmas */
    	struct rb_root_cached rb_root;
    };
    
  • struct anon_vma_chain检查(AVC),该结构体也是为反向映射服务的,目的链接VMA和AV,缓解反向映射遇到了效率和锁竞争激烈问题。

    // /include/linux/rmap.h
    //AVC是链接VMA和AV间的桥梁
    struct anon_vma_chain {
    	struct vm_area_struct *vma;
    	struct anon_vma *anon_vma;
    	//通过same_vma链表节点,将anon_vma_chain添加到vma->anon_vma_chain链表中;
    	struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */
    	//通过rb红黑树节点,将anon_vma_chain添加到anon_vma->rb_root的红黑树中;
        //该rb为anon_vma->rb_root中的一个节点,该节点存储自己的avc信息
    	struct rb_node rb;			/* locked by anon_vma->rwsem */
    	unsigned long rb_subtree_last;
    #ifdef CONFIG_DEBUG_VM_RB
    	unsigned long cached_vma_start, cached_vma_last;
    #endif
    };
    

ps:

  1. AVC是一个链接VMA和AV桥梁的数据结构,每个AVC都有一组对应的VMA和AV。在VMA中所有指向该VMA的AVC都会被链接成一个链表,链表头结点存储在VMA的anon_vma_chain成员上。而对于一个AV数据,他会管理若干个AVC(间接管理VMA),AV会将与其关联的所有AVC存储在一个红黑树中,红黑树的根为AV的rb_root成员。
  2. 为什么要用如此多的结构体和复杂的机制来实现匿名页反向映射:
    1. 节省内存空间。
    2. 在linux os中一个虚拟页面同时只能有一个物理页面与之映射,但一个物理页面可以同时被多个进程的虚拟地址内存映射。不同虚拟页面同时映射到同一物理页面是因为子进程克隆父进程VMA,和KSM机制的存在。

1.2 linux匿名页的反向映射机制

  1. 通过物理地址获取到该物理地址对应物理页描述符struct page
  2. 利用页描述符的mapping成员获取到该物理页对应的AV数据。
  3. AV中的rb_root成员是一颗红黑树的树根,树的成员存储着该物理页对应的所有AVC数据。然后遍AV->rb_root红黑树。处理红黑树的每个节点中的AVC数据。a为处理每个AVC数据的方法。
    1. 利用每个AVC数据中的vma成员,可以获取到该物理页对应的VMA。利用物理页描述符page中的index成员,可以在该VMA中获取到该页起始位置的虚拟地址。同时我们也能轻松通过VMA获取到对应进程的pid号。
  4. 遍历完AV中的rb_root红黑树中的每个节点的AVC数据,我们就能获得映射到该物理页的所有进程的pid和对应的VMA(虚拟地址)。

上述整个流程如图1所示。

[内核内存] 反向映射详解_第1张图片

1.匿名页正向映射和反向映射示意图

1.3 进程创建时反向映射相关结构体间的关系

由于linux进程具有写时复制技术,子进程在创建时共享其父进程的匿名页;只有当子进程对匿名页进行写操作时,子进程才会新分配一个物理页,并将原先匿名页的数据copy到新创建的物理页中,最后子进程通过虚拟地址向新创建的物理页进行写操作。

下面模拟linux中一个进程创建,子进程创建,孙进程创建这些场景中匿名页反向映射相关结构体的变化过程:

  1. 进程A被创建,该进程的匿名映射VMA0通过page fault分配第一个物理页。此时进程A中VMA0,AV0和AVC0这3个数据结构的关系图如图2所示:[内核内存] 反向映射详解_第2张图片

    2.多进程VAM,AV和AVC关系建立示意图(parent)
        图2中进程A新分配的物理页pi会映射到进程A中VMA0某一段对应的虚拟地址上。因为该页为匿名页为了维护反向映射的机制,物理页pi的描述符
    struct page中的mapping成员指向AV0,struct page的index成员是pi物理页对应虚拟地址在VMA0中的偏移。
        为了维护反向映射的机制,进程A在建立虚拟地址和pi物理地址的过程中会创建AV0和AVC0。对于AV0,他是进程A中的一个strutc anon_vma,因为
    AV0此处是一个顶级结构所以stuct anon_vma的root和parent成员指向它自身。AV0该数据结构通过AVC0的中转来管理与pi物理页有关的VMA结构(此时只
    有VMA0与pi有关)。因为AVC0是一个struct anon_vma_chain结构体,他的anon_vam和vma分别指向AV0和VMA0,所以AVC0是链接AV0和VMA0的桥梁.此外
    AVC0插入到AV0的b_root成员指向的红黑树中,同时也插入到VMA0的anon_vma_chain成员指向的链表中。
    
  2. 进程A执行fork创建一个子进程B(VMA1是为子进程B创建的虚拟地址空间段),此时进程A,B中VMA,AVC和AVC3者间的关系如图3所示:

    [内核内存] 反向映射详解_第3张图片

    3.多进程VAM,AV和AVC关系建立示意图(son)

    进程A通过fork创建子进程B,则子进程B会将父进程的VMA0复制到子进程中记为VMA1,这时子进程会为自己的VMA1分配一个自己的AV记为AV1。接下来就是为进程A,B相关的AVC,AV和VMA建立连接关系:

    1. 为子进程B的VMA1和父进程A的VA0建立桥梁关系:
      1. 给子进程B分配一个AVC_0-1用于连接父进程中VMA1和子进程中的AV0(实际就是讲AVC0中的内容拷贝到AVC_0-1)
      2. 在子进程B中将AVC_0-1插入到VMA1的anon_vma_chain成员指向的双向链表中。
      3. 在父进程A中将将子进程B创建的AVC_0-1插入到父进程AV0的rb_root成员指向的的红黑树中
    2. 建立子进程B中自身的VMA1和AV1间的关系(此时AV1并没有与物理页建立关系)
      1. 给子进程B分配一个AVC1用于连接进程B自身的VMA1和AV1。
      2. 在进程B中将AVC1插入VMA1的anon_vma_chain成员指向的双向链表中
      3. 在进程B中将AVC1插入到AV1的rb_root成员指向的红黑树中
      4. 在进程B中将AV1的地址赋值给VMA1的av成员变量
      5. 在子进程B中将AV1的root成员指向父进程A的AV0,然后将B进程的AV1中的parent成员指向父进程A的AV0。

    通过上面建立的关系可以达到如下目的:

    1. 父进程A可以通过AV0的rb_root访问到自身的VMA0和子进程的VMA1(以红黑树中的各个AVC为桥梁简介访问)
    2. 子进程B也可以访问到父进程A中的AV0(通过AVC_0-1,因为AVC_0-1插入到了VMA1的anon_vma_chain成员指向的双向链表)

    父进程A还可以创建其他的子进程,新建进程与父进程间的关系和进程B基本一样。只需要注意父进程A每创建一个新的子进程,A进程的AV0的rb_root指向的红黑树就必定会插入一个起桥梁作用的AVC,用该AVC来将新建子进程的VMA和AV0连接起来(该新建子进程的VMA是VMA0的一个拷贝)

  3. 若子进程B再通过fork创建一个进程C,则父进程A,子进程B和孙子进程C够成的3层AV和VMA的关系结构如图4所示。

    [内核内存] 反向映射详解_第4张图片

    4.多进程VAM,AV和AVC关系建立示意图(grandson)

    子进程B创建孙子进程C,在执行fork时相关操作同2基本一致,只是需要多建立一层爷孙关系,具体步骤如下:

    1. 先给C进程分配一个VMA2,然后将B进程中VMA1拷给C进程的VMA2中。
    2. 为了关联进程C与爷进程A,给C进程分配一个AVC_0-2结构用于连接C进程中的VMA2和A进程中的AV0.并将 AVC_0-2分别插入VMA2的anon_vma_chain成员指向的双向链表和AV0的rb_root成员指向的红黑树中
    3. 为了关联进程C与父进程B,给C进程分配一个AVC_1-2结构用于连接C进程中的VMA2和B进程中的AV1.并将AVC_1-2分别插入VMA2的anon_vma_chain成员指向的双向链表和AV1的rb_root成员指向的红黑树中
    4. 同样在fork孙子进程C时也需要给C进程的VMA2分配一个自身的AV结构记为AV2.接着分配一个AVC2来连接C进程自身的AV2和VMA2.最后将AVC2插入到VMA2的anon_vma_chain成员指向的双向链表和AV2的rb_root成员指向的红黑树中。
    5. 在进程C中AV2的root成员指向AV0,将AV2的parent成员指向AV1

1.4 linux为匿名页的VMA分配AV的流程

通常linux用户态进程用malloc给自己分配一段内存时(匿名页),会先给进程分配一个新的VMA结构体用于管理虚拟段地址,此时并不会立即给新分配的VMA关联物理内存,只有当进程需要操作新分配的虚拟地址时,cpu才会触发"page fault"机制,该机制通过调用do_anonymous_page函数给新VMA分配一段物理内存并将VMA和新分配的物理内存建立映射关系。函数源码如 下所示。

do_anonymous_page函数(缺页异常匿名页处理方式)

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
        unsigned long address, pte_t *page_table, pmd_t *pmd,
        unsigned int flags)
{
	...
	...
    ...
    /* Allocate our own private page. */
    //为进程地址空间准备struct anon_vma数据结构和struct anon_vma_chain链表。
    if (unlikely(anon_vma_prepare(vma)))
        goto oom;
    //从HIGHMEM区域分配一个zeroed页面
    page = alloc_zeroed_user_highpage_movable(vma, address);
    if (!page)
        goto oom;
	...
	...
    ...
    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);
	...
	...
    ...
}
anon_vma_prepare函数(关联匿名页的VMA和AV)

page fault在建立VMA和物理内存页的映射关系时会用到调用一个名为anon_vma_prepare函数,用此函数来给进程虚拟地址空间中的VMA分配AV,以及利用AVC结构以红黑树和链表的 方式将VMA和AV关联起来。函数调用流程如图5所示。

[内核内存] 反向映射详解_第5张图片

5.给VMA分配AV流程示意图

anon_vma_prepare函数具体代码实现如下:

int anon_vma_prepare(struct vm_area_struct *vma)
{
    struct anon_vma *anon_vma = vma->anon_vma;
    struct anon_vma_chain *avc;

    might_sleep();
    if (unlikely(!anon_vma)) {
        struct mm_struct *mm = vma->vm_mm;
        struct anon_vma *allocated;
		//分配一个struct anon_vma_chain结构。
        avc = anon_vma_chain_alloc(GFP_KERNEL);
        if (!avc)
            goto out_enomem;
		//是否可以和前后vma合并
        anon_vma = find_mergeable_anon_vma(vma);
        allocated = NULL;
        //如果无法合并,则重新分配一个结构体
        if (!anon_vma) {
            anon_vma = anon_vma_alloc();
            if (unlikely(!anon_vma))
                goto out_enomem_free_avc;
            allocated = anon_vma;
        }

        anon_vma_lock_write(anon_vma);
        /* page_table_lock to protect against threads */
        spin_lock(&mm->page_table_lock);
        if (likely(!vma->anon_vma)) {
            //建立struct vm_area_struct和struct anon_vma关联
            vma->anon_vma = anon_vma;
            //建立struct anon_vma_chain和其他结构体的关系。
            anon_vma_chain_link(vma, avc, anon_vma);
            /* vma reference or self-parent link for new root */
            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);
    }
    return 0;

 out_enomem_free_avc:
    anon_vma_chain_free(avc);
 out_enomem:
    return -ENOMEM;
}
page_add_new_anon_rmap函数(关联匿名页描述符struct page和AV)

当通过AVC建立了VMA和AV的关联后,还需要将物理页描述符struct page和其对应的AV关联起来,这样才能打通整个反向映射流程。执行完page_add_new_anon_rmap函数后,我们就可以通过物理页描述符struct page的mapping成员找到对应的AV,进而找到AV红黑树下的所有VMA。(每个VMA可以通过相关方式获取到物理页对应进程pid号和进程对应的虚拟地址)。

page_add_new_anon_rmap函数源码:

void page_add_new_anon_rmap(struct page *page,
    struct vm_area_struct *vma, unsigned long address)
{
    VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
	//设置PG_SwapBacked表示这个页面可以swap到磁盘。
    SetPageSwapBacked(page);
    //设置_mapcount引用计数为0
    atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */
    if (PageTransHuge(page))
        __inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
    __mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
            hpage_nr_pages(page));
    //将page->mapping指向vma对应的AV
    __page_set_anon_rmap(page, vma, address, 1);
}

__page_set_anon_rmap函数源码

static void __page_set_anon_rmap(struct page *page,
    struct vm_area_struct *vma, unsigned long address, int exclusive)
{
    struct anon_vma *anon_vma = vma->anon_vma;

    BUG_ON(!anon_vma);
    //当前页是否有为匿名页
    if (PageAnon(page))
        return;

    /*
     * If the page isn't exclusively mapped into this vma,
     * we must use the _oldest_ possible anon_vma for the
     * page mapping!
     */
    if (!exclusive)
        anon_vma = anon_vma->root;

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

通过page_add_new_anon_rmap函数来关联匿名页描述符struct page和AV,最终得到struct page和AV的映射关系图如图6所示。[内核内存] 反向映射详解_第6张图片

6.匿名页描述符page与AV映射关系

**ps:**anon_vma_prepare函数会在内存管理机制中的很多流程中使用,本小节只对缺页异常处理中匿名页这一块的流程做了分析。下面列出该函数重要的应用。

  1. page fault时内存分配
    1. do_anonymous_pages()函数
    2. do_cow_fault()函数
    3. wp_page_copy()函数
  2. 内存迁移
    1. migrate_vm_insert_page()函数
  3. 堆扩展
    1. expand_downwards()函数
    2. expand_wards()函数

1.5 linux子进程创建时匿名页的写时复制(COW)机制

通过面的介绍我们了解了反向映射的基本原理,以及父子进程间VMA,VAC和VA间得连接关系。下面我们讲解如何利用反向映射相关结构体来实现子进程创建时匿名页的写时复制机制。

  1. 一个进程A有一个匿名VMA1,当进程A访问该VMA1时,出现page fault.于是进入内核态为该VMA1分配一段物理内存记为pi,并建立VMA1与pi的映射。关系图如下:[内核内存] 反向映射详解_第7张图片

    7.反向映射-进程创建匿名页并映射物理内存
  2. 进程A通过fork创建进程B时,linux不会为子进程B创建新的物理内存,而是公用父进程的物理内存。此时A,B进程共享的物理内存被设定为只读权限。如图6所示B进程和A进程共享pi物理内存,在进程B中VMA1虚拟内存段指定的虚拟地址映射到物理内存页pi,VMA1通过 AVC_0-1与进程A的AV0建立连接关系。此时物理页pi在进程B中的反向映射链为:
    AV0—>AVC_0-1—>VMA1。虽然进程B此时对于VMA1有自身的AV结构AV1,但此时AV1未与任何物理内存中的页建立连接关系。

[内核内存] 反向映射详解_第8张图片

8.反向映射-进程创建子进程并共享匿名页
  1. 若进程A或进程B对物理页pi进行写操作时,因为B进程对物理页pi只有读权限,写操作会使CPU会触发 “page fault” 的错误,从而调用do_page_fault()函数执行do_wp_page()操作.do_wp_page()函数会先进行一些安全监测操作,然后调用__do_wp_page()函数做复制操作具体步骤如下:

    1. 先为进程B申请一块新的物理内存区域(即为p(i+3))
    2. 然后将pi物理内存页中的内容复制到p(i+3)物理内存中去
    3. 接着修改VMA1中对应虚拟地址指向的页表项,让其pte页表项中的pfn字段由pi物理页的pfn改写为p(i+3)物理页的pfn.此时进程A,B相同虚拟内存指向了不通的物理内存。注意在解除VMA1中对应虚拟地址和物理页pi的映射关系时,pi在进程B中的反向映射链:
      AV0—>AVC_0-1—>VMA1也会被解除。
    4. 最后修改相关物理页的权限,让进程A对物理页pi具有读写权限,进程B对于物理页p(i+3)具有可读写权限。

    上述3步阐述了linux子进程创建如何利用反向映射相关结构体实现写时复制(COW)技术,最终父子进程中与反向映射相关结构体间的连接关系如图7所示。

[内核内存] 反向映射详解_第9张图片

9.反向映射-子进程写时复制

2 文件页反向映射

同匿名页一样,可能会有多个进程的VMA同时共享一个文件映射页。而进程文件页的反向映射是通过一个与文件相关的结构address_space来进行维护的。

若某个物理页是文件页,则该页的结构描述符struct page的mapping成员指向一个address_space结构体。了解page cache的都知道address_space结构中的page_tree成员指向的基数树用于维护并存储该文件特定区域的文件缓存页。而在文件页的反向映射中address_space结构体的i_mmap成员指向的的是一个rbtree(老版本是priority search tree,由于实现复杂被rbtree替代),该rbtree存储着共享该文件页的所有进程的VMA。所以文件页的反向映射流程如图8所示。

在这里插入图片描述

10.文件页反向映射关系图

由图8可知文件页的结构描述符struct page的mapping成员指向adress_space,struct address_space的i_mmap成员指向一个rbtree的树根,因为一个共享文件页会被映射到多个进程的VMA中,因此所有的这些VMA都会被插入到上述rbtree树中。最后只需将struct page的index成员和红黑树中每个节点存储的VMA数据相结合,os就能获取到所有映射了该文件页的进程pid和文件页在对应进程中的虚拟地址。到此文件页反向映射流程全部打通。

ps:

  1. 文件页类型:

    1. 物理内存页是有后备文件的,这种类型的页面和某个文件相关,例如进程的正文段和该进程的可执行文件相关。这种类型文件页不直接与文件进行读写交互而是通过文件缓存页(page cache)与文件进行读写交互
    2. 文件映射页,进程通过mmap函数直接对文件进行映射,再通过虚拟地址访问此种物理文件页时,文件页会直接与磁盘文件进行读写交互而不会利用page cahce,它也叫做文件映射页
    3. 文件缓存页(page cache)由内核分配的物理页,用户缓存对应磁盘文件。此种文件用户态进程不能直接映射访问。
  2. 如何定位某文件页首地址在对应文件的偏移量和在某一进程中映射的的虚拟地址?(计算流程如图9所示)

    1. 在文件中的偏移量为struct page->index

    2. 某一进程中映射的的虚拟地址记为viraddress,通过逆向映射计算出该文件页在该进程中对应的虚拟地址段结构描述符记为VMA。则

      viraddress = VMA->vm_start + page->index - VMA->vm_pgoff
      

    在这里插入图片描述

    11.文件页对应进程虚拟地址和文件偏移量计算示意图

3 KSM页面反向映射

3.1 什么是KSM页面

KSM机制是linux内核将内容相同的匿名页面进行合并,将映射到这个页面的页表项标记为只读,然后释放原来的页表,来达到节省大量内存的目的。KSM机制中合并的只读页面被称为KSM页,根据KSM页的特性可知该页属于匿名页(KSM页是匿名页的子集,在linux内核中KSM页的页描述符struct page中的mapping成员第0位和第1位都被置位)。linux中KSM实现详细介绍可参考https://www.cnblogs.com/arnoldlu/p/8335541.html#scan_get_next_rmap_item或https://zhuanlan.zhihu.com/p/102469328。

3.2 KSM页面反向映射

在KSM机制中会维护两颗红黑树,分别为stable tree和unstable tree.其中stable tree中的每个节点stable_node是用来维护的KSM机制中的KSM页(简称kpage).对于每个kpage都有一个rmap_item结构体来描述它的反向映射,其中rmap_item结构中的anon_vma成员是一个struct anon_vma结构体.拿到kpage对应的ano_vma数据,我们就可以向匿名页反向映射原理一样,从anon_vma的rb_root成员指向的红黑树中找到映射该kpage物理页所有的vma,即找到所有映射该kpage页的进程和进程中对应的虚拟地址。

struct rmap_item {
    struct rmap_item *rmap_list
    union {
        //当rmap_item加入stable树时,指向VMA的anon_vma数据结构。
        struct anon_vma *anon_vma;    /* when stable */
#ifdef CONFIG_NUMA
        int nid;        /* when node of unstable tree */
#endif
    };
    //进程的struct mm_struct数据结构
    struct mm_struct *mm;
    unsigned long address;        /* + low bits used for flags below */
    unsigned int oldchecksum;    /* when unstable */
    union {
        struct rb_node node;    /* when node of unstable tree */
        struct {        /* when listed from stable tree */
           	struct stable_node *head;
            struct hlist_node hlist;
        };
    };
};

ps:一个kpage会存储在一个stable tree的。该树的每个节点由struct stable_node结构体来描述

struct stable_node {
	union {
		struct rb_node node;	/* when node of stable tree */
		struct {		/* when listed for migration */
			struct list_head *head;
			struct list_head list;
		};
	};
    /*hlist是一个双向链表,用于存储所有与kpage相关联的rmap_item数据。因为kpage可能是多个用户或多个进程共享的物理
     *页面,因此在反向映射过程中kpage在不同的进程的VMA中虚拟地址偏移是不一样的。kpage对每个不同进程的反向映射都会
     *维护一个特定的rmap_item结构。而kpage在指定进程中的vma的虚拟地址偏移量是该指定进程对应的
     *rmap_item->address(在非KSM匿名页中此偏移量是page->index).
     */
	struct hlist_head hlist;
	unsigned long kpfn;
#ifdef CONFIG_NUMA
	int nid;
#endif
};

linux Rmap Walk机制

在linux内核中如果一个page被多个虚拟地址(虚拟页)映射,我们可以通过Rmap Walk机制对该page的所有vma进行遍历。该机制通过struct rmap_walk_control来进行控制,需要

//mm/rmap.h
/*
 * rmap_walk_control: To control rmap traversing for specific needs
 *
 * arg: passed to rmap_one() and invalid_vma()
 * rmap_one: executed on each vma where page is mapped
 * done: for checking traversing termination condition
 * anon_lock: for getting anon_lock by optimized way rather than default
 * invalid_vma: for skipping uninterested vma
 */
struct rmap_walk_control {
	void *arg;
	int (*rmap_one)(struct page *page, struct vm_area_struct *vma,
					unsigned long addr, void *arg);
	int (*done)(struct page *page);
	struct anon_vma *(*anon_lock)(struct page *page);
	bool (*invalid_vma)(struct vm_area_struct *vma, void *arg);
};

Rmap Walk机制实现函数

//mm/rmap.c
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, false);
	else
		return rmap_walk_file(page, rwc, false);
}

Rmap Walk机制实现流程图(匿名页):

[内核内存] 反向映射详解_第10张图片

12.rmap_walk机制

知识来源:

https://www.dazhuanlan.com/2019/11/14/5dcd1e7420eb8/

https://www.cnblogs.com/LoyenWang/p/12164683.html

https://www.sohu.com/a/294105390_467784

https://www.cnblogs.com/linhaostudy/p/10350326.html

你可能感兴趣的:(linux内存,linux内核内存,反向映射,逆向映射,写时复制,ksm)