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_structvm_operations_structdo_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 或已在 mmapremap_pfn_range 建好映射

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

4. 驱动里两种主流做法

  1. remap_pfn_range(或 vm_iomap_memory

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

  2. 延迟 fault

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

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

5. 与 read/write 如何选型

场景 更合适
小而偶发控制消息 read/write/ioctl
大块流式、零拷贝 mmapsplice/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>0offset 页对齐;失败时打印 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 小时前
linux网络:UDP原理
linux·网络·udp
Web极客码1 小时前
Pydantic 校验通过不等于答案正确:如何识别 LLM 的语义错误
服务器·人工智能·ai·llm
张洛闻Eren1 小时前
云原生k8s【第八课】: ETCD 备份与恢复
linux·运维·docker·kubernetes·k8s
新时代牛马1 小时前
Linux 守护进程与服务完整篇:从daemonize、systemd 到pidfile 与排障
linux·运维·服务器
毕竟是shy哥10 小时前
vs code连接远程服务器docker环境及配置免密登录
服务器·docker·vs code
徐小黑ACG11 小时前
nginx配置文件
linux·服务器·nginx
新时代牛马11 小时前
Linux VFS 完整篇:从path_openat、dentry/inode 到page cache 与挂载排障
linux·运维·服务器
java_logo11 小时前
Docker 部署 Rocky Linux:轻松搭建 RHEL 兼容企业级基础镜像平台
linux·docker·容器·rocky linux·基础镜像·轩辕镜像·rhel 兼容
拂拉氏12 小时前
【知识讲解】 Linux虚拟地址空间认识
linux·虚拟地址空间