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

    文章

    11292

    浏览量

    209332
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68525

原文标题:3.总结

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

收藏 人收藏

    评论

    相关推荐

    Linux lsof命令的基本用法

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

    存储器访问速度最快的是什么

    在探讨存储器访问速度最快的是哪一种时,我们首先需要了解计算机存储系统的层次结构以及各类存储器的特性和功能。计算机存储系统通常包括多个层次的存储器,从速度最快、容量最小的寄存器开始,到速度较慢、容量较大的外存储器结束。这些存储器在访问
    的头像 发表于 10-12 17:01 1786次阅读

    匿名浏览到数据安全:代理IP用户心声全记录

    匿名浏览到数据安全,代理IP技术在现代网络环境扮演着重要角色。以下是代理IP用户心声的全记录,涵盖了其应用、优势、挑战及用户心声。
    的头像 发表于 09-30 08:59 192次阅读

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

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

    Linux内核表映射的基础知识

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

    详解Linux的权限控制

    本章将和大家分享Linux的权限控制。废话不多说,下面我们直接进入主题。
    的头像 发表于 08-05 15:32 565次阅读
    详解<b class='flag-5'>Linux</b><b class='flag-5'>中</b>的权限控制

    云服务器linux搭建ftp服务访问不了怎么解决?

    如果你在云服务器linux上搭建了FTP服务但无法访问,这可能是由于几种原因引起的。以下是一些常见问题和解决方法: 1、防火墙配置:确保防火墙允许FTP流量通过。FTP服务器通常使用端口21(控制
    的头像 发表于 04-18 17:23 1496次阅读

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

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

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

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

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

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

    鸿蒙ArkUI开发-实现增删Tab

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

    如何通过WebDAV服务器访问NAS

    WebDAV的客户端程序(如WinSCP、RaiDrive、Mac OS Finder、Linux 资源管理器)访问TNAS设备。 接下来为大家分享如何通过WebDAV服务器访问铁威马NAS: 1、使用拥有
    的头像 发表于 01-16 15:30 1041次阅读
    如何通过WebDAV服务器<b class='flag-5'>访问</b>NAS

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

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

    如何解决Linux系统的网络连接问题?

    如何解决Linux系统的网络连接问题? Linux系统的网络连接问题是常见的技术难题之一,通常涉及在Linux上配置网络接口、解决网络故
    的头像 发表于 01-12 15:17 980次阅读

    Linux常用命令自学手册》+一本手边linux速查字典

    占用的页数不多,2-3,有些就1,可以说精练到都是精华了,从内容上看,有语法格式,常用参数,参考示例,有助于快速学习,如果需要深入了解,则可以看看《linux命令速查手册》,它一般一个命令会有3
    发表于 12-26 08:46