2026/6/9 22:50:19
网站建设
项目流程
学校网站建设介绍,商水住房城乡建设网站,qq邮箱官网登录入口,改wordpress评论邮箱Linux 内核中 mmap 的实现(基于 2.6.12)
概述
基于 2.6.12 内核, 说明 mmap 系统调用的核心数据结构、系统调用路径及关键实现. 主要文件: mm/mmap.c、mm/msync.c、mm/filemap.c、include/linux/mm.h、include/linux/mman.h.
核心数据结构
mm_struct (进程地址空间描述符)
// i…Linux 内核中 mmap 的实现(基于 2.6.12)概述基于 2.6.12 内核, 说明 mmap 系统调用的核心数据结构、系统调用路径及关键实现. 主要文件:mm/mmap.c、mm/msync.c、mm/filemap.c、include/linux/mm.h、include/linux/mman.h.核心数据结构mm_struct (进程地址空间描述符)// include/linux/mm_types.hstructmm_struct{structvm_area_struct*mmap;// VMA 链表头structrb_rootmm_rb;// VMA 红黑树根节点structvm_area_struct*mmap_cache;// 最近使用的 VMA 缓存unsignedlongfree_area_cache;// 空闲区域搜索的起始地址unsignedlongmmap_base;// mmap 区域的基地址unsignedlongtotal_vm;// 总虚拟内存页数unsignedlonglocked_vm;// 锁定的内存页数structrw_semaphoremmap_sem;// 保护地址空间的读写信号量// ... 其他字段};vm_area_struct (虚拟内存区域)// include/linux/mm_types.hstructvm_area_struct{structmm_struct*vm_mm;// 所属的地址空间unsignedlongvm_start;// 虚拟地址区间起始地址unsignedlongvm_end;// 虚拟地址区间结束地址(不包含)structvm_area_struct*vm_next;// 链表中的下一个 VMAstructrb_nodevm_rb;// 红黑树节点unsignedlongvm_flags;// 权限与属性标志pgprot_tvm_page_prot;// 页保护属性pgoff_tvm_pgoff;// 文件页偏移(文件映射时使用)structfile*vm_file;// 关联的文件对象(文件映射时使用)void*vm_private_data;// 私有数据structvm_operations_struct*vm_ops;// VMA 操作函数指针// ... 其他字段};file (文件对象)// include/linux/fs.hstructfile{structdentry*f_dentry;// 目录项structvfsmount*f_vfsmnt;// 文件系统挂载点structfile_operations*f_op;// 文件操作函数指针structaddress_space*f_mapping;// 地址空间(用于页缓存)fmode_tf_mode;// 文件打开模式// ... 其他字段};系统调用路径(x86_64 类推)sys_mmap/sys_mmap2(架构层入口) →do_mmap_pgoff(核心实现)sys_munmap→do_munmap(解除映射)sys_msync→msync_interval(同步映射区域到文件)sys_mprotect→do_mprotect(修改保护属性)核心流程创建映射(mmap)流程概述:参数校验与页对齐: 检查长度、偏移、权限标志选择/验证地址: 非 MAP_FIXED 由get_unmapped_area选择合适空洞, MAP_FIXED 必须满足对齐且不与现有 VMA 冲突权限检查与文件映射: 共享映射需要写权限匹配, 文件映射调用file-f_op-mmap建立关联建立 VMA: 分配vm_area_struct, 设置vm_flags、vm_pgoff、vm_file, 插入mm_struct的 VMA 红黑树和链表返回映射起始地址sys_mmap2 系统调用入口// arch/x86_64/kernel/sys_x86_64.c (简化示意)asmlinkagelongsys_mmap2(unsignedlongaddr,unsignedlonglen,unsignedlongprot,unsignedlongflags,unsignedlongfd,unsignedlongpgoff){structfile*fileNULL;unsignedlongerror;// 如果不是匿名映射, 获取文件指针if(!(flagsMAP_ANONYMOUS)){filefget(fd);if(!file)return-EBADF;}// 获取写锁保护地址空间操作down_write(current-mm-mmap_sem);// do_mmap_pgoff 完成核心逻辑errordo_mmap_pgoff(file,addr,len,prot,flags,pgoff);up_write(current-mm-mmap_sem);if(file)fput(file);returnerror;}do_mmap_pgoff 核心实现// mm/mmap.cunsignedlongdo_mmap_pgoff(structfile*file,unsignedlongaddr,unsignedlonglen,unsignedlongprot,unsignedlongflags,unsignedlongpgoff){structmm_struct*mmcurrent-mm;structvm_area_struct*vma,*prev;structinode*inode;unsignedintvm_flags;intcorrect_wcount0;interror;structrb_node**rb_link,*rb_parent;intaccountable1;unsignedlongcharged0,reqprotprot;// 1. 文件映射的初步检查if(file){// 检查文件是否支持 mmap 操作if(!file-f_op||!file-f_op-mmap)return-ENODEV;// 检查执行权限与文件系统挂载标志if((protPROT_EXEC)(file-f_vfsmnt-mnt_flagsMNT_NOEXEC))return-EPERM;}// 2. 处理 READ_IMPLIES_EXEC 个性标志// 某些架构/程序期望 PROT_READ 隐含 PROT_EXECif((protPROT_READ)(current-personalityREAD_IMPLIES_EXEC))if(!(file(file-f_vfsmnt-mnt_flagsMNT_NOEXEC)))prot|PROT_EXEC;// 3. 参数校验: 长度不能为 0if(!len)return-EINVAL;// 4. 长度页对齐并检查溢出lenPAGE_ALIGN(len);if(!len||lenTASK_SIZE)return-ENOMEM;// 5. 检查文件偏移溢出if((pgoff(lenPAGE_SHIFT))pgoff)return-EOVERFLOW;// 6. 检查 VMA 数量限制if(mm-map_countsysctl_max_map_count)return-ENOMEM;// 7. 获取或选择映射地址// 非 MAP_FIXED 时由 get_unmapped_area 选择合适地址addrget_unmapped_area(file,addr,len,pgoff,flags);if(addr~PAGE_MASK)returnaddr;// 返回错误码// 8. 计算 VMA 标志位// 将用户空间的 prot 和 flags 转换为内核的 vm_flagsvm_flagscalc_vm_prot_bits(prot)|calc_vm_flag_bits(flags)|mm-def_flags|VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC;// 9. 处理 MAP_LOCKED 标志(锁定内存)if(flagsMAP_LOCKED){if(!can_do_mlock())return-EPERM;vm_flags|VM_LOCKED;// 检查内存锁定限制unsignedlonglocked,lock_limit;lockedlenPAGE_SHIFT;lockedmm-locked_vm;lock_limitcurrent-signal-rlim[RLIMIT_MEMLOCK].rlim_cur;lock_limitPAGE_SHIFT;if(lockedlock_limit!capable(CAP_IPC_LOCK))return-EAGAIN;}inodefile?file-f_dentry-d_inode:NULL;// 10. 根据映射类型设置 VMA 标志if(file){switch(flagsMAP_TYPE){caseMAP_SHARED:// 共享映射: 需要写权限时检查文件是否可写if((protPROT_WRITE)!(file-f_modeFMODE_WRITE))return-EACCES;// 不能对追加模式文件进行共享写映射if(IS_APPEND(inode)(file-f_modeFMODE_WRITE))return-EACCES;// 检查是否有强制锁if(locks_verify_locked(inode))return-EAGAIN;vm_flags|VM_SHARED|VM_MAYSHARE;// 如果文件不可写, 清除写权限标志if(!(file-f_modeFMODE_WRITE))vm_flags~(VM_MAYWRITE|VM_SHARED);break;caseMAP_PRIVATE:// 私有映射: 至少需要读权限if(!(file-f_modeFMODE_READ))return-EACCES;break;default:return-EINVAL;}}else{// 匿名映射switch(flagsMAP_TYPE){caseMAP_SHARED:vm_flags|VM_SHARED|VM_MAYSHARE;break;caseMAP_PRIVATE:// 设置 pgoff 为虚拟地址对应的页号pgoffaddrPAGE_SHIFT;break;default:return-EINVAL;}}// 11. LSM (Linux Security Module) 安全检查errorsecurity_file_mmap(file,reqprot,prot,flags);if(error)returnerror;// 12. 清除可能冲突的旧映射error-ENOMEM;munmap_back:vmafind_vma_prepare(mm,addr,prev,rb_link,rb_parent);if(vmavma-vm_startaddrlen){// 如果新映射与现有 VMA 冲突, 先解除旧映射if(do_munmap(mm,addr,len))return-ENOMEM;gotomunmap_back;}// 13. 检查地址空间限制if(!may_expand_vm(mm,lenPAGE_SHIFT))return-ENOMEM;// 14. 内存记账(accounting)if(accountable(!(flagsMAP_NORESERVE)||sysctl_overcommit_memoryOVERCOMMIT_NEVER)){if(vm_flagsVM_SHARED){// 共享映射需要记账vm_flags|VM_ACCOUNT;}elseif(vm_flagsVM_WRITE){// 私有可写映射需要预留内存chargedlenPAGE_SHIFT;if(security_vm_enough_memory(charged))return-ENOMEM;vm_flags|VM_ACCOUNT;}}// 15. 尝试合并相邻的匿名私有映射if(!file!(vm_flagsVM_SHARED)vma_merge(mm,prev,addr,addrlen,vm_flags,NULL,NULL,pgoff,NULL))gotoout;// 16. 分配 VMA 结构体vmakmem_cache_alloc(vm_area_cachep,SLAB_KERNEL);if(!vma){error-ENOMEM;gotounacct_error;}memset(vma,0,sizeof(*vma));// 17. 初始化 VMA 基本字段vma-vm_mmmm;vma-vm_startaddr;vma-vm_endaddrlen;vma-vm_flagsvm_flags;vma-vm_page_protprotection_map[vm_flags0x0f];vma-vm_pgoffpgoff;// 18. 处理文件映射或匿名共享映射if(file){// 处理 MAP_DENYWRITE 标志(禁止其他进程写入文件)if(vm_flagsVM_DENYWRITE){errordeny_write_access(file);if(error)gotofree_vma;correct_wcount1;}vma-vm_filefile;get_file(file);// 增加文件引用计数// 调用文件系统的 mmap 方法(通常是 generic_file_mmap)errorfile-f_op-mmap(file,vma);if(error)gotounmap_and_free_vma;}elseif(vm_flagsVM_SHARED){// 匿名共享映射: 使用 shmem (共享内存文件系统)errorshmem_zero_setup(vma);if(error)gotofree_vma;}// 19. 清理共享映射的 VM_ACCOUNT 标志(由 shmem 负责记账)if((vm_flags(VM_SHARED|VM_ACCOUNT))(VM_SHARED|VM_ACCOUNT))vma-vm_flags~VM_ACCOUNT;// 20. 保存可能被文件系统 mmap 方法修改的地址和偏移addrvma-vm_start;pgoffvma-vm_pgoff;vm_flagsvma-vm_flags;// 21. 尝试与相邻 VMA 合并, 或插入新的 VMAif(!file||!vma_merge(mm,prev,addr,vma-vm_end,vma-vm_flags,NULL,file,pgoff,vma_policy(vma))){// 不能合并, 插入新的 VMAfilevma-vm_file;vma_link(mm,vma,prev,rb_link,rb_parent);if(correct_wcount)atomic_inc(inode-i_writecount);}else{// 可以合并, 释放刚分配的 VMAif(file){if(correct_wcount)atomic_inc(inode-i_writecount);fput(file);}mpol_free(vma_policy(vma));kmem_cache_free(vm_area_cachep,vma);}out:// 22. 更新统计信息mm-total_vmlenPAGE_SHIFT;__vm_stat_account(mm,vm_flags,file,lenPAGE_SHIFT);if(vm_flagsVM_LOCKED){mm-locked_vmlenPAGE_SHIFT;// MAP_LOCKED 时立即分配物理页make_pages_present(addr,addrlen);}// 23. 处理 MAP_POPULATE 标志(预分配页)if(flagsMAP_POPULATE){up_write(mm-mmap_sem);sys_remap_file_pages(addr,len,0,pgoff,flagsMAP_NONBLOCK);down_write(mm-mmap_sem);}returnaddr;unmap_and_free_vma:// 错误处理: 文件系统 mmap 失败if(correct_wcount)atomic_inc(inode-i_writecount);vma-vm_fileNULL;fput(file);unmap_region(mm,vma,prev,vma-vm_start,vma-vm_end);charged0;free_vma:kmem_cache_free(vm_area_cachep,vma);unacct_error:if(charged)vm_unacct_memory(charged);returnerror;}get_unmapped_area 地址选择// mm/mmap.cunsignedlongget_unmapped_area(structfile*file,unsignedlongaddr,unsignedlonglen,unsignedlongpgoff,unsignedlongflags){unsignedlongret;// 非 MAP_FIXED 时选择地址if(!(flagsMAP_FIXED)){unsignedlong(*get_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);// 优先使用文件系统提供的地址选择函数get_areacurrent-mm-get_unmapped_area;if(filefile-f_opfile-f_op-get_unmapped_area)get_areafile-f_op-get_unmapped_area;addrget_area(file,addr,len,pgoff,flags);if(IS_ERR_VALUE(addr))returnaddr;}// 检查地址范围是否有效if(addrTASK_SIZE-len)return-ENOMEM;// 检查地址是否页对齐if(addr~PAGE_MASK)return-EINVAL;// 处理大页映射的特殊检查if(fileis_file_hugepages(file)){retprepare_hugepage_range(addr,len);}else{retis_hugepage_only_range(current-mm,addr,len);}if(ret)return-EINVAL;returnaddr;}// 默认的地址选择算法(自底向上)unsignedlongarch_get_unmapped_area(structfile*filp,unsignedlongaddr,unsignedlonglen,unsignedlongpgoff,unsignedlongflags){structmm_struct*mmcurrent-mm;structvm_area_struct*vma;unsignedlongstart_addr;if(lenTASK_SIZE)return-ENOMEM;// 如果指定了建议地址, 先检查是否可用if(addr){addrPAGE_ALIGN(addr);vmafind_vma(mm,addr);if(TASK_SIZE-lenaddr(!vma||addrlenvma-vm_start))returnaddr;}// 从上次搜索的缓存地址开始start_addraddrmm-free_area_cache;full_search:// 遍历 VMA 链表查找空闲区域for(vmafind_vma(mm,addr);;vmavma-vm_next){// 检查是否超出地址空间if(TASK_SIZE-lenaddr){// 从头开始重新搜索if(start_addr!TASK_UNMAPPED_BASE){start_addraddrTASK_UNMAPPED_BASE;gotofull_search;}return-ENOMEM;}// 找到合适的空洞if(!vma||addrlenvma-vm_start){// 更新缓存地址mm-free_area_cacheaddrlen;returnaddr;}addrvma-vm_end;}}generic_file_mmap 文件映射设置// mm/filemap.cintgeneric_file_mmap(structfile*file,structvm_area_struct*vma){structaddress_space*mappingfile-f_mapping;// 检查地址空间是否支持 readpage 操作(用于缺页处理)if(!mapping-a_ops-readpage)return-ENOEXEC;// 更新文件的访问时间file_accessed(file);// 设置 VMA 的操作函数指针(包含 fault、populate 等)vma-vm_opsgeneric_file_vm_ops;return0;}vma_link 插入 VMA// mm/mmap.cstaticvoidvma_link(structmm_struct*mm,structvm_area_struct*vma,structvm_area_struct*prev,structrb_node**rb_link,structrb_node*rb_parent){// 插入到 VMA 链表__vma_link_list(mm,vma,prev);// 插入到 VMA 红黑树__vma_link_rb(mm,vma,rb_link,rb_parent);// 如果文件映射, 还需要插入到文件的地址空间树if(vma-vm_file){structaddress_space*mappingvma-vm_file-f_mapping;spin_lock(mapping-i_mmap_lock);__vma_link_file(vma);spin_unlock(mapping-i_mmap_lock);}// 更新 VMA 计数mm-map_count;}解除映射(munmap)流程概述:对齐地址与长度: 地址必须页对齐, 长度页对齐查找覆盖的 VMA: 找到与解除区域重叠的所有 VMA拆分 VMA: 如果解除区域只覆盖 VMA 的一部分, 需要拆分移除 VMA: 从链表、红黑树、文件地址空间树中移除释放页表和资源: 调用unmap_region释放页表项和物理页sys_munmap 系统调用入口// mm/mmap.casmlinkagelongsys_munmap(unsignedlongaddr,size_tlen){intret;structmm_struct*mmcurrent-mm;profile_munmap(addr);// 获取写锁保护地址空间操作down_write(mm-mmap_sem);retdo_munmap(mm,addr,len);up_write(mm-mmap_sem);returnret;}do_munmap 核心实现// mm/mmap.cintdo_munmap(structmm_struct*mm,unsignedlongstart,size_tlen){unsignedlongend;structvm_area_struct*vma,*prev,*last;// 1. 参数校验: 地址必须页对齐, 范围必须有效if((start~PAGE_MASK)||startTASK_SIZE||lenTASK_SIZE-start)return-EINVAL;// 2. 长度页对齐if((lenPAGE_ALIGN(len))0)return-EINVAL;// 3. 查找第一个重叠的 VMAvmafind_vma_prev(mm,start,prev);if(!vma)return0;// 没有重叠的 VMA, 直接返回成功// 此时 start vma-vm_end// 4. 检查是否真的重叠endstartlen;if(vma-vm_startend)return0;// 不重叠// 5. 如果解除区域不是从 VMA 起始处开始, 需要拆分 VMAif(startvma-vm_start){interrorsplit_vma(mm,vma,start,0);if(error)returnerror;prevvma;// 拆分后 prev 指向前半部分}// 6. 检查是否需要拆分最后一个 VMAlastfind_vma(mm,end);if(lastendlast-vm_start){interrorsplit_vma(mm,last,end,1);if(error)returnerror;}// 重新定位到要解除的第一个 VMAvmaprev?prev-vm_next:mm-mmap;// 7. 从链表和红黑树中分离要解除的 VMAdetach_vmas_to_be_unmapped(mm,vma,prev,end);// 8. 解除页表映射并释放物理页unmap_region(mm,vma,prev,start,end);// 9. 释放 VMA 结构体和相关资源unmap_vma_list(mm,vma);return0;}同步映射区域(msync)流程概述:参数校验: 地址页对齐, 标志位检查查找覆盖的 VMA: 遍历所有与同步区域重叠的 VMA同步脏页: 对于共享映射, 将脏页写回文件文件系统同步: MS_SYNC 时调用文件系统的 fsync 方法sys_msync 系统调用入口// mm/msync.casmlinkagelongsys_msync(unsignedlongstart,size_tlen,intflags){unsignedlongend;structvm_area_struct*vma;intunmapped_error,error-EINVAL;// 1. 设置同步写标志(用于 I/O 调度)if(flagsMS_SYNC)current-flags|PF_SYNCWRITE;down_read(current-mm-mmap_sem);// 2. 参数校验: 标志位检查if(flags~(MS_ASYNC|MS_INVALIDATE|MS_SYNC))gotoout;// 地址必须页对齐if(start~PAGE_MASK)gotoout;// MS_ASYNC 和 MS_SYNC 不能同时设置if((flagsMS_ASYNC)(flagsMS_SYNC))gotoout;// 3. 长度页对齐并检查溢出error-ENOMEM;len(len~PAGE_MASK)PAGE_MASK;endstartlen;if(endstart)gotoout;error0;if(endstart)gotoout;// 4. 查找第一个覆盖的 VMAvmafind_vma(current-mm,start);unmapped_error0;// 5. 遍历所有覆盖的 VMA 进行同步for(;;){error-ENOMEM;if(!vma)gotoout;// 处理未映射的区域if(startvma-vm_start){unmapped_error-ENOMEM;startvma-vm_start;}// 同步当前 VMA 覆盖的部分if(endvma-vm_end){if(startend){errormsync_interval(vma,start,end,flags);if(error)gotoout;}errorunmapped_error;gotoout;}// 同步到当前 VMA 的结束errormsync_interval(vma,start,vma-vm_end,flags);if(error)gotoout;startvma-vm_end;vmavma-vm_next;}out:up_read(current-mm-mmap_sem);current-flags~PF_SYNCWRITE;returnerror;}msync_interval 同步区间// mm/msync.cstaticintmsync_interval(structvm_area_struct*vma,unsignedlongaddr,unsignedlongend,intflags){intret0;structfile*filevma-vm_file;// 1. MS_INVALIDATE 与 VM_LOCKED 冲突if((flagsMS_INVALIDATE)(vma-vm_flagsVM_LOCKED))return-EBUSY;// 2. 只处理共享文件映射if(file(vma-vm_flagsVM_SHARED)){// 同步页表中的脏页到页缓存filemap_sync(vma,addr,end);// 3. MS_SYNC 时同步到磁盘if(flagsMS_SYNC){structaddress_space*mappingfile-f_mapping;interr;// 将页缓存中的脏页写回磁盘retfilemap_fdatawrite(mapping);// 调用文件系统的 fsync 方法if(file-f_opfile-f_op-fsync){errfile-f_op-fsync(file,file-f_dentry,1);if(err!ret)reterr;}// 等待写操作完成errfilemap_fdatawait(mapping);if(!ret)reterr;}}returnret;}保护属性变更(mprotect)流程概述:对齐区间: 地址和长度页对齐遍历相关 VMA: 找到所有需要修改的 VMA校验新权限: 检查新权限是否合法(如文件不可写时不能设置写权限)拆分 VMA: 如果只修改部分 VMA, 需要拆分更新 vm_flags: 修改 VMA 标志位和页保护属性刷新 TLB: 使页表缓存失效关键限制(2.6.12 默认)vm.max_map_count: 每个进程最大 VMA 数量(/proc/sys/vm/max_map_count)RLIMIT_AS: 进程虚拟地址空间限制RLIMIT_MEMLOCK: 进程可锁定内存限制TASK_SIZE: 用户空间地址空间大小(架构相关)等待与唤醒mmap 本身不涉及等待队列, 但缺页时会在handle_mm_fault中可能因 I/O 阻塞MAP_LOCKED/SHM_LOCK 等锁定内存场景, 会受内存限额和锁定限制影响msync 的 MS_SYNC 模式会等待 I/O 完成文件与路径mm/mmap.c: mmap/munmap 系统调用实现、VMA 管理mm/msync.c: msync 系统调用实现mm/mprotect.c: mprotect 系统调用实现mm/filemap.c: 文件映射相关操作(generic_file_mmap等)include/linux/mm.h: 内存管理相关结构体和函数声明include/linux/mman.h: mmap 相关常量定义小结mmap 的核心在于do_mmap_pgoff: 地址选择、VMA 构建、文件/匿名映射、插入 VMA 结构munmap 通过do_munmap实现: 查找重叠 VMA、拆分、移除、释放页表msync 通过msync_interval实现: 同步共享映射的脏页到文件2.6.12 不支持后续特性(如 memfd, userfaultfd, MAP_POPULATE 扩展行为等), 描述保持 2.6.12 视角