Linux 内存映射mmap 完整篇:从用户态 API、VMA 到缺页与驱动实现

mmap 之后第一次访问就 SIGSEGV、文件映射与预期内容不一致、驱动里实现了 fops->mmap 但用户态读到的全是 0------多半不是「指针用错」,而是 映射类型(文件/匿名/设备)、权限与缺页路径 没对齐。

mmap 把「以字节流读写」变成「按页映射进进程地址空间」:共享库、帧缓冲、DMA 缓冲、环形队列零拷贝都靠它。本文从用户态 API → 内核 do_mmap/vma → 缺页 → 驱动 vm_operations_struct → 排障 Checklist 闭环。源 chapter(085--090)为提纲;正文按主线 mm 与真实符号重写。


源码锚点

路径 作用
mm/mmap.c do_mmap、VMA 插入与合并、权限检查
include/linux/mm.h vm_area_struct、vm_operations_struct、do_mmap/vm_mmap
mm/memory.c handle_mm_fault 一带缺页处理
mm/filemap.c 等 文件映射与 page cache 交互
include/linux/fs.h file_operations.mmap
man 2 mmap 用户态标志:MAP_SHARED/PRIVATE/ANONYMOUS 等

用户态 API:

c 复制代码
void *p = mmap(NULL, length,
	       PROT_READ | PROT_WRITE,
	       MAP_SHARED,   /* 或 MAP_PRIVATE / MAP_ANONYMOUS */
	       fd, offset);
if (p == MAP_FAILED) /* errno: EINVAL/ENOMEM/EACCES/... */
	perror("mmap");
/* 使用 p[i] ------ 可能触发缺页 */
munmap(p, length);

内核侧核心结构:

c 复制代码
/* include/linux/mm.h */
struct vm_area_struct {
	unsigned long vm_start, vm_end;
	struct file *vm_file;
	unsigned long vm_pgoff;          /* 页为单位的偏移 */
	pgprot_t vm_page_prot;
	unsigned long vm_flags;          /* VM_READ/WRITE/SHARED/... */
	const struct vm_operations_struct *vm_ops;
	/* ... */
};

struct vm_operations_struct {
	void (*open)(struct vm_area_struct *vma);
	void (*close)(struct vm_area_struct *vma);
	vm_fault_t (*fault)(struct vm_fault *vmf);
	/* ... */
};

驱动实现 mmap 的常见写法:

c 复制代码
static int my_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct my_dev *priv = file->private_data;
	/* 把设备缓冲区页映射进用户 VMA */
	return remap_pfn_range(vma, vma->vm_start,
			       page_to_pfn(priv->page),
			       vma->vm_end - vma->vm_start,
			       vma->vm_page_prot);
	/* 或:vma->vm_ops = &my_vm_ops; 在 fault 里 insert_pfn */
}

调用链

用户态 mmap 到建立 VMA

#mermaid-svg-PELDbVMBFikW3D0Q{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-PELDbVMBFikW3D0Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PELDbVMBFikW3D0Q .error-icon{fill:#552222;}#mermaid-svg-PELDbVMBFikW3D0Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PELDbVMBFikW3D0Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PELDbVMBFikW3D0Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PELDbVMBFikW3D0Q .marker.cross{stroke:#333333;}#mermaid-svg-PELDbVMBFikW3D0Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PELDbVMBFikW3D0Q p{margin:0;}#mermaid-svg-PELDbVMBFikW3D0Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PELDbVMBFikW3D0Q .cluster-label text{fill:#333;}#mermaid-svg-PELDbVMBFikW3D0Q .cluster-label span{color:#333;}#mermaid-svg-PELDbVMBFikW3D0Q .cluster-label span p{background-color:transparent;}#mermaid-svg-PELDbVMBFikW3D0Q .label text,#mermaid-svg-PELDbVMBFikW3D0Q span{fill:#333;color:#333;}#mermaid-svg-PELDbVMBFikW3D0Q .node rect,#mermaid-svg-PELDbVMBFikW3D0Q .node circle,#mermaid-svg-PELDbVMBFikW3D0Q .node ellipse,#mermaid-svg-PELDbVMBFikW3D0Q .node polygon,#mermaid-svg-PELDbVMBFikW3D0Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PELDbVMBFikW3D0Q .rough-node .label text,#mermaid-svg-PELDbVMBFikW3D0Q .node .label text,#mermaid-svg-PELDbVMBFikW3D0Q .image-shape .label,#mermaid-svg-PELDbVMBFikW3D0Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-PELDbVMBFikW3D0Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PELDbVMBFikW3D0Q .rough-node .label,#mermaid-svg-PELDbVMBFikW3D0Q .node .label,#mermaid-svg-PELDbVMBFikW3D0Q .image-shape .label,#mermaid-svg-PELDbVMBFikW3D0Q .icon-shape .label{text-align:center;}#mermaid-svg-PELDbVMBFikW3D0Q .node.clickable{cursor:pointer;}#mermaid-svg-PELDbVMBFikW3D0Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PELDbVMBFikW3D0Q .arrowheadPath{fill:#333333;}#mermaid-svg-PELDbVMBFikW3D0Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PELDbVMBFikW3D0Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PELDbVMBFikW3D0Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PELDbVMBFikW3D0Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PELDbVMBFikW3D0Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PELDbVMBFikW3D0Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PELDbVMBFikW3D0Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PELDbVMBFikW3D0Q .cluster text{fill:#333;}#mermaid-svg-PELDbVMBFikW3D0Q .cluster span{color:#333;}#mermaid-svg-PELDbVMBFikW3D0Q 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-PELDbVMBFikW3D0Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PELDbVMBFikW3D0Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-PELDbVMBFikW3D0Q .icon-shape,#mermaid-svg-PELDbVMBFikW3D0Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PELDbVMBFikW3D0Q .icon-shape p,#mermaid-svg-PELDbVMBFikW3D0Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PELDbVMBFikW3D0Q .icon-shape .label rect,#mermaid-svg-PELDbVMBFikW3D0Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PELDbVMBFikW3D0Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PELDbVMBFikW3D0Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PELDbVMBFikW3D0Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 匿名 MAP_ANONYMOUS
文件
无
有
用户态 mmap(fd, len, prot, flags, offset)
syscall 入口
do_mmap / vm_mmap
映射类型
匿名 VMA
file->f_op->mmap
驱动或文件系统设置 vm_ops/页
插入 mm->mm_rb / VMA 链表
返回用户虚拟地址
首次访问 → handle_mm_fault
已有页表项?
vm_ops->fault 或文件/匿名缺页
直接访问

三类映射数据流

#mermaid-svg-tycnx0Hu21WPnnjX{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-tycnx0Hu21WPnnjX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tycnx0Hu21WPnnjX .error-icon{fill:#552222;}#mermaid-svg-tycnx0Hu21WPnnjX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tycnx0Hu21WPnnjX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tycnx0Hu21WPnnjX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tycnx0Hu21WPnnjX .marker.cross{stroke:#333333;}#mermaid-svg-tycnx0Hu21WPnnjX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tycnx0Hu21WPnnjX p{margin:0;}#mermaid-svg-tycnx0Hu21WPnnjX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tycnx0Hu21WPnnjX .cluster-label text{fill:#333;}#mermaid-svg-tycnx0Hu21WPnnjX .cluster-label span{color:#333;}#mermaid-svg-tycnx0Hu21WPnnjX .cluster-label span p{background-color:transparent;}#mermaid-svg-tycnx0Hu21WPnnjX .label text,#mermaid-svg-tycnx0Hu21WPnnjX span{fill:#333;color:#333;}#mermaid-svg-tycnx0Hu21WPnnjX .node rect,#mermaid-svg-tycnx0Hu21WPnnjX .node circle,#mermaid-svg-tycnx0Hu21WPnnjX .node ellipse,#mermaid-svg-tycnx0Hu21WPnnjX .node polygon,#mermaid-svg-tycnx0Hu21WPnnjX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tycnx0Hu21WPnnjX .rough-node .label text,#mermaid-svg-tycnx0Hu21WPnnjX .node .label text,#mermaid-svg-tycnx0Hu21WPnnjX .image-shape .label,#mermaid-svg-tycnx0Hu21WPnnjX .icon-shape .label{text-anchor:middle;}#mermaid-svg-tycnx0Hu21WPnnjX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tycnx0Hu21WPnnjX .rough-node .label,#mermaid-svg-tycnx0Hu21WPnnjX .node .label,#mermaid-svg-tycnx0Hu21WPnnjX .image-shape .label,#mermaid-svg-tycnx0Hu21WPnnjX .icon-shape .label{text-align:center;}#mermaid-svg-tycnx0Hu21WPnnjX .node.clickable{cursor:pointer;}#mermaid-svg-tycnx0Hu21WPnnjX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tycnx0Hu21WPnnjX .arrowheadPath{fill:#333333;}#mermaid-svg-tycnx0Hu21WPnnjX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tycnx0Hu21WPnnjX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tycnx0Hu21WPnnjX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tycnx0Hu21WPnnjX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tycnx0Hu21WPnnjX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tycnx0Hu21WPnnjX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tycnx0Hu21WPnnjX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tycnx0Hu21WPnnjX .cluster text{fill:#333;}#mermaid-svg-tycnx0Hu21WPnnjX .cluster span{color:#333;}#mermaid-svg-tycnx0Hu21WPnnjX 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-tycnx0Hu21WPnnjX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tycnx0Hu21WPnnjX rect.text{fill:none;stroke-width:0;}#mermaid-svg-tycnx0Hu21WPnnjX .icon-shape,#mermaid-svg-tycnx0Hu21WPnnjX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tycnx0Hu21WPnnjX .icon-shape p,#mermaid-svg-tycnx0Hu21WPnnjX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tycnx0Hu21WPnnjX .icon-shape .label rect,#mermaid-svg-tycnx0Hu21WPnnjX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tycnx0Hu21WPnnjX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tycnx0Hu21WPnnjX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tycnx0Hu21WPnnjX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 设备
fops->mmap
remap_pfn_range / fault insert
设备内存或 DMA 缓冲
文件
fd 文件映射
page cache
MAP_SHARED 写回/PRIVATE COW
匿名
堆/匿名 mmap
缺页分配页框
可选写时复制


重点知识

1. 标志位决定语义

标志 含义 典型用途
MAP_SHARED 修改对其他映射/文件可见(按对象语义) 共享内存、帧缓冲
MAP_PRIVATE 写时复制,不写穿到底层文件 可执行文件私有映射
MAP_ANONYMOUS 无文件,内容填 0 大块堆外内存
MAP_FIXED 强制地址(危险,慎用) 特殊加载器
PROT_* 页权限;与 vm_page_prot 相关 与 NX/W^X 策略相关

offset 必须按页对齐;长度不足一页也按页粒度记账。EINVAL 常见原因:对齐、长度 0、prot/flags 非法组合。

2. VMA:进程地址空间的「一段合同」

每次成功 mmap(及 brk/栈扩展等)对应一个或多个 vm_area_struct。排障时:

bash 复制代码
cat /proc/<pid>/maps
cat /proc/<pid>/smaps   # 更细:RSS、PSS、Locked

看权限列(r-xp/rw-s 等)是否与 PROT_/MAP_SHARED 一致;看路径是文件、anon 还是 /dev/...。

3. 缺页才是「真正分配/插入页」的时刻

mmap 多数情况下只建立 VMA,不一定立刻分配物理页。第一次读/写触发缺页:

  • 匿名:分配页框并清零
  • 文件:从 page cache / 文件系统读入
  • 设备:走 vm_ops->fault 或已在 mmap 里 remap_pfn_range 建好映射

因此「mmap 成功但一访问就挂」要查:权限、是否已 munmap、设备 fault 是否失败、文件是否被截断(SIGBUS)。

4. 驱动里两种主流做法

  1. remap_pfn_range(或 vm_iomap_memory)

    在 mmap 调用时直接填页表。适合固定物理区、寄存器窗、已分配的 DMA 页。注意缓存属性(pgprot_noncached 等)与用户可写范围,避免把内核任意物理页暴露出去。

  2. 延迟 fault

    vma->vm_ops = &ops,在 fault 里 vmf_insert_pfn / 填 page。适合大范围、稀疏访问。

安全红线:校验 vma->vm_pgoff 与长度,防止用户映射越过缓冲区;不要把任意 __pa 开放给用户。

5. 与 read/write 如何选型

场景 更合适
小而偶发控制消息 read/write/ioctl
大块流式、零拷贝 mmap 或 splice/sendfile
寄存器级访问 mmap 非缓存窗或专用 ioctl
跨进程共享 MAP_SHARED + 文件(memfd/shm)或 ashmem 类机制

6. 常见坑

坑 现象 对策
未页对齐 offset EINVAL offset & (PAGE_SIZE-1) == 0
MAP_PRIVATE 当共享用 他进程看不见修改 改 MAP_SHARED 或用显式 IPC
映射后关闭 fd 仍用 多数情况合法,但勿误判生命周期 以 munmap 为准;文档写清
文件被截断 访问洞外 → SIGBUS 锁文件大小或处理信号
驱动未改 vm_page_prot 设备内存缓存不一致 按 SoC 要求设 noncached/writecombine
忘记 vm_ops->close 释放 泄漏 close/munmap 路径对称

7. 最小自测

bash 复制代码
# 匿名
python3 - <<'PY'
import mmap
m = mmap.mmap(-1, 4096)
m[0:5] = b'hello'
print(m[:5])
m.close()
PY

# 文件
dd if=/dev/zero of=/tmp/t bs=4096 count=1
# 再写小 C 程序 MAP_SHARED 读写验证

驱动:mmap 后用户态写魔数,驱动或另一映射读回比对。


Checklist

  • length>0、offset 页对齐;失败时打印 errno
  • 共享语义选对 MAP_SHARED/PRIVATE;匿名用 MAP_ANONYMOUS 且 fd=-1
  • /proc/self/maps 能看到对应 VMA 与权限
  • 理解「mmap 成功 ≠ 已分配页」;大页/锁页场景另验 mlock
  • 驱动校验映射范围;物理窗设置正确 cache 属性
  • munmap/文件释放/模块卸载无 UAF;vm_ops.close 对称
  • 截断、权限不足、缺页失败路径有明确信号/返回值
  • 性能场景对比 read vs mmap,避免无依据的「全面 mmap」

合并自:Linux系统编程/chapters/085--090-内存映射(2026-09-06)

相关推荐
律宏阔1 天前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔1 天前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴1 天前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千1 天前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei371 天前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei371 天前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei371 天前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh1 天前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW1 天前
制造业高端化,为什么必须重估Linux和数据库?
linux
百万蹄蹄向前冲1 天前
双端同步!云服务器装最新Node.js v26.10全过程追踪
服务器·人工智能·node.js