Linux 匿名页的反向映射

时间:2022-09-01 00:15:27

我们知道LINUX的内存管理系统中有”反向映射“这一说,目的是为了快速去查找出一个特定的物理页在哪些进程中被映射到了什么地址,这样如果我们想把这一页换出(SWAP),或是迁移(Migrate)的时候,就能相应该更改所有相关进程的页表来达到这个目的。

1、为什么要使用反向映射

  物理内存的分页机制,一个PTE(Page Table Entry)对应一个物理页,但一个物理页可以由多个PTE与之相对应,当该页要被回收时,Linux2.4的做法是遍历每个进程的所有PTE判断该PTE是否与该页建立了映射,如果建立则取消该映射,最后无PTE与该相关联后才回收该页。该方法显而易见效率极低,因为其为了查找某个页的关联PTE遍历了所有的PTE,我们不禁想:如果把每个页关联的PTE保存在页结构里面,每次只需要访问那些与之相关联的PTE不很方便吗?确实,2.4之后确实采用过此方法,为每个页结构(Page)维护一个链表,这样确实节省了时间,但此链表所占用的空间及维护此链表的代价很大,在2.6中弃之不用,但反向映射机制的思想不过如此,所以还是有参考价值的,可以参考:http://blog.csdn.net/dog250/article/details/5303581

2、Linux2.6中是如何实现反向映射

2.1 与RM(Reverse Mapping)相关的结构

page, address_space, vm_area_struct, mm_struct, anon_vma.

以下均显示部分成员:

struct page{      struct address_space *mapping;  /* address_space类型,为对齐需要,其值为4的位数,所以最低两位无用,为充分利用资源,所以此处利用此最低位。                                         * 最低位为1表示该页为匿名页,并且它指向anon_vma对象。                                         * 最低为0表映射页,此时mapping指向文件节点地址空间。                                         */    atomic_t _mapcount;      /* 取值-1时表示没有指向该页框的引用,                             取值0时表示该页框不可共享                             取值大于0时表示该页框可共享表示有几个PTE引用                          */  pgoff_t index;};
struct mm_struct {
pgd_t * pgd;
}
struct vm_area_struct {
struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
}
struct anon_vma {
spinlock_t lock; /* Serialize access to vma list */
struct list_head head; /* List of private "related" vmas */
};

2.2 进程地址空间

Linux 匿名页的反向映射

  1. 每个进程有个进程描述符task_struct,其中有mm域指向该进程的内存描述符mm_struct。

  2. 每个进程都拥有一个内存描述符,其中有PGD域,指向该进程地址空间的全局页目录;mmap域指向第一个内存区域描述符vm_area_strut1。

  3. 进程通过内存区域描述符vm_area_struct管理内存区域,每个内存区域描述符都有vm_start和vm_end域指向该内存区域的在虚拟内存中的起始位置;vm_mm域指向该进程的内存描述符;每个vm_area_struct都有一个anon_vma域指向该进程的anon_vma;

  4. 每个进程都有一个anon_vma,是用于链接所有vm_area_struct的头结点,通过vm_area_struct的anon_vma_node构成双循环链表。

最终形成了上图。

现在假设我们要回收一个页,我们要做的是访问所有与该页相关联的PTE并修改之取消二者之间的关联。与之相关联的函数为:try_to_unmap。

2.3 try_to_unmap

2.3.1 try_to_unmap函数及PageOn宏 分析

int try_to_unmap(struct page *page)
{
int ret; BUG_ON(PageReserved(page));
BUG_ON(!PageLocked(page)); /*判断是不是匿名页,若是的话执行try_to_unmap_anon函数,否则的话执行try_to_unmap_file函数*/
if (PageAnon(page)) // PageAnon函数分析在下面
ret = try_to_unmap_anon(page);
else
ret = try_to_unmap_file(page); if (!page_mapped(page))
ret = SWAP_SUCCESS;
return ret;
} static inline int PageAnon(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
/* #define PAGE_MAPPING_ANON 1 此函数非常easy,就是判断page的mapping成员的末位是不是1,是的话返回1,不是的话返回0*/ }

2.3.2 try_to_unmap_anon函数及page_lock_anon_vma函数及list_for_each_entry宏 分析

还没开始看文件系统一节,所以try_to_unmap_file没看懂,所以此处只分析 try_to_unmap_anon函数,等看完vfs后再来补充吧。

static int try_to_unmap_anon(struct page *page)
{
struct anon_vma *anon_vma;
struct vm_area_struct *vma;
int ret = SWAP_AGAIN; anon_vma = page_lock_anon_vma(page); /* 获取该匿名页的anon_vma结构
* page_lock_anon_vma函数分析在下面。
*/
if (!anon_vma)
return ret; list_for_each_entry(vma, &anon_vma->head, anon_vma_node) { /* 循环遍历
* list_for_each_entry分析在下面
* anon_vma就是上图中anon_vma的指针,anon_vma->head得到其head成员(是list_head)类型,
* 其next值便对应上图中vm_area_struct1中的anon_vma_node中的head。
* vma 是vm_area_struct类型的指针,anon_vma_node为typeof(*vma)即vm_area_struct中的成员。
* 到此便可以开始双链表循环
*/
ret = try_to_unmap_one(page, vma); // 不管是调用try_to_unmap_anon还是try_to_unmap_file最终还是回到try_to_unmap_one上
if (ret == SWAP_FAIL || !page_mapped(page))
break;
}
spin_unlock(&anon_vma->lock);
return ret;
} static struct anon_vma *page_lock_anon_vma(struct page *page)
{
struct anon_vma *anon_vma = NULL;
unsigned long anon_mapping; rcu_read_lock();
anon_mapping = (unsigned long) page->mapping;
if (!(anon_mapping & PAGE_MAPPING_ANON))
goto out;
if (!page_mapped(page))
goto out;
// 前面已经提到,mapping最低位为1时表匿名页,此时mapping是指向anon_vma指针,故此处-1后强制转化为struct anon_vma指针类型,并返回该值。
anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
spin_lock(&anon_vma->lock);
out:
rcu_read_unlock();
return anon_vma;
} /* 参数含义:
* head是list_head指针,无非此处需要的第一个list_head是head->next
* pos是个指向包含list_head的结构体的指针,可以用typeof(*pos)解引用来得到此结构体
* member 是list_head在typeof(*pos)中的名称
* 这样pos = list_entry((head)->next, typeof(*pos), member)第一次便初始化pos为指向包含head->next指向的那个结构体的指针。
* 之后便是双循环链表遍历了
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \ // list_entry分析在下面
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member)) /* list_entry函数其实非常简单,其各参数的意义:
* ptr 指向list_head类型的指针
* type 包含list_head类型的结构体
* member list_head在type中的名称
* 返回值:包含ptr指向的list_head的类型为type的指针,即由list_head指针得到包含此list_head结构体的指针,实现也很简单,ptr减去member在type中的偏移即可
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

2.3.3 try_to_unmap_one函数及vma_address函数及pdg_offset宏 分析

Linux 匿名页的反向映射

Linux采用三级页表:

PGD:*页表,由pgd_t项组成的数组,其中第一项指向一个二级页表。

PMD:二级页表,由pmd_t项组成的数组,其中第一项指向一个三级页表(两级处理器没有物理的PMD)。

PTE:是一个页对齐的数组,第一项称为一个页表项,由pte_t类型表示。一个pte_t包含了数据页的物理地址。

static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long address;
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
pte_t pteval;
int ret = SWAP_AGAIN; if (!mm->rss)
goto out;
address = vma_address(page, vma); /* 通过页和vma得到线性地址
* vm_address函数解析在下面
*/
if (address == -EFAULT)
goto out; /*
* We need the page_table_lock to protect us from page faults,
* munmap, fork, etc...
*/
spin_lock(&mm->page_table_lock); // 页表锁 pgd = pgd_offset(mm, address); /* 获得pdg
* pdg_offset通过内存描述符和线性地址得到pgd
* 该函数解析在下面
*/
if (!pgd_present(*pgd))
goto out_unlock; pud = pud_offset(pgd, address); /* 获得pud
i386上应该是0吧?
*/
if (!pud_present(*pud))
goto out_unlock; pmd = pmd_offset(pud, address); /* 获得pmd */
if (!pmd_present(*pmd))
goto out_unlock; pte = pte_offset_map(pmd, address); /* 获得pte */
if (!pte_present(*pte))
goto out_unmap; /* 有了pgd pmd pte 后我们便达到我们目的了 ===> 查找与页相关联系的页表项,找到后便可以进行修改了(如果是要换出该页的话则应该解除映射pte_unmap()函数)
* 但修改之前还要做些判断和处理
*/ //
if (page_to_pfn(page) != pte_pfn(*pte))
goto out_unmap; /*
* If the page is mlock()d, we cannot swap it out.
* If it's recently referenced (perhaps page_referenced
* skipped over this mm) then we should reactivate it.
*/
if ((vma->vm_flags & (VM_LOCKED|VM_RESERVED)) ||
ptep_clear_flush_young(vma, address, pte)) {
ret = SWAP_FAIL;
goto out_unmap;
} /*
* Don't pull an anonymous page out from under get_user_pages.
* GUP carefully breaks COW and raises page count (while holding
* page_table_lock, as we have here) to make sure that the page
* cannot be freed. If we unmap that page here, a user write
* access to the virtual address will bring back the page, but
* its raised count will (ironically) be taken to mean it's not
* an exclusive swap page, do_wp_page will replace it by a copy
* page, and the user never get to see the data GUP was holding
* the original page for.
*
* This test is also useful for when swapoff (unuse_process) has
* to drop page lock: its reference to the page stops existing
* ptes from being unmapped, so swapoff can make progress.
*/
if (PageSwapCache(page) &&
page_count(page) != page_mapcount(page) + 2) {
ret = SWAP_FAIL;
goto out_unmap;
} /* Nuke the page table entry. */
flush_cache_page(vma, address);
pteval = ptep_clear_flush(vma, address, pte); /* Move the dirty bit to the physical page now the pte is gone. */
if (pte_dirty(pteval))
set_page_dirty(page); if (PageAnon(page)) {
swp_entry_t entry = { .val = page->private };
/*
* Store the swap location in the pte.
* See handle_pte_fault() ...
*/
BUG_ON(!PageSwapCache(page));
swap_duplicate(entry);
if (list_empty(&mm->mmlist)) {
spin_lock(&mmlist_lock);
list_add(&mm->mmlist, &init_mm.mmlist);
spin_unlock(&mmlist_lock);
}
set_pte(pte, swp_entry_to_pte(entry));
BUG_ON(pte_file(*pte));
mm->anon_rss--;
} mm->rss--;
acct_update_integrals();
page_remove_rmap(page);
page_cache_release(page); out_unmap:
pte_unmap(pte);
out_unlock:
spin_unlock(&mm->page_table_lock);
out:
return ret;
} static inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT); /* PAGE_CACHE_SHIFT - PAGE_SHIFT值为0,其实就是把page->index赋给pgoff
* 至于为什么要这样右移一下,我也不清楚
*/
unsigned long address;
address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT); /* page->index是页的偏移
* vma->vm_pgoff是内存区域首地址的偏移,都是以页为单位
* 相减后再<<PAGE_SHIFT便得到页在内存区域的中的偏移
* 再+vma->vma_start便得到页在内存区域中的地址
*/
if (unlikely(address < vma->vm_start || address >= vma->vm_end)) { /* 得到的地址应该在vm->vm_start和vm_end之间,否则报错 */
/* page should be within any vma from prio_tree_next */
BUG_ON(!PageAnon(page));
return -EFAULT;
}
return address;
} #define PGDIR_SHIFT 22 // 在i386机子上线性地址0-11位表PTE,12-21表PMD,22-31位表PGD,即线性地址右移22位的结果为其在全局页目录的偏移
#define PTRS_PER_PGD 1024 // 因PGD共10位,所以其最多可以有2^10=1024个全局描述符项 #define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1)) // 得到线性地址address在全局页目录里面的偏移
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address)) // 再加上全局描述符基地址(存储在内存描述符mm_struct中的pdg域)后便得到其在全局描述符中的具体位置

Linux 匿名页的反向映射的更多相关文章

  1. Linux内存管理 &lpar;12&rpar;反向映射RMAP

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

  2. linux内存源码分析 - 内存回收&lpar;匿名页反向映射&rpar;

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 看完了内存压缩,最近在看内存回收这块的代码,发现内容有些多,需要分几块去详细说明,首先先说说匿名页的反向映 ...

  3. Linux mem 2&period;6 Rmap 内存反向映射机制

    文章目录 1. 简介 2. 匿名内存 Rmap 的建立 2.1 fork() 2.2 do_page_fault() 3. 文件内存 Rmap 的建立 3.1 fork() 3.2 do_page_f ...

  4. &lbrack;转帖&rsqb; 学习 Linux 大页的内存知识

    一.在解释什么情况下需要开启大页和为啥需要开启大页前先了解下Linux下页的相关的知识:以下的内容是基于32位的系统,4K的内存页大小做出的计算1)目录表,用来存放页表的位置,共包含1024个目录en ...

  5. 为什么Linux默认页大小是4KB

    本文转载自为什么 Linux 默认页大小是 4KB 导语 我们都知道 Linux 会以页为单位管理内存,无论是将磁盘中的数据加载到内存中,还是将内存中的数据写回磁盘,操作系统都会以页面为单位进行操作, ...

  6. linux 巨页使用测试

    这里记录测试巨页的mmap使用,测试代码和<linux 巨页使用测试以及勘误1>类似. 跟踪脚本如下: probe kernel.function("hugetlb_reserv ...

  7. 【netcore基础】CentOS 7&period;6&period;1810 搭建&period;net core 2&period;1 linux 运行环境 nginx反向代理 supervisor配置自启动

    之前写过一篇Ubuntu的环境搭建博客,感觉一些配置大同小异,这里重点记录下 nginx 作为静态 angular 项目文件服务器的配置 参考链接 [netcore基础]ubuntu 16.04 搭建 ...

  8. 03&period;AutoMapper 之反向映射与逆向扁平化&lpar;Reverse Mapping and Unflattening&rpar;

    https://www.jianshu.com/p/d72400b337e0 AutoMapper现在支持更丰富的反向映射支持. 假设有以下实体: public class Order { publi ...

  9. 使用maven插件反向映射generatorConfig&period;xml生成代码

    一.配置Maven pom.xml 文件 <!-- 反向映射 --> <plugin> <groupId>org.mybatis.generator</gro ...

随机推荐

  1. C&num;中static关键字的作用

    静态分配有两种情况:1.用在类里的属性.方法前面,这样的静态属性与方法不需要创建实例就能访问,通过类名或对象名都能访问它,静态属性.方法只有"一份":即如果一个类新建有N个对象,这 ...

  2. linux下c程序调用reboot函数实现直接重启【转】

    转自:http://www.blog.chinaunix.net/uid-20564848-id-73878.html linux下c程序调用reboot函数实现直接重启 当然你也可以直接调用syst ...

  3. poj1068解题报告(模拟类)

    POJ 1068,题目链接http://poj.org/problem?id=1068 题意: 对于给出给出的原括号串S,对应两种数字密码串P.W: S         (((()()()))) P- ...

  4. form表单样式

    <BODY> <div id="modify-data"> <form class="modify-data-form"> ...

  5. Redis面试点

      redis的数据结构有那些 字符串 String 字典:Hash 列表:List 集合:set 有序集合:sortedSet 如果大量的key设置在同一时间过期,一般需要注意什么 大量的key过期 ...

  6. phpStorm字体大小无法调整&comma; 怎么办?

    最近上手了一款轻量级IDE phpStorm,可是就在调整编辑器字体大小时却遇到问题了, 发现字体大小无法调整,另外还有字体大小往左还有个“√”,始终无法去掉,这个勾限制了字体系列,就可怜巴巴的那几个 ...

  7. linux中创建和解压文档的 tar 命令教程

    linux & zip & tar https://www.cnblogs.com/xgqfrms/p/9714161.html 1 linux中的tar命令 tar(磁带归档)命令是 ...

  8. MPSVPX 配置

    MPSVPX 配置 设置主机名,IP地址,掩码,网关,DNS服务器,时区(使用WebGUI界面设置). bash-2.05b# cat svm.conf arp -d -a route flush i ...

  9. mini2440移植uboot 2014&period;04&lpar;一)

    最新版的uboot添加了很多新功能,我决定在最新版代码基础上重新移植一遍加深理解. 我修改的代码已经上传到github上,地址:https://github.com/qiaoyuguo/u-boot- ...

  10. Codeforces Round &num;423 &lpar;Div&period; 2&comma; rated&comma; based on VK Cup Finals&rpar; E DNA Evolution

    DNA Evolution 题目让我们联想到树状数组或者线段树,但是如果像普通那样子统计一段的和,空间会爆炸. 所以我们想怎样可以表示一段区间的字符串. 学习一发大佬的解法. 开一个C[10][10] ...