Skip to content

第七十一章:内存映射

元婴后期

涉及内核源码:

林小源站在内景的边缘,望着远处一片他从未涉足的区域。

那片区域和他熟悉的 VMA 大陆完全不同。VMA 大陆是柔和的蓝白色,像冰原;而那片区域是刺目的赤红色,表面流淌着不规则的光纹,像某种熔岩在翻涌。空气中弥漫着一股焦灼的气息——不是热,是某种"不耐烦"。

"那是什么地方?"他问。

mm_struct 没有立刻回答。过了几息,它才开口,声音比平时更谨慎:"设备内存。"

"设备?"

"显卡、网卡、磁盘控制器——那些外设的寄存器和内存。"mm_struct 说,"它们也有自己的物理地址,但不在我的管辖范围内。"

林小源走近了几步。赤红色的区域表面有无数条细线向外延伸,像触手一样试图抓住什么。他注意到每条细线的末端都连着一个小小的光点——那些光点在闪烁,像在传递信号。

"它们想连到哪里?"

"你的进程空间。"mm_struct 的声音带上了一丝凝重,"设备的寄存器通常映射到物理地址空间的高端——比如 0xFE000000 这样的地址。驱动程序需要把这些物理地址映射到进程的虚拟地址空间,用户态程序才能访问设备。"

林小源看到一条赤红色的细线正朝他脚下的 VMA 大陆延伸。细线碰到大陆边缘时,发出一声低沉的嗡鸣,像金属撞击。

"那条线是什么?"

" 系统调用。"mm_struct 说,"用户态程序通过 mmap 请求映射设备内存。驱动程序在内核态调用 ,把物理页帧号映射到虚拟地址。"

林小源蹲下来,看着那条细线慢慢嵌入地面。地面裂开一道缝,缝隙里透出赤红色的光。他能感觉到那道光的"温度"——不是物理意义上的热,而是一种异质的、不属于普通内存的气息。

"这安全吗?"他问。

mm_struct 沉默了一瞬。"不完全安全。设备内存不在伙伴系统的管理范围内,不能被交换到 swap,没有 page cache。一旦映射了,就直通硬件。"它的声音压低了,"你踩上去的每一步,都是在和设备直接对话。"

林小源缩回了脚。赤红色的光从缝隙中溢出来,在地面上蔓延了一小片,然后慢慢凝固。

他在内景中找了一处高台,俯瞰那片赤红色的区域。

从高处看,设备内存的布局更加清晰。那些赤红色的区域散落在物理地址空间的最顶端,像一座座孤岛。每一座孤岛都连着一条细细的线——有些连向内核空间,有些连向用户态的 VMA 大陆。

" 。"一个陌生的声音从旁边传来。

林小源转头,看到一个半透明的结构体悬浮在空中。它比 mm_struct 小得多,形状像一块不规则的金属块,表面刻着密密麻麻的地址。

"你是谁?"

" 。"金属块的声音干脆利落,"你脚下的每一块 VMA 都是我。但今天我不是来介绍自己的——我是来告诉你,设备内存和普通内存的映射方式不一样。"

"怎么不一样?"

"普通内存的映射是双向的——虚拟地址指向物理页,物理页也在伙伴系统的管理下。" vm_area_struct 说,"但设备内存不一样。 把物理地址映射到内核虚拟地址空间,驱动程序通过返回的虚拟地址访问设备寄存器。这不是'分配'——这是'直连'。"

林小源看到内核空间里有一道光桥从赤红色的孤岛延伸出来,直接连入内核的数据结构。光桥上流动着数据——不是普通的读写,而是某种更原始的信号。

" 返回的地址," vm_area_struct 继续说,"不能解引用为普通指针。你需要用 这样的专用函数来访问。因为设备寄存器的访问特性可能和普通内存不同——可能是 MMIO,可能是端口映射,可能是某种你没见过的协议。"

"映射是访问的前提。"林小源喃喃道。

"没错。" vm_area_struct 说,"没有映射,你连设备的门都摸不到。"

林小源在高台上站了很久,看着那些赤红色的孤岛。

他注意到一个细节——有些孤岛的表面在有规律地闪烁,像心跳。每一次闪烁,都有一小批数据从光桥上传过来,又被送回去。

"那是什么?"

"MMIO。" mm_struct 的声音从远处传来,"Memory-Mapped I/O。设备寄存器被映射到内存地址空间,CPU 像访问内存一样访问设备——但实际上是在和设备通信。"

林小源跳下高台,走向最近的一条光桥。他小心翼翼地把脚踩上去——光桥比他想象的要稳固,但脚底传来一种奇怪的"脉搏感",像踩在某种活物的皮肤上。

"你感觉到了?" mm_struct 说,"那是设备的时钟信号。每一次脉搏,设备都在等待 CPU 的指令。"

林小源蹲下来,把耳朵贴近光桥。他听到了——不是声音,而是一种节奏。嗒、嗒、嗒——像远处的鼓点。

"CPU 写入一个寄存器," mm_struct 说,"设备读取指令,执行操作,把结果写回另一个寄存器。CPU 轮询读取,直到拿到结果。整个过程——就像两个人隔着一堵墙敲摩尔斯电码。"

"墙?"

"总线。" mm_struct 说,"CPU 和设备之间的通信要经过总线。总线有自己的协议、时序、带宽限制。你以为自己在'访问内存',实际上每一步都是在走一套严格的通信流程。"

林小源站起来,看着远处那些赤红色的孤岛。它们不再像之前那样刺目了——他开始理解,那些光纹不是混乱的,而是有规律的。每一次闪烁、每一条细线、每一道脉搏,都在传递着有意义的信息。

"内存和 I/O 的边界是模糊的——他喃喃道,自己也说不清是感慨还是困惑。"

"不。" mm_struct 纠正他,"边界是清晰的——只是你以前看不到。设备内存是另一个世界,有自己的规则。你要学会区分:哪些地址指向内存,哪些地址指向设备。搞混了——"它停顿了一下,"搞混了,你写入的数据可能变成显卡上的一条乱码,或者网卡发出的一个畸形包。"

林小源打了个寒颤。他回头看了一眼那片赤红色的区域,然后转身走向自己的 VMA 大陆。

设备内存是一扇门——门后面是硬件的世界。


c
/*
 * 设备内存映射:
 *
 * 1. mmap() 系统调用
 *    用户态程序通过 mmap() 映射设备内存
 *
 * 2. remap_pfn_range()
 *    内核函数,把物理页帧号映射到虚拟地址
 *    用于设备驱动的 mmap 实现
 *
 * 3. ioremap()
 *    内核函数,把物理地址映射到内核虚拟地址
 *    用于驱动程序访问设备寄存器
 *
 * 设备内存的特点:
 *   - 不在伙伴系统的管理范围内
 *   - 通常映射到物理地址空间的高端
 *   - 不能被交换到 swap
 *   - 访问特性可能与普通内存不同
 */

struct vm_area_struct {
    unsigned long vm_start;
    unsigned long vm_end;
    unsigned long vm_pgoff;
    unsigned long vm_flags;
};

int remap_pfn_range(struct vm_area_struct *vma,
                    unsigned long addr,
                    unsigned long pfn,
                    unsigned long size) {
    printf("  [remap_pfn_range] VA 0x%lx → PFN 0x%lx (size %lu)\n",
           addr, pfn, size);
    return 0;
}

printf("=== 设备内存映射 ===\n\n");

/* 设备寄存器的物理地址 */
unsigned long device_phys = 0xFE000000;
unsigned long device_size = 4096;

printf("设备寄存器:\n");
printf("  物理地址: 0x%lx\n", device_phys);
printf("  大小: %lu 字节\n\n", device_size);

/* 用户态 mmap */
struct vm_area_struct vma = {
    .vm_start = 0x7F000000,
    .vm_end   = 0x7F001000,
    .vm_pgoff = 0,
    .vm_flags = 0x03,  /* READ | WRITE */
};

printf("用户态 mmap:\n");
printf("  虚拟地址: 0x%lx - 0x%lx\n", vma.vm_start, vma.vm_end);

/* 驱动的 mmap 实现 */
printf("  驱动调用 remap_pfn_range():\n");
unsigned long pfn = device_phys >> 12;
remap_pfn_range(&vma, vma.vm_start, pfn, device_size);

printf("\n--- ioremap ---\n");
printf("内核驱动使用 ioremap() 访问设备寄存器:\n");
printf("  void *regs = ioremap(0xFE000000, 4096);\n");
printf("  writel(0x01, regs + REG_CONTROL);\n");
printf("  unsigned int status = readl(regs + REG_STATUS);\n");
printf("  iounmap(regs);\n\n");

printf("--- 设备内存 vs 普通内存 ---\n");
printf("普通内存:\n");
printf("  - 由伙伴系统管理\n");
printf("  - 可以被交换到 swap\n");
printf("  - 有 page cache\n");
printf("  - 访问特性正常\n\n");
printf("设备内存:\n");
printf("  - 不在伙伴系统管理范围内\n");
printf("  - 不能被交换到 swap\n");
printf("  - 没有 page cache\n");
printf("  - 访问特性可能不同(如 MMIO)\n");

道藏笔记

内核启示

设备内存映射是驱动程序访问设备的核心机制。

设备内存映射的方式:

  1. — 用户态程序映射设备内存
  2. — 驱动的 mmap 实现
  3. — 内核驱动访问设备寄存器

设备内存的特点:

  • 不在伙伴系统管理范围内
  • 不能被交换到 swap
  • 没有 page cache
  • 访问特性可能不同(MMIO)

MMIO(Memory-Mapped I/O):

  • 设备寄存器映射到内存地址空间
  • CPU 像访问内存一样访问设备
  • 是现代设备驱动的基础

设备内存映射让"设备"变成了"内存"——CPU 可以直接访问。


破关试炼

内存映射之试

本章梳理内存映射时,管理进程虚拟地址空间的总账结构是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道