地址空间 = 带访问权限的内存段

操作系统应该提供一个修改进程地址空间的系统调用

// 映射
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);

// 修改映射权限
int mprotect(void *addr, size_t length, int prot);

本质:在状态机状态上增加/删除/修改一段可访问的内存

  • mmap: 可以用来申请内存 (MAP_ANONYMOUS),也可以把文件 “搬到” 进程地址空间中

把文件映射到进程地址空间?

它们的确好像没有什么区别

  • 文件 = 字节序列 (操作系统中的对象)
  • 内存 = 字节序列
  • 操作系统允许映射好像挺合理的……
    • 带来了很大的方便
    • ELF loader 用 mmap 非常容易实现
      • 解析出要加载哪部分到内存,直接 mmap 就完了
      • 我们的 loader 的确是这么做的 (strace)

使用 mmap

Example 1: 申请大量内存空间

  • 瞬间完成内存分配
    • mmap/munmap 为 malloc/free 提供了机制
    • libc 的大 malloc 会直接调用一次 mmap 实现
  • 不妨 strace/gdb 看一下

Example 2: Everything is a file

  • 映射大文件、只访问其中的一小部分
with open('/dev/sda', 'rb') as fp:
    mm = mmap.mmap(fp.fileno(),
                   prot=mmap.PROT_READ, length=128 << 30)
    hexdump.hexdump(mm[:512])

Memory-Mapped File: 一致性

但我们好像带来了一些问题……

  • 如果把页面映射到文件
    • 修改什么时候生效?
      • 立即生效:那会造成巨大量的磁盘 I/O
      • unmap (进程终止) 时生效:好像又太迟了……
    • 若干个映射到同一个文件的进程?
      • 共享一份内存?
      • 各自有本地的副本?

请查阅手册,看看操作系统是如何规定这些操作的行为的

  • 例如阅读 msync (2)
  • 这才是操作系统真正的复杂性