引言:mmap 返回成功了,但物理内存可能一个字节都没给
一个最常见的误解:mmap 申请 4GB 内存,返回地址了,就以为"拿到了 4GB 物理内存"。真相是------mmap 只登记了一块 vma(虚拟地址区间 + 权限 + 来源),一个物理页都没分配。等你真正读写这段地址(触发缺页),内核才按需去分配/读入物理页。
这就是按需分配(demand paging) :mmap 是"登记"这一步,缺页(#3)是"真正给内存"那一步。本篇讲的是前半段------内核怎么把用户态的一段虚拟地址"登记在案",以及这段地址是匿名 的(无文件后备,给零页)还是文件的(有文件后备,从 page cache 读)。
这条链路:mmap 系统调用 → do_mmap 选地址、算权限 → mmap_region 建 vma 插入地址空间树 → 缺页时按 vma 类型分配物理页。
全景图
#mermaid-svg-kxYKstm8Q6ihwOjs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kxYKstm8Q6ihwOjs .error-icon{fill:#552222;}#mermaid-svg-kxYKstm8Q6ihwOjs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kxYKstm8Q6ihwOjs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kxYKstm8Q6ihwOjs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kxYKstm8Q6ihwOjs .marker.cross{stroke:#333333;}#mermaid-svg-kxYKstm8Q6ihwOjs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kxYKstm8Q6ihwOjs p{margin:0;}#mermaid-svg-kxYKstm8Q6ihwOjs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster-label text{fill:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster-label span{color:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster-label span p{background-color:transparent;}#mermaid-svg-kxYKstm8Q6ihwOjs .label text,#mermaid-svg-kxYKstm8Q6ihwOjs span{fill:#333;color:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs .node rect,#mermaid-svg-kxYKstm8Q6ihwOjs .node circle,#mermaid-svg-kxYKstm8Q6ihwOjs .node ellipse,#mermaid-svg-kxYKstm8Q6ihwOjs .node polygon,#mermaid-svg-kxYKstm8Q6ihwOjs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kxYKstm8Q6ihwOjs .rough-node .label text,#mermaid-svg-kxYKstm8Q6ihwOjs .node .label text,#mermaid-svg-kxYKstm8Q6ihwOjs .image-shape .label,#mermaid-svg-kxYKstm8Q6ihwOjs .icon-shape .label{text-anchor:middle;}#mermaid-svg-kxYKstm8Q6ihwOjs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kxYKstm8Q6ihwOjs .rough-node .label,#mermaid-svg-kxYKstm8Q6ihwOjs .node .label,#mermaid-svg-kxYKstm8Q6ihwOjs .image-shape .label,#mermaid-svg-kxYKstm8Q6ihwOjs .icon-shape .label{text-align:center;}#mermaid-svg-kxYKstm8Q6ihwOjs .node.clickable{cursor:pointer;}#mermaid-svg-kxYKstm8Q6ihwOjs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kxYKstm8Q6ihwOjs .arrowheadPath{fill:#333333;}#mermaid-svg-kxYKstm8Q6ihwOjs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kxYKstm8Q6ihwOjs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kxYKstm8Q6ihwOjs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kxYKstm8Q6ihwOjs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kxYKstm8Q6ihwOjs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kxYKstm8Q6ihwOjs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster text{fill:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs .cluster span{color:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kxYKstm8Q6ihwOjs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kxYKstm8Q6ihwOjs rect.text{fill:none;stroke-width:0;}#mermaid-svg-kxYKstm8Q6ihwOjs .icon-shape,#mermaid-svg-kxYKstm8Q6ihwOjs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kxYKstm8Q6ihwOjs .icon-shape p,#mermaid-svg-kxYKstm8Q6ihwOjs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kxYKstm8Q6ihwOjs .icon-shape .label rect,#mermaid-svg-kxYKstm8Q6ihwOjs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kxYKstm8Q6ihwOjs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kxYKstm8Q6ihwOjs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kxYKstm8Q6ihwOjs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户态 mmap
sys_x86_64.c:86
ksys_mmap_pgoff
mmap.c:1382 按 fd 找 file
vm_mmap_pgoff util.c:533
拿 mmap 写锁
do_mmap mmap.c:1203
get_unmapped_area 选地址 + 算 vm_flags
mmap_region mmap.c:2662
建 vma 插入地址空间树
匿名? vma_set_anonymous :2819
文件? call_mmap :2774
插入 maple tree
vma_iter_store :2838
按需分配(真正访问时)
handle_mm_fault memory.c:5255
匿名: do_anonymous_page :4067
给零页 / 写时复制
文件: do_fault :4678
从 page cache 读
一句话:用户态 mmap → do_mmap 选地址算权限 → mmap_region 建 vma 插进 maple tree(不分配物理页)→ 真正访问时 handle_mm_fault 按 vma 类型(匿名给零页 / 文件读 page cache)按需分配。
一、mmap 系统调用:参数与 flags
1.1 签名
c
// SYSCALL_DEFINE6(mmap)(arch/x86/kernel/sys_x86_64.c:86)
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off);
// 实现(:86-94)------ 只做一件事:把字节偏移 off 右移 PAGE_SHIFT 变成"页号",交给 ksys_mmap_pgoff
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
if (off & ~PAGE_MASK) // 偏移必须页对齐
return -EINVAL;
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}
六个参数里最关键的三个:
| 参数 | 含义 | 关键值 |
|---|---|---|
| prot | 访问权限 | PROT_READ 0x1 / PROT_WRITE 0x2 / PROT_EXEC 0x4 / PROT_NONE 0x0(mman-common.h:10-16) |
| flags | 映射方式 | MAP_SHARED 0x01 / MAP_PRIVATE 0x02(mman.h:17-18)、MAP_ANONYMOUS 0x20 / MAP_FIXED 0x10 / MAP_POPULATE(mman-common.h:22-26) |
| fd + off | 映射来源 | fd != -1 文件映射、fd == -1(或 MAP_ANONYMOUS)匿名映射 |
1.2 两个关键分水岭
分水岭一:匿名 vs 文件 ,看 fd(更准确说,最终落在 vma->vm_file 是否为空):
- 匿名映射 (
MAP_ANONYMOUS或fd == -1):没有文件后备。缺页时给零页 (读)或新分配的零填充页(写)。就是"纯内存"。 - 文件映射 (
fd指向一个文件):有文件后备。缺页时从 page cache 读对应偏移的内容。
分水岭二:共享 vs 私有 ,看 MAP_SHARED / MAP_PRIVATE:
MAP_SHARED:改动对同一文件的其他映射/进程可见,且会写回文件。MAP_PRIVATE:改动是私有的(COW 写时复制 ),不写回文件;对文件的写会触发do_wp_page(#3)复制一页出来改副本。
这两个分水岭可以两两组合,最常见的四种(见第五节)。
二、struct vm_area_struct:地址空间的"登记表"
mmap 的核心产物是一块 vma(virtual memory area,虚拟内存区)------它描述"一段连续的虚拟地址区间 + 统一的属性"。这是 Linux 地址空间管理的最小单元。
c
// struct vm_area_struct(include/linux/mm_types.h:565)------ 关键字段
struct vm_area_struct {
unsigned long vm_start; // 区间起点 [vm_start, vm_end)
unsigned long vm_end; // 区间终点(开区间)
struct mm_struct *vm_mm; // 属于哪个进程的地址空间(:579)
pgprot_t vm_page_prot; // 这段区间的页表权限(:580)
vm_flags_t vm_flags; // VM_READ/WRITE/SHARED/... 属性位(:587)
struct { // 文件映射: 挂到 address_space->i_mmap 区间树
struct rb_node rb; // (:618-621)
unsigned long rb_subtree_last;
} shared;
struct list_head anon_vma_chain; // 匿名 COW: 跟踪共享同一物理页的 vma(:629)
struct anon_vma *anon_vma; // (:631)
const struct vm_operations_struct *vm_ops; // 文件系统/驱动的回调(:634)
unsigned long vm_pgoff; // 映射起点在文件内的页偏移(:637)
struct file *vm_file; // 后备文件,匿名映射为 NULL(:639)
void *vm_private_data; // (:640)
};
几个字段点破本质:
vm_start/vm_end圈出区间,vm_flags+vm_page_prot定权限,vm_file/vm_pgoff定来源------这三样加一起,缺页时就能回答"这段地址该怎么填页"。shared.rb(文件)vsanon_vma_chain(匿名):两套不同的"反向查找"结构。文件映射挂在文件address_space->i_mmap区间树上(便于写回时找到所有映射此文件的进程);匿名映射靠 anon_vma 串起共享同一物理页的多个 vma(便于 COW/换出时改所有进程的页表)。vm_ops:文件系统/驱动(如显卡 DRM 驱动、tmpfs)通过它把mmap转交给自己的->mmap回调。
2.1 地址空间树:v6.6 用 maple tree
一个进程的所有 vma 存在 mm_struct 里,v6.6 用 maple tree (mm->mm_mt,mm_types.h:690)管理------这是 v6.1+ 用 maple tree 替换掉老红黑树 + 链表的演进:
c
// struct mm_struct(include/linux/mm_types.h:673)------ 关键字段
struct mm_struct {
struct {
struct maple_tree mm_mt; // 所有 vma 的树(:690),按地址有序
...
};
struct rw_semaphore mmap_lock; // 地址空间的大锁(:763)
...
};
maple tree 按 vm_start 有序存储 vma,缺页时 find_vma 能 O(log n) 找到包含某地址的 vma。mmap_lock 是读写信号量------改地址空间(mmap/munmap)拿写锁,缺页(多数情况)只拿读锁。
三、mmap 调用链:从系统调用到 vma 落树
完整链路(x86-64):
c
// ① 系统调用入口(sys_x86_64.c:86,前面已列)→ ② ksys_mmap_pgoff(mmap.c:1382)
// 按 fd 找到 file 后,转 ③ vm_mmap_pgoff(mm/util.c:533):
unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long pgoff)
{
...
ret = security_mmap_file(file, prot, flag); // LSM 安全检查
if (!ret) {
if (mmap_write_lock_killable(mm)) // 拿 mmap 写锁
return -EINTR;
ret = do_mmap(file, addr, len, prot, flag, 0, pgoff, &populate, &uf);
mmap_write_unlock(mm); // ④ do_mmap 在写锁保护下执行
...
if (populate)
mm_populate(ret, populate); // ⑤ MAP_POPULATE/MAP_LOCKED 提前 fault-in
}
return ret;
}
do_mmap(mm/mmap.c:1203)做两件关键事:
c
unsigned long do_mmap(struct file *file, unsigned long addr, ...)
{
...
len = PAGE_ALIGN(len); // 长度向上取整到页
if (mm->map_count > sysctl_max_map_count) // vma 数量上限(默认 65530)
return -ENOMEM;
/* ① 选地址:MAP_FIXED 用指定地址,否则让内核挑(top-down,ASLR 相关) */
addr = get_unmapped_area(file, addr, len, pgoff, flags); // :1250
/* ② 把 prot + flags 换算成 vm_flags */
vm_flags |= calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) | // :1269
mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
...
/* ③ 调 mmap_region 真正建 vma */
}
最后 mmap_region(mm/mmap.c:2662)落树:
c
unsigned long mmap_region(struct file *file, unsigned long addr, ...)
{
...
/* ① MAP_FIXED 先把目标区间已有的映射清掉 */
if (do_vmi_munmap(&vmi, mm, addr, len, uf, false)) // :2693
return -ENOMEM;
/* ② 可记账的映射(私有可写),预扣内存配额 */
if (accountable_mapping(file, vm_flags)) {
charged = len >> PAGE_SHIFT;
if (security_vm_enough_memory_mm(mm, charged)) // :2701
return -ENOMEM;
}
/* ③ 尝试和相邻 vma 合并(同一文件、连续、同权限 → 并成一块) */
if (vma && !vma_expand(&vmi, vma, merge_start, merge_end, ...)) // :2738-2739
goto expanded;
/* ④ 分配 vma 并填字段 */
vma = vm_area_alloc(mm); // :2753
vma->vm_start = addr; vma->vm_end = end;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff; // :2760-2764
if (file) {
vma->vm_file = get_file(file);
error = call_mmap(file, vma); // ⑤ 文件映射: 调 file->f_op->mmap(:2774)
} else if (vm_flags & VM_SHARED) {
error = shmem_zero_setup(vma); // ⑥ 匿名共享: 走 shmem(tmpfs 那套,:2815)
} else {
vma_set_anonymous(vma); // ⑦ 匿名私有: 标记为匿名(:2819)
}
/* ⑧ 插入地址空间树 + 文件区间树 */
vma_iter_store(&vmi, vma); // 插进 mm->mm_mt maple tree(:2838)
mm->map_count++;
if (vma->vm_file) {
vma_interval_tree_insert(vma, &vma->vm_file->f_mapping->i_mmap); // :2846
}
...
}
核心动作就三步:分配 vma → 填字段(start/end/flags/file)→ 插进 maple tree。全程没碰 alloc_page。
Why 分
do_mmap(选地址)和mmap_region(建 vma)两层 :do_mmap是"通用准备"------长度对齐、数量上限、选地址、算 vm_flags,任何来源的映射(文件/匿名/驱动)都要过;mmap_region是"通用收尾"------建 vma、按 file 有无分派、插树。中间按来源分派的那一步(call_mmap/shmem_zero_setup/vma_set_anonymous)是可替换的策略点 :文件系统/驱动(DRM、RDMA、HMM)通过注册自己的->mmap回调插进这条通用链,而不改 mmap 主框架。这也是内核"通用框架 + 策略回调"的复用典范(对比 #9 compaction 的compact_control+ 迁移回调)。
四、mmap 的灵魂:只登记,不分配(按需分配)
上一节看到 mmap_region 全程没调 alloc_page。那物理页什么时候来?在真正访问这段地址、触发缺页时 (#3 的 handle_mm_fault,memory.c:5255):
c
handle_mm_fault
→ do_pte_missing(memory.c:3667) // 页表项为空
→ vma_is_anonymous(vma) ?
do_anonymous_page(:4067) // 匿名:给零页(读)/ 新零填充页(写)
: do_fault(:4678) // 文件:从 page cache 读(或 COW)
关键点:缺页处理靠 vma 才知道该怎么填页 ------vma 在缺页入口 do_user_addr_fault(fault.c:1239)就通过 lock_mm_and_find_vma(:1385)找到了,一路传进 do_pte_missing(memory.c:3667),后者看 vma_is_anonymous(vmf->vma)(即 vm_file 是否为空)决定走匿名还是文件路径。所以 vma 就是"这段地址的说明书",mmap 登记了说明书,缺页照着说明书填页。
这也是为什么 mmap 大文件/大内存几乎瞬时返回、且不占物理内存:登记的成本是 O(1) 建个 vma,真正的内存成本推迟到每次缺页。
Why 按需分配 :① 省内存 ------
mmap4GB 但只访问 100MB,就只占 100MB 物理内存;② 推迟成本 ------大文件映射时,缺页才读盘,不用一次性全读进来;③ 支持 overcommit ------进程可以"申请"超过物理内存的地址空间,只要不同时真正用满(这也埋下了 OOM killer #12 的伏笔)。代价是:缺页是同步的、逐页的 ------一次性访问大区域会触发一串缺页,所以有了MAP_POPULATE(mm_populateutil.c:551 提前把页 fault-in)和大页(#4)来摊薄。
五、匿名 vs 文件:四种组合的缺页行为
| 组合 | 典型场景 | 缺页时干什么 | 改动去向 |
|---|---|---|---|
匿名 + MAP_PRIVATE |
malloc 大块、栈、堆、mmap(NULL,...,MAP_ANONYMOUS) |
do_anonymous_page 给零页(读)/新零页(写) |
只在自己进程内 |
匿名 + MAP_SHARED |
进程间共享内存(shm_open/tmpfs) |
走 shmem (shmem_zero_setup :2815),页在 shmem 的 page cache 里 |
所有映射此 shmem 的进程可见 |
文件 + MAP_SHARED |
模型权重/数据集文件映射、mmap 写回 | do_fault 从 page cache 读文件页 |
写回文件(刷 page cache,接 #5) |
文件 + MAP_PRIVATE |
加载可执行文件、只读共享库 | do_fault 从 page cache 读;写触发 COW (do_wp_page :3338 复制一页) |
改动私有,不写回文件 |
两个最容易混的:
- 文件 +
MAP_PRIVATE= COW :多个进程映射同一可执行文件/共享库,物理页只有一份(都在 page cache),读共享;某进程要写,do_wp_page(#3)复制一份给这个进程改,别人不受影响------这正是**"可执行文件只有一份物理内存"**的原因。 - 匿名 +
MAP_SHARED= shmem :别看MAP_ANONYMOUS,加了MAP_SHARED它其实走shmem_zero_setup(mmap.c:2815),背后是 tmpfs 那套------有 page cache、可换出(swap,接 #7)。所以"匿名共享"和"匿名私有"在内核里是两条不同的路。
六、munmap:释放虚拟区间
mmap 的反操作。SYSCALL_DEFINE2(munmap)(mmap.c:2942)→ __vm_munmap(:2918)→ do_munmap(:2654)→ unmap_region(:2322)→ 拆页表:
c
// do_munmap → unmap_region(mmap.c:2322)
static void unmap_region(struct mm_struct *mm, struct ma_state *mas, ...)
{
struct mmu_gather tlb;
tlb_gather_mmu(&tlb, mm);
unmap_vmas(&tlb, mas, vma, start, end, tree_end, mm_wr_locked); // ① 逐 vma 清页表
free_pgtables(&tlb, mas, vma, ...); // ② 释放空的各级页表
tlb_finish_mmu(&tlb); // ③ 刷 TLB
}
// unmap_vmas → unmap_page_range(memory.c:1629)→ zap_pte_range(memory.c:1394)
// 逐 PTE 清掉映射、回收物理页(匿名页 refcount--、文件页 unmap 但留在 page cache)
关键点:munmap 只清页表和释放匿名物理页;文件映射的物理页不释放 (那是 page cache,属于文件,不属于这个映射,见 #5)。批量刷 TLB 的套路:unmap_region 先 tlb_gather_mmu(:2331)开启批量收集,unmap_vmas → zap_pte_range 逐页清 PTE(把要刷的页记进 mmu_gather),最后 tlb_finish_mmu(:2338)一次刷,避免逐页刷 TLB 的开销。
七、与前面各篇的关系 + 记住三点
7.1 与前面各篇的关系
| 篇 | 关系 |
|---|---|
| #3 页表缺页 | mmap 是"登记 vma"(前半段),缺页是"按需分配"(后半段);缺页靠 find_vma 找到 vma 才知道怎么填页 |
| #5 page cache | 文件映射的物理页就是 page cache;do_fault 从 page cache 读,MAP_SHARED 改动写回 page cache |
| #6 GUP | pin_user_pages 把用户态 mmap 出来的页 pin 给设备 DMA;mmap 是 GUP 的对象 |
| #7 swap | 匿名页缺页是"新分配",不够了换出到 swap;匿名共享(shmem)也能换出 |
| #12 OOM | mmap 的 overcommit(登记了没真给)是 OOM 的伏笔------真用到才发现不够 |
| #4 大页 | MAP_HUGETLB 标志直接 mmap 大页;khugepaged 后台把普通映射合成大页(khugepaged_enter_vma mmap.c:2855) |
7.2 与 AI Infra 的关系
- 模型权重加载 :
mmap权重文件 + 按需读盘,比read少一次内核→用户拷贝(零拷贝);配合MAP_SHARED,权重在 page cache 里一份、多个进程共享。 - 大数据集 / 列存 :Arrow/Parquet 大文件
mmap后按需访问,省用户态缓冲。 - CUDA 零拷贝 / pinned memory :
cudaHostRegister背后是mlock(类似MAP_LOCKED语义)+ GUP pin(#6),把 mmap 的用户页固定住给 GPU DMA。 - 进程间共享 :
shm_open+mmap(MAP_SHARED)是多进程共享参数的常规做法。
7.3 记住三点
-
mmap 只登记 vma,不分配物理页 :
mmap→do_mmap(mmap.c:1203,get_unmapped_area选地址 :1250 +calc_vm_prot_bits/flag_bits算 vm_flags :1269)→mmap_region(:2662)建 vma、按来源分派(文件call_mmap:2774 / 匿名共享shmem_zero_setup:2815 / 匿名私有vma_set_anonymous:2819)、插进mm->mm_mtmaple tree(vma_iter_store:2838)。全程无alloc_page。 -
物理页按需分配,缺页靠 vma 决定怎么填 :真正访问时
handle_mm_fault(memory.c:5255)→do_pte_missing(:3667)→vma_is_anonymous分流:匿名do_anonymous_page(:4067 给零页)、文件do_fault(:4678 读 page cache)。vma 是"这段地址的说明书"。 -
两个分水岭定行为 :匿名 vs 文件(看
vma->vm_file)决定"给零页还是读 page cache";MAP_SHAREDvsMAP_PRIVATE决定"改动写回还是 COW"。文件 +MAP_PRIVATE= 可执行文件只有一份物理内存(写才 COW);匿名 +MAP_SHARED= shmem(有 page cache 可换出)。释放munmap→unmap_region(mmap.c:2322)→zap_pte_range(memory.c:1394)只清页表、文件页留在 page cache。
附:本篇关键源码索引
| 符号 | 位置 |
|---|---|
SYSCALL_DEFINE6(mmap)(x86 入口,off→pgoff) |
arch/x86/kernel/sys_x86_64.c:86 |
ksys_mmap_pgoff(按 fd 找 file) |
mm/mmap.c:1382 |
vm_mmap_pgoff(拿 mmap 写锁 + do_mmap + mm_populate) |
mm/util.c:533 |
do_mmap(get_unmapped_area 选地址 + 算 vm_flags) |
mm/mmap.c:1203 |
get_unmapped_area(地址选择,top-down) |
mm/mmap.c:1801 |
mmap_region(建 vma、分派、插树) |
mm/mmap.c:2662 |
call_mmap(文件映射 → file->f_op->mmap) |
include/linux/fs.h:1959 |
shmem_zero_setup(匿名共享 → shmem) |
mm/shmem.c:4852 |
vma_set_anonymous(匿名私有) |
include/linux/mm.h:875 |
vma_iter_store(插入 maple tree) |
mm/internal.h:1102 |
struct vm_area_struct(vm_start/vm_end/vm_flags/vm_file/vm_pgoff) |
include/linux/mm_types.h:565 |
struct mm_struct(mm_mt maple tree / mmap_lock) |
include/linux/mm_types.h:673(:690 / :763) |
handle_mm_fault / do_pte_missing(缺页入口) |
mm/memory.c:5255 / :3667 |
do_anonymous_page(匿名缺页给零页) |
mm/memory.c:4067 |
do_fault(文件缺页读 page cache) |
mm/memory.c:4678 |
do_wp_page(COW 写时复制) |
mm/memory.c:3338 |
SYSCALL_DEFINE2(munmap) / do_munmap |
mm/mmap.c:2942 / :2654 |
unmap_region(释放区间) |
mm/mmap.c:2322 |
unmap_page_range / zap_pte_range(清页表) |
mm/memory.c:1629 / :1394 |