news 2026/5/1 4:07:31

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/5/1 4:07:22

零样本语音生成新突破:GLM-TTS情感控制与音素级调节全解析

零样本语音生成新突破&#xff1a;GLM-TTS情感控制与音素级调节全解析 在虚拟主播越来越“能说会道”、有声书生产从人工朗读转向AI合成的今天&#xff0c;一个核心问题始终困扰着开发者&#xff1a;如何让机器语音不仅听起来像真人&#xff0c;还能像真人一样表达情绪、准确发…

作者头像 李华
网站建设 2026/4/21 12:37:30

GLM-TTS能否支持体育赛事解说?激情解说风格模拟

GLM-TTS能否支持体育赛事解说&#xff1f;激情解说风格模拟 在一场关键的足球决赛中&#xff0c;第89分钟&#xff0c;球员突入禁区、一脚劲射破门——此时&#xff0c;全场沸腾&#xff0c;解说员高呼“球进了&#xff01;&#xff01;&#xff01;”的声音划破空气。这种极具…

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

G2P_replace_dict.l配置教程:自定义多音字发音规则

G2P_replace_dict.l配置教程&#xff1a;自定义多音字发音规则 在中文语音合成的应用场景中&#xff0c;哪怕是最先进的TTS系统也常被一个看似简单的问题困扰——“重”到底读作“zhng”还是“chng”&#xff1f;这类多音字的歧义不仅影响听感自然度&#xff0c;更可能引发语义…

作者头像 李华
网站建设 2026/4/30 3:57:40

web语音应用新趋势:基于GLM-TTS构建在线配音平台原型

Web语音应用新趋势&#xff1a;基于GLM-TTS构建在线配音平台原型 在短视频内容爆炸式增长的今天&#xff0c;创作者们面临一个共同难题&#xff1a;如何快速为海量视频配上自然、富有表现力的声音&#xff1f;传统配音依赖专业录音师和后期制作&#xff0c;成本高、周期长。而…

作者头像 李华
网站建设 2026/4/28 9:14:30

GLM-TTS与Kafka消息队列集成:高吞吐量语音生成架构设计

GLM-TTS与Kafka消息队列集成&#xff1a;高吞吐量语音生成架构设计 在智能客服、有声读物和虚拟主播等场景中&#xff0c;个性化语音内容的自动化生产正从“能用”迈向“好用”。然而&#xff0c;当企业试图将高质量语音合成技术投入大规模应用时&#xff0c;常常面临一个现实困…

作者头像 李华
网站建设 2026/4/17 22:19:19

工业自动化场景下USB转串口无驱动应对策略

工业自动化中的“即插即用”革命&#xff1a;如何让USB转串口彻底告别驱动烦恼 你有没有遇到过这样的场景&#xff1f; 深夜调试一条产线&#xff0c;工控机突然无法识别USB转RS-485模块&#xff0c;设备管理器里赫然显示&#xff1a;“ usb-serial controller 找不到驱动程序…

作者头像 李华