所谓反向映射是相对于从虚拟地址到物理地址的映射,反向映射是从物理页面到虚拟地址空间VMA的反向映射。

RMAP能否实现的基础是通过struct anon_vma、struct anon_vma_chain和sturct vm_area_struct建立了联系,通过物理页面反向查找到VMA。

用户在使用虚拟内存过程中,PTE页表项中保留着虚拟内存页面映射到物理内存页面的记录。

一个物理页面可以同时被多个进程的虚拟地址内存映射,但一个虚拟页面同时只能有一个物理页面与之映射。

不同虚拟页面同时映射到同一物理页面是因为子进程克隆父进程VMA,和KSM机制的存在。

如果页面要被回收,就必须要找出哪些进程在使用这个页面,然后断开这些虚拟地址到物理页面的映射。

匿名页面实际的断开映射操作在rmap_walk_anon中进行的,可以看出从struct page、到struct anon_vma、到struct anon_vma_chain、到struct vm_area_struct的关系。

1. 父进程分配匿名页面

父进程为自己的进程地址空间VMA分配物理内存时,通常会产生匿名页面。

do_anonymous_page()会分配匿名页面;do_wp_page()发生写时复制COW时也会产生一个新的匿名页面。

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. */
if (unlikely(anon_vma_prepare(vma)))------------------------------为进程地址空间准备struct anon_vma数据结构和struct anon_vma_chain链表。
goto oom;
page = alloc_zeroed_user_highpage_movable(vma, address);----------从HIGHMEM区域分配一个zeroed页面
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);
...
}

RMAP反向映射系统中有两个重要的数据结构:一个是struct anon_vma,简称AV;一个是struct anon_vma_chain,简称AVC。

struct anon_vma {
struct anon_vma *root; /* Root of this anon_vma tree */----------------指向anon_vma数据机构中的根节点
struct rw_semaphore rwsem; /* W: modification, R: walking the list */------保护anon_vma中链表的读写信号量
/*
* 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;------------------------------------------------------------对anon_vma的引用计数 /*
* 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 */--------------------指向父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).
*/
struct rb_root rb_root; /* Interval tree of private "related" vmas */-----红黑树根节点
}

struct anon_vma_chain数据结构是链接父子进程的枢纽:

struct anon_vma_chain {
struct vm_area_struct *vma;-----------------------------------------------指向VMA
struct anon_vma *anon_vma;------------------------------------------------指向anon_vma数据结构,可以指向父进程或子进程的anon_vma数据结构。
struct list_head same_vma; /* locked by mmap_sem & page_table_lock */---链表节点,通常把anon_vma_chain添加到vma->anon_vma_chain链表中。
struct rb_node rb; /* locked by anon_vma->rwsem */-------------红黑树节点,通常把anon_vma_chain添加到anon_vma->rb_root的红黑树中。
unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
unsigned long cached_vma_start, cached_vma_last;
#endif
}

下面分析如何建立AV、AVC、VMA之间的关系:

int anon_vma_prepare(struct vm_area_struct *vma)
{
struct anon_vma *anon_vma = vma->anon_vma;--------------vma->anon_vma指向struct anon_vma数据结构。
struct anon_vma_chain *avc; might_sleep();
if (unlikely(!anon_vma)) {
struct mm_struct *mm = vma->vm_mm;
struct anon_vma *allocated; avc = anon_vma_chain_alloc(GFP_KERNEL);------------分配一个struct anon_vma_chain结构。
if (!avc)
goto out_enomem; anon_vma = find_mergeable_anon_vma(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)) {
vma->anon_vma = anon_vma;-------------------------建立struct vm_area_struct和struct anon_vma关联
anon_vma_chain_link(vma, avc, anon_vma);----------建立struct anon_vma_chain和其他结构体的关系。
/* 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;
}

至此已经建立struct vm_area_struct、struct anon_vma、struct anon_vma_chain三者之间的链接,并插入相应链表、红黑树中。

从AVC可以轻松找到VMA和AV;AV可以通过红黑树找到AVC,然后发现所有红黑树中的AV;VMA可以直接找到AV,也可以通过AVC链表找到AVC。

static void anon_vma_chain_link(struct vm_area_struct *vma,
struct anon_vma_chain *avc,
struct anon_vma *anon_vma)
{
avc->vma = vma;--------------------------------------------建立struct anon_vma_chain和struct vm_area_struct关联
avc->anon_vma = anon_vma;----------------------------------建立struct anon_vma_chain和struct anon_vma关联
list_add(&avc->same_vma, &vma->anon_vma_chain);------------将AVC添加到struct vm_area_struct->anon_vma_chain链表中。
anon_vma_interval_tree_insert(avc, &anon_vma->rb_root);----将AVC添加到struct anon_vma->rb_root红黑树中。
}

调用alloc_zeroed_user_highpage_movable分配物理内存之后,调用page_add_new_anon_rmap建立PTE映射关系。

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);
SetPageSwapBacked(page);----------------------------------------------------------设置PG_SwapBacked表示这个页面可以swap到磁盘。
atomic_set(&page->_mapcount, 0); /* increment count (starts at -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);--------------------------------------设置这个页面位匿名映射
} 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))---------------------------------------------------------------判断当前页面是否是匿名页面PAGE_MAPPING_ANON
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;
page->mapping = (struct address_space *) anon_vma;------------------------------mapping指定页面所在的地址空间,这里指向匿名页面的地址空间数据结构struct anon_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页表项:

static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
{
struct vm_area_struct *mpnt, *tmp, *prev, **pprev;
struct rb_node **rb_link, *rb_parent;
int retval;
unsigned long charge; uprobe_start_dup_mmap();
down_write(&oldmm->mmap_sem);
flush_cache_dup_mm(oldmm);
uprobe_dup_mmap(oldmm, mm);
/*
* Not linked in yet - no deadlock potential:
*/
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)
goto out;
retval = khugepaged_fork(mm, oldmm);
if (retval)
goto out; prev = NULL;
for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {-------------------for循环遍历父进程的进程地址空间VMA。
struct file *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) {
unsigned long len = vma_pages(mpnt); if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
goto fail_nomem;
charge = len;
}
tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
if (!tmp)
goto fail_nomem;
*tmp = *mpnt;--------------------------------------------------------复制父进程地址空间VMA到刚创建的子进程tmp中。
INIT_LIST_HEAD(&tmp->anon_vma_chain);
retval = vma_dup_policy(mpnt, tmp);
if (retval)
goto fail_nomem_policy;
tmp->vm_mm = mm;
if (anon_vma_fork(tmp, mpnt))----------------------------------------为子进程创建相应的anon_vma数据结构。
goto fail_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)
goto out;
}
...
}

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应用

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

kswapd内核线程回收页面需要断开所有映射了该匿名页面的用户PTE页表项。

页面迁移时,需要断开所有映射到匿名页面的用户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;
}

内核中有三种页面需要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);
}

下面以匿名页面的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;
}

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

Linux内存管理2.6 -反向映射RMAP(最终版本)的更多相关文章

  1. Linux内存管理 (2)页表的映射过程

    专题:Linux内存管理专题 关键词:swapper_pd_dir.ARM PGD/PTE.Linux PGD/PTE.pgd_offset_k. Linux下的页表映射分为两种,一是Linux自身的 ...

  2. Linux内存管理 (4)分配物理页面

    专题:Linux内存管理专题 关键词:分配掩码.伙伴系统.水位(watermark).空闲伙伴块合并. 我们知道Linux内存管理是以页为单位进行的,对内存的管理是通过伙伴系统进行. 从Linux内存 ...

  3. Linux内存管理 - slab分配器和kmalloc

    本文目的在于分析Linux内存管理机制的slab分配器.内核版本为2.6.31.1. SLAB分配器 内核需要经常分配内存,我们在内核中最常用的分配内存的方式就是kmalloc了.前面讲过的伙伴系统只 ...

  4. Linux内存管理 - buddy系统

    本文目的在于分析Linux内存管理机制中的伙伴系统.内核版本为2.6.31.1. 伙伴系统的概念 在系统运行过程中,经常需要分配一组连续的页,而频繁的申请和释放内存页会导致内存中散布着许多不连续的页, ...

  5. Linux内存管理 (12)反向映射RMAP

    专题:Linux内存管理专题 关键词:RMAP.VMA.AV.AVC. 所谓反向映射是相对于从虚拟地址到物理地址的映射,反向映射是从物理页面到虚拟地址空间VMA的反向映射. RMAP能否实现的基础是通 ...

  6. Linux内存管理专题

    Linux的内存管理涉及到的内容非常庞杂,而且与内核的方方面面耦合在一起,想要理解透彻非常困难. 在开始学习之前进行了一些准备工作<如何展开Linux Memory Management学习?& ...

  7. Linux内存管理 (7)VMA操作

    专题:Linux内存管理专题 关键词:VMA.vm_area_struct.查找/插入/合并VMA.红黑树. 用户进程可以拥有3GB大小的空间,远大于物理内存,那么这些用户进程的虚拟地址空间是如何管理 ...

  8. Linux内存管理 (10)缺页中断处理

    专题:Linux内存管理专题 关键词:数据异常.缺页中断.匿名页面.文件映射页面.写时复制页面.swap页面. malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有 ...

  9. Linux内存管理 (11)page引用计数

    专题:Linux内存管理专题 关键词:struct page._count._mapcount.PG_locked/PG_referenced/PG_active/PG_dirty等. Linux的内 ...

  10. Linux内存管理 (15)页面迁移

    专题:Linux内存管理专题 关键词:RMAP.页面迁移. 相关章节:反向映射RMAP.内存规整. 页面迁移的初衷是为NUMA系统提供一种将进程迁移到任意内存节点的能力,后来内存规整和内存热插拔场景都 ...

随机推荐

  1. cpu亲和性相关函数和宏 基础讲解[cpu_set_t]

    cpu亲和性相关函数和宏讲解: 写在前面: 我在查找关于linux cpu宏函数没看到有对宏函数基础的.详细的讲解,笔者便通过官方文档入手,对次进行的翻译和理解希望能帮到对这方面宏有疑惑的读者 exp ...

  2. 【Vue2】 Watch 监听器

    监听器案例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  3. 硬盘测速工具中的队列深度是个什么东西——CrystalDiskMark中的Q32T16是什么意思

    ================================ 最近有使用CrystalDiskMark给自己的硬盘做测速,发现有个名词自己不是很理解,就是像Q32T16这样的词: 在网上找了好久, ...

  4. C#自定义快捷操作键的实现 - 开源研究系列文章

    这次想到应用程序的快捷方式使用的问题. Windows已经提供了API函数能够对窗体的热键进行注册,然后就能够在窗体中使用这些注册的热键进行操作了.于是笔者就对这个操作进行了整理,将注册热键操作写成了 ...

  5. SeaTunnel毕业!首个国人主导的数据集成项目成为Apache顶级项目

    采访嘉宾 | 郭炜.高俊 编辑 | Tina 北京时间 2023 年 6 月 1 日,全球最大的开源软件基金会 Apache Software Foundation(以下简称 ASF)正式宣布 Apa ...

  6. 在IIS上部署ASP.NET Core Web API和Blazor Wasm详细教程

    前言 前段时间我们完成了七天.NET 8 操作 SQLite 入门到实战的开发系列教程,有不少同学留言问如何将项目发布部署到IIS上面运行.本篇文章我们就一起来讲讲在IIS上部署ASP.NET Cor ...

  7. 信创环境:鲲鹏ARM+麒麟V10离线部署K8s和Rainbond信创平台

    在上篇<国产化信创开源云原生平台>文章中,我们介绍了 Rainbond 作为可能是国内首个开源国产化信创平台,在支持国产化和信创方面的能力,并简要介绍了如何在国产化信创环境中在线部署 Ku ...

  8. Linux内核如何判断地址是否位于用户空间?

    一. 问题描述 access_ok函数是什么原理? 二.问题分析 我们在内核空间和用户空间进行数据拷贝的时候必须判断用户空间地址是否合法. 主要通过偶函数access_ok来判断. 1. Linux用 ...

  9. 关于为什么使用 ASCII GBK Unicode编码

    关于为什么使用 ASCII GBK Unicode编码 由来:大家都知道计算机最早是美国人为了更加便捷的存储和计算数据发明的,但是呢计算机底层都是硬件,只能存储像0101这样的二进制数据,那美国人为了 ...

  10. 解决gedit报错无法打开的问题

    彻底解决关于gedit的Unable to init server: 无法连接: 拒绝连接_BD_Marathon的博客-CSDN博客_unable to init server: 无法连接: 拒绝连 ...