Linux 6.6内核内存管理深度解析(十六):mmap — 用户态“申请内存“的真相

引言: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 (文件)vs anon_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 按需分配 :① 省内存 ------mmap 4GB 但只访问 100MB,就只占 100MB 物理内存;② 推迟成本 ------大文件映射时,缺页才读盘,不用一次性全读进来;③ 支持 overcommit ------进程可以"申请"超过物理内存的地址空间,只要不同时真正用满(这也埋下了 OOM killer #12 的伏笔)。代价是:缺页是同步的、逐页的 ------一次性访问大区域会触发一串缺页,所以有了 MAP_POPULATE(mm_populate util.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 记住三点

  1. 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_mt maple tree(vma_iter_store :2838)。全程无 alloc_page。

  2. 物理页按需分配,缺页靠 vma 决定怎么填 :真正访问时 handle_mm_fault(memory.c:5255)→ do_pte_missing(:3667)→ vma_is_anonymous 分流:匿名 do_anonymous_page(:4067 给零页)、文件 do_fault(:4678 读 page cache)。vma 是"这段地址的说明书"。

  3. 两个分水岭定行为 :匿名 vs 文件(看 vma->vm_file)决定"给零页还是读 page cache";MAP_SHARED vs MAP_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
相关推荐
ClickHouseDB1 小时前
ClickHouse Cloud 与 Snowflake 实时性价比对比:CostBench 测试中的 412 倍差距来
运维·数据库·系统架构
欧米欧1 小时前
Linux系统篇(七),环境变量的底层溯源,核心获取机制与安全应用
linux·运维·服务器
Ruiery1 小时前
Linux 6.6内核内存管理深度解析(十八):KASLR 内核地址空间随机化
linux·运维·服务器·安全
todoitbo2 小时前
群晖NAS部署JmalCloud:Docker安装、文件管理、博客与公网访问完整教程
运维·docker·容器
时间售卖铺2 小时前
触发器与函数实战:MySQL高效编程秘诀
运维·数据库·mysql·云原生
qetfw2 小时前
UOS MariaDB 数据库:安装、最小权限与备份验证
linux·数据库·mariadb·uos
前端精髓2 小时前
调用大模型API
运维·服务器·microsoft
跨境小彭3 小时前
Temu 全托管模式下,SKU 利润核算的实践复盘
大数据·运维·人工智能·自动化·temu
李纲明3 小时前
WordPress 定时发布不跑、备份邮件延误:先核 WP-Cron 触发与回环,再改系统 Cron
运维·wordpress·定时任务·devdiy·wp-cron