Skip to content

内存映射

由于 CPU 访问内存必须经历 MMU,操作系统控制需要 MMU 将使用的内存页映射到一个真实的物理内页上。

直接映射

因为 MMU 的存在,操作系统也不可以直接访问物理内存,所以操作系统在访问物理内存的时候依然需要进行映射,由此形成内核地址空间。为了简单,操作系统内核态的虚拟地址直接使用线性映射即可。

线性映射的时候,操作系统的虚拟地址空间可能是分段组合的,每个分段只与物理地址空间有一个常数偏移量。

另外,由于物理空间本身是由硬件平台所决定的,而且物理地址空间中有很多的空洞,操作系统可以使用内核地址将分散的空间连缀起来,从而简化操作系统的内核空间内存管理。

虚拟内存映射

物理内存和虚拟内存之间存在映射关系:

  • 映射以一块连续的内存为单位(通常为 4096 Byte)
  • 一块虚拟内存对应一块真实的物理内存
  • 这个单位称为内存分页
  • 操作系统通过页表(多级数组数据结构)维护映射关系

操作系统需要将进程的虚拟内存空间中的内存页映射到一个真实的物理内页上。其具体操作就是修改和维护 task_struct 中的 mm_struct 字段,创建一个虚拟内存表项,并标记为不存在。当用户态调用 mmap 接口的时候进行映射操作。

在进程首次申请内存的时候,操作系统会先建立表项,申明虚拟内存页到真实物理页的映射关系,但是不会真正的去进行内存分配,只有当进程真正去访问某个虚拟页的时候,并触发缺页异常时,操作系统才会开始为这个页去分配真实的物理页。

共享内存

操作系统创建一段能够在不同的进程中访问的内存空间,并且允许不同的进程打开它。linux 系统往往会将共享内存放置在 /dev/shm 目录下,呈现为一个设备文件,例如 /dev/shm/my_shm,不同的进程如果可以直接访问这个设备,那么就可以打开它,并使用 mmap 操作文件描述符,从而映射同一块物理内存空间,从而实现进程间的高速、大规模数据交换。

自定义内存映射

c
#include <sys/mman.h>
/**
 * @param addr 可以是 NULL,由操作系统自行分配
 * @param fd 需要映射的文件
 * @param offset 偏移量
 */
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

mmap 函数将文件 fd 中的内容映射到当前进程的内存空间中 addr 位置处,大小为 length,通过 prot 参数控制内存访问权限,通过 flags 提供更多配置选项。