news 2026/4/16 19:09:04

Linux 内存管理:匿名内存映射简析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内存管理:匿名内存映射简析

文章目录

  • 1. 前言
  • 2. 匿名内存映射的典型场景
    • 2.1 只读内存匿名映射过程
    • 2.2 只写内存匿名映射过程
    • 2.3 COW 匿名映射过程
      • 2.3.1 先读后写内存匿名映射过程
      • 2.3.2 父子进程写 COW 匿名映射过程

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 匿名内存映射的典型场景

本文以ARMv7 + Linux 4.14.x为上下文,分析匿名内存映射的典型场景下,代码实现的概要细节。

2.1 只读内存匿名映射过程

用户空间分配内存,而后进行读操作,映射为zero page

size_tsize=1*1024*1024;char*ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);charc=ptr[0];// 读操作触发 page faultmunmap(ptr,size);

语句char c = ptr[0];会触发硬件page fault,从而进入读操作匿名内存页的映射过程。ARMv7架构的处理流程如下:

do_DataAbort()...do_page_fault()__do_page_fault()handle_mm_fault()__handle_mm_fault()handle_pte_fault()
staticinthandle_pte_fault(structvm_fault*vmf){...if(unlikely(pmd_none(*vmf->pmd))){/* * Leave __pte_alloc() until later: because vm_ops->fault may * want to allocate huge page, and if we expose page table * for an instant, it will be difficult to retract from * concurrent faults and from rmap lookups. */vmf->pte=NULL;}else{...}if(!vmf->pte){/* PTE 页表 还未建立 */if(vma_is_anonymous(vmf->vma))returndo_anonymous_page(vmf);/* 匿名映射 */elsereturndo_fault(vmf);}...}staticintdo_anonymous_page(structvm_fault*vmf){.../* Use the zero-page for reads */if(!(vmf->flags&FAULT_FLAG_WRITE)&&!mm_forbids_zeropage(vma->vm_mm)){entry=pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),vma->vm_page_prot));...gotosetpte;}...setpte:set_pte_at(vma->vm_mm,vmf->address,vmf->pte,entry);...}

上面的代码中,my_zero_pfn()选择将读地址映射到zero page

从硬件 MMU 可以得知,当前的page fault是由读还是写引起的,从而去决定是否设置FAULT_FLAG_WRITE标志位。如ARMv7架构下,发生page fault时,从寄存器DFSR(Data Fault Status Register)获知是读还是写:

2.2 只写内存匿名映射过程

用户空间分配内存,而后进行写操作,新分配物理内存并映射:

size_tsize=1*1024*1024;char*ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);ptr[0]=0xAA;// 写操作触发 page faultmunmap(ptr,size);

语句ptr[0] = 0xAA;会触发硬件page fault,从而进入写操作匿名内存页的映射过程。ARMv7架构的处理流程如下:

do_DataAbort()...do_page_fault()staticint__kprobesdo_page_fault(unsignedlongaddr,unsignedintfsr,structpt_regs*regs){...unsignedintflags=FAULT_FLAG_ALLOW_RETRY|FAULT_FLAG_KILLABLE;.../* 从 DFSR 寄存器得知是写操作引发的 page fault,则设置 FAULT_FLAG_WRITE 标志位 */if(fsr&FSR_WRITE)flags|=FAULT_FLAG_WRITE;...fault=__do_page_fault(mm,addr,fsr,flags,tsk);...}__do_page_fault()handle_mm_fault()__handle_mm_fault()handle_pte_fault()do_anonymous_page()
staticintdo_anonymous_page(structvm_fault*vmf){...structpage*page;....../* 分配新内存页面 */page=alloc_zeroed_user_highpage_movable(vma,vmf->address);if(!page)gotooom;...__SetPageUptodate(page);entry=mk_pte(page,vma->vm_page_prot);/* 构建 pte */if(vma->vm_flags&VM_WRITE)entry=pte_mkwrite(pte_mkdirty(entry));/* 设置页面可写 */...setpte:set_pte_at(vma->vm_mm,vmf->address,vmf->pte,entry);/* 填充 PTE 页表项 */...}

2.3 COW 匿名映射过程

2.3.1 先读后写内存匿名映射过程

用户空间分配内存,先读而后进行写操作,将引发写时拷贝(COW)

size_tsize=1*1024*1024;char*ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);charc=ptr[0];// 读操作触发 page fault,过程见 2.1 只读内存匿名映射过程ptr[0]=0xAA;// 对只读内存进行写操作,触发 COWmunmap(ptr,size);

语句char c = ptr[0];触发page fault,将虚拟地址映射到只读的zero page,过程见2.1 只读内存匿名映射过程,这里不再赘述。语句ptr[0] = 0xAA;写只读内存,将触发写时拷贝(COW)

do_DataAbort()...do_page_fault()__do_page_fault()handle_mm_fault()__handle_mm_fault()handle_pte_fault()staticinthandle_pte_fault(structvm_fault*vmf){pte_tentry;if(unlikely(pmd_none(*vmf->pmd))){...}else{...vmf->pte=pte_offset_map(vmf->pmd,vmf->address);vmf->orig_pte=*vmf->pte;...}...vmf->ptl=pte_lockptr(vmf->vma->vm_mm,vmf->pmd);spin_lock(vmf->ptl);entry=vmf->orig_pte;if(unlikely(!pte_same(*vmf->pte,entry)))gotounlock;if(vmf->flags&FAULT_FLAG_WRITE){/* 请求发起写操作 */if(!pte_write(entry))/* 但页面不允许写 */returndo_wp_page(vmf);/* 做写时拷贝(COW: Copy-On-Write) */entry=pte_mkdirty(entry);}entry=pte_mkyoung(entry);...unlock:pte_unmap_unlock(vmf->pte,vmf->ptl);return0;}staticintdo_wp_page(structvm_fault*vmf)__releases(vmf->ptl){...returnwp_page_copy(vmf);}

2.3.2 父子进程写 COW 匿名映射过程

size_tsize=1*1024*1024;char*ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);// (1) 写操作触发 page fault,分配内存页面ptr[0]='A';fork();// (2.1) 父进程写触发 page fault,// (2.2)ptr[0]='B';
  • 父进程写触发 COW

假定父进程先执行ptr[0] = 'B';,在写入过程中,会触发 page fault 并进行 COW:

staticintdo_wp_page(structvm_fault*vmf)__releases(vmf->ptl){structvm_area_struct*vma=vmf->vma;vmf->page=vm_normal_page(vma,vmf->address,vmf->orig_pte);.../* * Ok, we need to copy. Oh, well.. */get_page(vmf->page);pte_unmap_unlock(vmf->pte,vmf->ptl);returnwp_page_copy(vmf);}staticintwp_page_copy(structvm_fault*vmf){...if(is_zero_pfn(pte_pfn(vmf->orig_pte))){/* 旧页面 是 zero page, 直接分配清 0 的新页面即可, 无需拷贝 */...}else{/* 旧页面 非 zero page, 分配新页面, 并拷贝旧页面内容 *//* 为(父进程)分配新页面 */new_page=alloc_page_vma(GFP_HIGHUSER_MOVABLE,vma,vmf->address);if(!new_page)gotooom;/* 拷贝 (父进程) 旧页面内容 到 新分配的页面 */cow_user_page(new_page,old_page,vmf->address,vma);}.../* * Re-check the pte - we dropped the lock */vmf->pte=pte_offset_map_lock(mm,vmf->pmd,vmf->address,&vmf->ptl);if(likely(pte_same(*vmf->pte,vmf->orig_pte))){...flush_cache_page(vma,vmf->address,pte_pfn(vmf->orig_pte));/* 为(父进程)构建新的 PTE 页表项内容 */entry=mk_pte(new_page,vma->vm_page_prot);entry=maybe_mkwrite(pte_mkdirty(entry),vma);.../* 用新构建的 PTE 填充 PTE 页表项 */set_pte_at_notify(mm,vmf->address,vmf->pte,entry).../* Free the old page.. */new_page=old_page;page_copied=1;}else{...}...returnpage_copied?VM_FAULT_WRITE:0;...}

从上面的分析看到,为父进程创建了新的页面(假定父进程先执行ptr[0] = 'B';),然后拷贝旧页面内容,并填充了 PTE 页表项。

  • 子进程写触发 COW

假定子进程后执行ptr[0] = 'B';,在写入过程中,会触发 page fault 并进行 COW:

staticintdo_wp_page(structvm_fault*vmf)__releases(vmf->ptl){structvm_area_struct*vma=vmf->vma;vmf->page=vm_normal_page(vma,vmf->address,vmf->orig_pte);.../* * Take out anonymous pages first, anonymous shared vmas are * not dirty accountable. */if(PageAnon(vmf->page)&&!PageKsm(vmf->page)){...if(!trylock_page(vmf->page)){...}if(reuse_swap_page(vmf->page,&total_map_swapcount)){.../* 子进程 重用 fork 时 父进程 的 旧页面 */wp_page_reuse(vmf);returnVM_FAULT_WRITE;}}elseif(unlikely((vma->vm_flags&(VM_WRITE|VM_SHARED))==(VM_WRITE|VM_SHARED))){...}}staticinlinevoidwp_page_reuse(structvm_fault*vmf)__releases(vmf->ptl){structvm_area_struct*vma=vmf->vma;structpage*page=vmf->page;pte_tentry;/* * Clear the pages cpupid information as the existing * information potentially belongs to a now completely * unrelated process. */if(page)page_cpupid_xchg_last(page,(1<<LAST_CPUPID_SHIFT)-1);flush_cache_page(vma,vmf->address,pte_pfn(vmf->orig_pte));entry=pte_mkyoung(vmf->orig_pte);entry=maybe_mkwrite(pte_mkdirty(entry),vma);if(ptep_set_access_flags(vma,vmf->address,vmf->pte,entry,1))update_mmu_cache(vma,vmf->address,vmf->pte);pte_unmap_unlock(vmf->pte,vmf->ptl);}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:35:33

学历低?靠系统学习,也能逆袭优质实习单位

“学历不够&#xff0c;实习没门”——这是很多低学历求职者的共同焦虑。无数案例证明&#xff0c;学历只是求职的“敲门砖”之一&#xff0c;而非唯一通行证。只要找准方向&#xff0c;通过系统学习打造核心竞争力&#xff0c;低学历者同样能逆袭进入建行、工行、小鹏汽车等优…

作者头像 李华
网站建设 2026/4/16 12:43:56

【大数据架构:架构思想基础】Google三篇论文开启大数据处理序章:(数据存储)分布式架构、(数据计算)并行计算、(数据管理)分片存储

文章目录一、《GFS&#xff1a;谷歌文件系统》&#xff08;GFS: Google File System&#xff09;&#xff1a;分布式存储的奠基之作二、《MapReduce&#xff1a;简化大规模数据集的并行计算》&#xff08;MapReduce: Simplified Data Processing on Large Clusters&#xff09;…

作者头像 李华
网站建设 2026/4/16 12:15:52

Windows崩溃分析入门:minidump文件详细说明

蓝屏别慌&#xff01;一张 .dmp 文件如何揭开 Windows 崩溃的真相 你有没有遇到过这样的情况&#xff1a;电脑用得好好的&#xff0c;突然“啪”一下蓝屏重启&#xff0c;再开机几分钟后又蓝屏&#xff1f;反复几次&#xff0c;心态崩了。重装系统、换内存条、清灰……试了个…

作者头像 李华
网站建设 2026/4/16 13:02:19

Windows下React Native搭建环境完整指南

从零开始&#xff1a;Windows 上手 React Native 开发环境搭建实战指南 你是不是也经历过这样的时刻&#xff1f;兴致勃勃想用 React Native 写个跨平台 App&#xff0c;结果刚打开命令行输入 npx react-native run-android &#xff0c;一串红字就砸了过来——“找不到 SDK…

作者头像 李华
网站建设 2026/4/16 12:28:54

语音合成中的引述语气模拟:直接引语与间接引语区分

语音合成中的引述语气模拟&#xff1a;直接引语与间接引语区分 在有声书朗读到虚拟主播播报的日常场景中&#xff0c;我们常会听到这样的句子&#xff1a;“他笑着说‘我赢了’”。如果语音系统只是平铺直叙地读出这句话&#xff0c;听众很容易分不清——到底是“他”在笑&…

作者头像 李华
网站建设 2026/4/16 13:04:28

Keil5安装教程详细步骤解析:项目开发前的准备操作指南

Keil5安装与配置实战指南&#xff1a;从零搭建嵌入式开发环境 你是不是也曾在准备STM32项目时&#xff0c;被Keil5的安装流程卡住&#xff1f;下载失败、驱动不识别、编译报错……明明只是想点个LED&#xff0c;却在环境搭建上耗掉一整天。 别担心&#xff0c;这几乎是每个嵌…

作者头像 李华