0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux中匿名页的访问分析

Linux阅码场 来源:Linux内核远航者 作者:Linux内核远航者 2021-10-12 17:52 次阅读

Linux有后备文件支持的页称为文件页,如属于进程的代码段、数据段的页,内存回收的时候这些页面只需要做脏页的同步即可(干净的页面可以直接丢弃掉)。反之为匿名页,如进程的堆栈使用的页,内存回收的时候这些页面不能简单的丢弃掉,需要交换到交换分区或交换文件。本文中,主要分析匿名页的访问将发生哪些可能颠覆我们认知的"化学反应"。

1.实例代码

首先以一个简单的示例代码来说明:

#include
#include
#include
#include
#include

#defineMAP_SIZE(100*1024*1024)

intmain(intargc,char*argv[])
{
char*p;
charval;
inti;

puts("beforemmapok,pleaceexec'free-m'!");
sleep(5);


//mmap

p=mmap(NULL,MAP_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
if(p==NULL){
perror("failtomalloc");
return-1;
}
puts("aftermmapok,pleaceexec'free-m'!");
sleep(5);


//read
for(i=0;i< MAP_SIZE; i++) {
  val = p[i];
 }
 puts("readok,pleaceexec'free-m'!");
sleep(5);

#if1
//write
memset(p,0x55,MAP_SIZE);
puts("writeok,pleaceexec'free-m'!");
#endif
//sleep
pause();

return0;
}

代码非常简单:首先通过mmap分配100M的私有可读可写匿名页面,然后进行读写访问,分别在提示的时候在另外一个窗口执行free -m命令查看输出结果。

程序执行结果如下:

$./anon_rw_demo
beforemmapok,pleaceexec'free-m'!
aftermmapok,pleaceexec'free-m'!
readok,pleaceexec'free-m'!
writeok,pleaceexec'free-m'!


命令执行结果如下:

$free-m
总计已用空闲共享缓冲/缓存可用
内存: 15729  8286  1945  895  5497  6220
交换: 16290 1599  14691
$free-m
总计已用空闲共享缓冲/缓存可用
内存: 15729  8286  1945  895  5497  6220
交换: 16290 1599  14691
$free-m
总计已用空闲共享缓冲/缓存可用
内存: 15729  8286  1945  895  5497  6220
交换: 16290 1599  14691
$free-m
总计已用空闲共享缓冲/缓存可用
内存: 15729  8383  1848  895  5497  6123
交换: 16290 1599  14691

可以看到:

第一次提示执行free命令的时候,我们还没有开始通过mmap分配内存,此时free命令输出作为参考。

第二次提示执行free命令的时候,我们已经通过mmap分配了100M的内存,此时发现free命令输出内存消耗基本没有变化。

第三次提示执行free命令的时候,我们对于分配的匿名页面进行了读操作,此时发现free命令输出内存消耗页基本没有变化,这基本上会颠覆我们的认知

第四次提示执行free命令的时候,我们对于分配的匿名页面进行了写操作,此时发现free命令输出内存消耗大概为100M。

2.内核原理

下面我们从Linux内核的层面来解析发生以上神奇现象的原理。

2.1 mmap的内存消耗

mmap申请匿名页的时候,只是申请了虚拟内存(通过vm_area_struct结构来描述,如描述虚拟内存区域的地址范围、访问权限等,以下简称vma),实际的物理内存并没有申请(除了用于管理虚拟内存区域的vma等结构内存的申请),当前虚拟内存和物理内存并没有建立页表映射关系,而真正的申请的匿名页所对应的物理页在实际访问的时候按需分配获得,所以此时我们看不到内存的消耗情况。

2.2 第一次读匿名页的内存消耗

通过mmap申请完虚拟内存之后,进程就可以按照之前申请vma的访问权限进行访问,第一发生读访问,这个时候由于虚拟内存和物理内存并没有建立页表映射关系,通过虚拟地址并不能查找到物理内存,所以会发生处理器的异常,最终分析是因为数据访问异常导致,就由处理器架构相关的代码进入了我们通用的缺页异常处理例程中。

缺页异常调用链如下:

"mm/memory.c"

处理器架构相关异常处理代码
->handle_mm_fault
->__handle_mm_fault
->handle_pte_fault
->if(!vmf->pte){-------------------1
if(vma_is_anonymous(vmf->vma))-------------------2
returndo_anonymous_page(vmf);-------------------3

缺页异常进入handle_pte_fault后,在1标签代码处,来判断访问的虚拟内存页的页表项是否为空,为空说明这个这个虚拟页没有和物理页建立映射关系。然后在2标签代码处判断是否为匿名页缺页异常(实际上是判断是否为私有的匿名页,当前当前示例代码场景申请的为私有匿名页面)。在3标签代码处,进行真正的私有匿名页缺页异常处理。

下面主要看下第一次读匿名页的处理:

do_anonymous_page
->pte_alloc(vma->vm_mm,vmf->pmd)-------------------1
->/*Usethezero-pageforreads*/
if(!(vmf->flags&FAULT_FLAG_WRITE)&&-------------------2
!mm_forbids_zeropage(vma->vm_mm)){-------------------3
entry=pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),
vma->vm_page_prot));-------------------4
vmf->pte=pte_offset_map_lock(vma->vm_mm,vmf->pmd,
vmf->address,&vmf->ptl);-------------------5

...
gotosetpte;
}
->page=alloc_zeroed_user_highpage_movable(vma,vmf->address);-------------------6
->entry=mk_pte(page,vma->vm_page_prot);-------------------7
entry=pte_sw_mkyoung(entry);-------------------8
if(vma->vm_flags&VM_WRITE)
entry=pte_mkwrite(pte_mkdirty(entry));-------------------9

vmf->pte=pte_offset_map_lock(vma->vm_mm,vmf->pmd,vmf->address,
&vmf->ptl);-------------------10

->set_pte_at(vma->vm_mm,vmf->address,vmf->pte,entry);-------------------11

1标签处:判断虚拟地址对应的pmd表项是否为空,为空来分配直接页表设置到pmd表项中。

2标签处:判断是否是进行读访问。

3标签处:判断是否没有禁止0页。

4标签处:就是对于没有禁止0页的匿名页读访问设置页表,这里通过0页的页帧号和mmap映射时指定的访问权限组合页表项的值。

5标签处:通过发生缺页的虚拟地址来计算出页表项的地址保存在 vmf->pte。

最11标签处:将4标签初组合出的页表项的值写入到5标签初计算出的页表项中。

以上分析可知:对于私有的匿名页,第一次读访问的时候都会发生缺页异常,然后通过页表映射0页,这个0页没有什么特殊之处,只不过它是在系统启动过程中初始化好的一块内容全为0的页面,这样做可以为进程分配了内存只进行读访问节省大量物理内存。

2.3 第一次写匿名页的内存消耗

大家可以将示例代码中,读访问屏蔽掉只进行写访问,观察内存消耗。

这个时候发生缺页异常时,不会在走2 3 4 5 便签处代码,而在6处分配了一个物理页面,在7 8 9组合页表项的值, 10处计算出页表项的地址,最后把组合的值设置到页表项中。

需要注意第9处,如果是写访问会设置页表项的可写标志位

以上分析可知:对于私有的匿名页,第一次写访问的时候都会发生缺页异常,会真正分配一个物理页面,然后将虚拟页面通过页面映射到物理页面,所以我们能观察到写之后发生了大量内存消耗。

2.4 第一次读然后写匿名页的内存消耗

这种场景就是示例代码中所做的实验,可以看到读的时候基本上没有内存消耗,写的时候发生了大量内存消耗。

关于第一次读,上面已经做过解释,下面主要看读完之后的页面发生写访问的情况。

2.4.1 从mmap说起

实际上,对于一个私有的内存映射,在mmap的时候为页表映射准备访问权限的时候并不是给予所有的权限,而是把可写属性去掉了。

我们可以从源代码找到答案:

"mm/mmap.c"

do_mmap
->mmap_region
->vma_set_page_prot(vma)
->vm_page_prot=vm_pgprot_modify(vma->vm_page_prot,vm_flags);---------1
->pgprot_modify(oldprot,vm_get_page_prot(vm_flags))
->WRITE_ONCE(vma->vm_page_prot,vm_page_prot);---------------2


/*descriptionofeffectsofmappingtypeandprotincurrentimplementation.
*thisisduetothelimitedx86pageprotectionhardware.Theexpected
*behaviorisinparens:
*
*map_typeprot
*PROT_NONEPROT_READPROT_WRITEPROT_EXEC
*MAP_SHAREDr:(no)nor:(yes)yesr:(no)yesr:(no)yes
*w:(no)now:(no)now:(yes)yesw:(no)no
*x:(no)nox:(no)yesx:(no)yesx:(yes)yes
*
*MAP_PRIVATEr:(no)nor:(yes)yesr:(no)yesr:(no)yes
*w:(no)now:(no)now:(copy)copyw:(no)no
*x:(no)nox:(no)yesx:(no)yesx:(yes)yes
*/
->vm_get_page_prot
pgprot_tprotection_map[16]__ro_after_init={
__P000,__P001,__P010,__P011,__P100,__P101,__P110,__P111,
__S000,__S001,__S010,__S011,__S100,__S101,__S110,__S111
};


对于__Pxxx, 最后一个x表示vma属性是否可读,倒数第二个x表示vma属性是否可写,P后面的x表示是否可执行。

1标签处根据mmap传递的访问权限来构造最终的访问权限标识。

2标签处将构造好的访问权限标识记录到vma->vm_page_prot中,供缺页异常设置页表使用。

注释中已经做了详细的解释,具体页表属性如何表示由各自的处理器架构相关代码来做(eg: 对于x86架构 #define __P111 PAGE_COPY_EXEC),我们只需要知道:无论我们想让vma具备那些属性组合,都会屏蔽掉写属性,具体可以查看相关的处理器架构实现。

所以,再次回到缺页异常处理代码中。在2.2小节的4标签处,使用mmap设置好的页表访问权限设置页表属性,当前场景我们知道,mmap中指定为私有的可读可写属性,而页表中只是设置为了只读属性

2.4.2 写时复制的触发

读访问将虚拟页以只读的方式映射到了0页,当再次发生写操作时,就会再次触数据访问异常,最终进入缺页异常处理例程中。

下面给出调用链:

"mm/memory.c"

handle_pte_fault
->if(vmf->flags&FAULT_FLAG_WRITE){-----------1
if(!pte_write(entry))-----------2
returndo_wp_page(vmf);-----------3

可以看到最终也是在handle_pte_fault中处理:在1标签处判断是否为写访问。在2标签处判断页表项的属性是否是只读。在3标签处进行实际的写时复制处理。

以上分析可知:发生写访问操作时,如果vma可写,但是页表属性标识不可写(只读),会发生写时复制缺页异常,对于当前场景的0页的写访问就是如此,在do_wp_page中会重新分配物理页面映射到虚拟页面,然后页表设置为可写属性,就完成了缺页处理。

3.总结

1)mmap分配私有匿名内存时,会设置vma的vm_page_prot成员,去除掉页表的写访问标识。

2)第一次读匿名页时,对于可读可写的vma,虚拟页会以只读的方式映射到0页。

3)第一次写匿名页时,对于可读可写的vma,会申请物理页面,虚拟页以可读可写的方式映射到此物理页。

4)第一次读匿名页后,然后写匿名页,先只读方式映射到0页,然后发生写时复制,分配物理页,虚拟页以可读可写的方式映射到此物理页。

可以发现,访问匿名页面时发生的“化学反应”并不是那么的简单,其中会涉及mmap的映射原则,0页的映射,匿名页面的处理,写时复制的处理等等,而且读写顺序不一样,产生的结果也会不一样,大家可以结合内核源代码进行分析,希望对大家理解匿名页缺页异常有所帮助。

责任编辑:haq


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11199

    浏览量

    208691
  • 代码
    +关注

    关注

    30

    文章

    4719

    浏览量

    68210

原文标题:3.总结

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux lsof命令的基本用法

    linux 系统,一切皆文件。通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以 lsof 命令不仅可以查看进程打开的文件、目录,还可以查看进程监听的端口等 sock
    的头像 发表于 10-23 11:52 153次阅读
    <b class='flag-5'>Linux</b> lsof命令的基本用法

    动态代理IP的匿名性和透明度,为主要考虑关键!

    动态代理IP的匿名性和透明度是用户选择代理服务时需要考虑的关键因素。根据用户的需求和场景,可以选择不同匿名级别和透明度的代理服务来平衡隐私保护和网络访问的需求。
    的头像 发表于 09-20 07:36 210次阅读
    动态代理IP的<b class='flag-5'>匿名</b>性和透明度,为主要考虑关键!

    Linux内核表映射的基础知识

    大家在看内核代码时会经常看的以上术语,但在ARM的芯片手册并没有用到这些术语,而是使用L1,L2,L3表这种术语。
    的头像 发表于 08-07 15:53 581次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>中</b><b class='flag-5'>页</b>表映射的基础知识

    loongarch是如何区分大和基本页的?

    在开发loongarch架构的操作系统的时候,我遇到了这样的问题:我不知道硬件是如何区分大和基本页的。 如图,关于基本页和大的格式在手册是这样的叙述的: 即便手册阐述了基本页和
    发表于 03-30 12:05

    Linux内核内存管理之内核非连续物理内存分配

    我们已经知道,最好将虚拟地址映射到连续帧,从而更好地利用缓存并实现更低的平均内存访问时间。然而,如果对内存区域的请求并不频繁,那么考虑基于通过连续线性地址访问非连续帧的分配方案是有
    的头像 发表于 02-23 09:44 836次阅读
    <b class='flag-5'>Linux</b>内核内存管理之内核非连续物理内存分配

    Linux内存管理之CPU本地帧缓存

    在前一节,我们学习了buddy伙伴关系系统,它适用于申请连续的大块物理内存;而有些时候,经常需要申请和释放单个帧。
    的头像 发表于 02-20 09:23 428次阅读

    鸿蒙ArkUI开发-实现增删Tab

    本文以浏览器增加或删除签为例,实现Tabs签的增删功能。
    的头像 发表于 01-29 18:43 1520次阅读
    鸿蒙ArkUI开发-实现增删Tab<b class='flag-5'>页</b>签

    如何解决C语言中的“访问权限冲突”异常?C语言引发异常原因分析

    如何解决C语言中的“访问权限冲突”异常?C语言引发异常原因分析  在C语言中,访问权限冲突异常通常是由于尝试访问未授权的变量、函数或其他数据结构而引起的。这种异常是编程中常见的错误之一
    的头像 发表于 01-12 16:03 4652次阅读

    UltraEdit在Mac/Linux上的使用技巧分享

    通过 Nautilus 的 FTP 您知道您可以使用各种服务器连接协议在 UltraEdit for Linux 访问远程 FTP 文件吗?
    的头像 发表于 11-29 09:21 716次阅读

    MMU多级表映射过程

    空间,也有相应的表负责虚拟地址到物理地址之间的转换。MMU查询的过程,用户进程的一级表的基址存放在TTBR0。操作系统的内核空间公用一块地址空间,MMU查询的过程,内核空间的一
    的头像 发表于 11-26 16:28 901次阅读
    MMU多级<b class='flag-5'>页</b>表映射过程

    MMU命中、缺页介绍

    命中、缺页 (1)命中 • a) 处理器要对虚拟地址VA进行访问。 • b) MMU的TLB没有命中,通过TWU遍历主存的PTEA
    的头像 发表于 11-26 16:19 993次阅读
    MMU<b class='flag-5'>中</b>的<b class='flag-5'>页</b>命中、缺页介绍

    从内存读取translation tables的逻辑介绍

    TWU table walk unit:包含从内存读取translation tables的逻辑 一个完整的表翻译和查找的过程叫作表查询(Translation Table Walk),
    的头像 发表于 11-26 16:04 521次阅读

    linux文件访问权限怎么设置

    、权限的类型、权限的表示方法以及如何使用命令来设置文件访问权限。 一、Linux 文件访问权限的背景知识 在 Linux ,每个文件和目录
    的头像 发表于 11-23 10:20 1409次阅读

    linux系统查看物理地址

    Linux系统访问和查看物理地址是一个非常重要的任务,因为它提供了对硬件设备的直接访问。本文将详细介绍如何在Linux系统
    的头像 发表于 11-16 16:47 3272次阅读

    MMU原理:CPU是如何访问到内存的?

    当CPU访问虚拟地址0的时候,MMU会去查上面表的第0行,发现第0行没有命中,于是无论以何种形式(R读,W写,X执行)访问,MMU都会给CPU发出page fault,CPU自动跳到fault的代码去处理fault。
    发表于 11-09 12:30 1048次阅读
    MMU原理:CPU是如何<b class='flag-5'>访问</b>到内存的?