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

    文章

    10970

    浏览量

    206666
  • 代码
    +关注

    关注

    30

    文章

    4551

    浏览量

    66626

原文标题:3.总结

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

收藏 人收藏

    评论

    相关推荐

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

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

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

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

    linux文件访问权限怎么设置

    Linux 文件访问权限是操作系统中一个非常重要的概念。正确地设置文件访问权限可以保护系统的安全性,防止未经授权的人员对文件进行修改、删除或执行。本文将详细介绍 Linux 文件
    的头像 发表于 11-23 10:20 670次阅读

    Linux命令之lsof应用

    linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。
    发表于 08-31 10:20 151次阅读

    Linux内核锁的那点事

    Linux设备驱动中,我们必须要解决的一个问题是:多个进程对共享资源的并发访问,并发的访问会导致竞态。
    发表于 07-04 11:14 164次阅读
    <b class='flag-5'>Linux</b>内核锁的那点事

    有什么方法可以从ESP8266访问Linux共享?

    有什么方法可以从我的 ESP8266 访问 Linux 共享? 我想根据一两个文件的状态点亮一些 LED。
    发表于 06-02 08:37

    一款可以通过Web访问Linux终端神器

    rtty 由客户端和服务端组成。客户端采用纯C实现,服务端采用 GO 语言实现,前端界面采用 vue 实现。使用 rtty 可以在任何地方通过 Web 访问您的设备的终端,通过 设备ID 来区分您的不同的设备。rtty 非常适合远程维护 Linux 设备。
    的头像 发表于 06-01 17:02 616次阅读

    linux文件挂载相关介绍

    mount是Linux下的一个命令,它可以将分区挂载或挂接到Linux的一个文件夹下,从而将分区和该目录联系起来,当我们只要访问这个文件夹时,就相当于访问该分区了。
    的头像 发表于 05-12 15:23 414次阅读

    匿名上位机基于串口的简单使用

    本文简介:本文主要介绍匿名上位机V7版本的一些基本的功能使用,以及基于匿名上位机协议写下位机代码。使用的下位机为STM32F103C8T6
    发表于 05-10 10:49 3次下载
    <b class='flag-5'>匿名</b>上位机基于串口的简单使用

    ANO匿名上位机V7协议&STM32

    ANO匿名上位机V7协议&STM32 说明:以下程序为自己编写,若有误欢迎各位指出。 基于ANO匿名V7上位机的通信协议编写的代码文章目录ANO匿名上位机V7协议&STM32 前言 一、Ano V7
    发表于 05-09 11:08 9次下载
    ANO<b class='flag-5'>匿名</b>上位机V7协议&STM32

    匿名上位机(V7)基于串口的简单使用

    本文主要介绍匿名上位机V7版本的一些基本的功能使用,以及基于匿名上位机协议写下位机代码。使用的下位机为STM32F103C8T6
    发表于 05-08 11:06 5次下载
    <b class='flag-5'>匿名</b>上位机(V7)基于串口的简单使用

    匿名四轴上位机使用

    关于匿名上位机好久没有使用了,之前也发过一篇,里面涉及到了一点匿名上位机的使用,最近又重操旧业,再熟悉一下。 我用的是V4.22版本,软件中有通信协议的介绍,写程序的时候就要严格按照上位机的通讯协议来写 1,我这里用的单片机是stm32f1系列,这里介绍一下数据格式及其
    发表于 05-08 10:00 0次下载
    <b class='flag-5'>匿名</b>四轴上位机使用

    如何在ls1046ardblinux访问u-boot环境变量?

    我正在 ls1046ardb 板上工作,我想从 linux 访问 u-boot 环境,当我提供fw_printenv - 命令未找到时。 我已经检查了各自的配置文件,请帮助我在哪里我必须修改
    发表于 05-06 07:13

    可以通过Web访问Linux终端工具

    rtty 由客户端和服务端组成。客户端采用纯C实现,服务端采用 GO 语言实现,前端界面采用 vue 实现。使用 rtty 可以在任何地方通过 Web 访问您的设备的终端,通过 设备ID 来区分您的不同的设备。rtty 非常适合远程维护 Linux设备。
    的头像 发表于 05-05 11:20 480次阅读

    如何通过TZASC分配安全内存并通过OP-TEE的可信应用程序访问它?

    了 optee,然后运行了 optee_hello_world 示例,它似乎工作正常。 我现在想为安全区域分配一些内存并使用受信任的应用程序访问它。我检查了 SRM,但我需要更清楚地了解我在 linux 驱动程序的何处进行这些
    发表于 05-04 08:46