news 2026/8/8 0:42:02

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/8/6 10:57:50

[特殊字符]_微服务架构下的性能调优实战[20260104165708]

作为一名经历过多个微服务架构项目的工程师&#xff0c;我深知在分布式环境下进行性能调优的复杂性。微服务架构虽然提供了良好的可扩展性和灵活性&#xff0c;但也带来了新的性能挑战。今天我要分享的是在微服务架构下进行性能调优的实战经验。 &#x1f4a1; 微服务架构的性…

作者头像 李华
网站建设 2026/8/7 20:25:21

Keil5破解涉及的授权层级结构:专业版权限制深度剖析

深入Keil5授权机制&#xff1a;专业版功能限制与破解路径的技术真相 你有没有在深夜调试一个嵌入式项目时&#xff0c;突然被一条警告打断——“Optimization level reduced due to license restrictions”&#xff1f; 或者刚配置好RTOS感知调试&#xff0c;却发现断点无法同…

作者头像 李华
网站建设 2026/7/30 23:47:01

GLM-TTS能否用于艺术展览?作品解读语音沉浸体验

GLM-TTS能否用于艺术展览&#xff1f;作品解读语音沉浸体验 在一座现代美术馆的展厅里&#xff0c;观众驻足于梵高的《星月夜》前。手机轻轻一扫&#xff0c;耳边响起的不是千篇一律的机械播报&#xff0c;而是一个带着轻微颤抖、语调低沉却饱含激情的声音&#xff1a;“这幅画…

作者头像 李华
网站建设 2026/7/28 1:10:15

GLM-TTS与Ceph对象存储集成:大规模音频文件持久化方案

GLM-TTS与Ceph对象存储集成&#xff1a;大规模音频文件持久化方案 在内容生成迈向“个性化”和“实时化”的今天&#xff0c;语音合成已不再是简单的文本朗读&#xff0c;而是承载情感、风格甚至人格表达的核心技术。以GLM-TTS为代表的先进TTS模型&#xff0c;凭借零样本音色克…

作者头像 李华
网站建设 2026/8/4 14:46:33

GLM-TTS与MinIO私有云存储集成:企业内部音频资产管理

GLM-TTS与MinIO私有云存储集成&#xff1a;企业内部音频资产管理 在智能语音内容爆发式增长的今天&#xff0c;越来越多的企业开始部署AI语音合成系统&#xff0c;用于客服播报、宣传配音、教育读物生成等场景。然而&#xff0c;一个普遍被忽视的问题是&#xff1a;当每天生成成…

作者头像 李华
网站建设 2026/8/7 5:02:35

I2C HID初学者指南:接口定义与报文格式通俗解释

I2C HID 初学者指南&#xff1a;从接口定义到报文解析的实战通解 你有没有遇到过这样的情况&#xff1f; 手头有个触摸屏模块&#xff0c;想接到主控板上&#xff0c;但主控没有USB Host功能&#xff1b;或者系统里已经挂了好几个旋钮、手势传感器&#xff0c;GPIO快被片选线…

作者头像 李华