K06 — The Full Flow of mmap Memory Allocation: Demand Paging and Physical Page Allocation

Key Insight: When you request 1 GB of memory, the system does not actually give you 1 GB upfront. It only allocates when you actually use it.


Lazy Allocation in mmap

mmap only creates virtual address mappings. It does not immediately allocate physical pages. Physical pages are allocated on demand during actual access (Demand Paging).

Bash
mmap 的工作方式:

mmap(NULL, 1GB, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  → 创建 VMA:vm_start ~ vm_end = 1GB
  → VMA 标记为匿名私有映射
  → 不分配任何物理页
  → 返回起始地址(程序以为有了 1GB)

第一次访问(假设读 0x1000 字节):
  → 访问的页面不在内存(Page Fault)
  → 内核分配物理页
  → 填充全零
  → 建立页表映射
  → 继续执行

结果:
  - 如果程序只访问 4KB,实际只用了 4KB 物理页
  - 如果程序访问 1GB,全部 1GB 物理页才会被分配

Page Fault Handling Flow

Bash
当访问 mmap 区域时发生 Page Fault:

do_page_fault() → __do_page_fault():

Step 1:查找 VMA
  vma = find_vma(mm, addr);
  if (!vma || addr < vma->vm_start)
      → 非法地址,发送 SIGSEGV

Step 2:检查访问权限
  if (vma->vm_flags & VM_WRITE && !pte_writable)
      → COW 或写保护错误

Step 3:分配物理页(handle_pte_fault)

  if (!pte_present(pte)) {
      // 页不在内存,按类型分配

      if (vma->vm_file) {
          // 文件映射
          do_file_fault();
          // 从磁盘读取文件内容到页面
      } else {
          // 匿名映射(MAP_ANON)
          do_anonymous_page();
          // 分配全零页面
      }
  }

Bash
do_anonymous_page() 的详细流程:

1. 从 buddy system 分配物理页
   page = alloc_page(GFP_KERNEL | __GFP_ZERO);
   // __GFP_ZERO:页面清零

2. 设置 PTE
   set_pte(ptel, mk_pte(page, vma->vm_page_prot));
   // 设置读写权限

3. 更新 LRU
   lru_cache_add(page);
   // 把页面加入 LRU(可被回收)

4. 刷新 TLB
   flush_tlb_page(vma, addr);

5. 返回,继续执行指令

MAP_ANONYMOUS vs MAP_FILE

Bash
匿名映射(MAP_ANON)的处理:

mmap(NULL, size, PROT, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);

Page Fault:
  → do_anonymous_page()
  → 分配全零页面
  → PTE 指向该页面

特点:
  - 内容是全零(不来自文件)
  - 页面可以独立存在(不在 page cache 中)
  - fork 时走 COW(共享但只读)

---

文件映射(MAP_FILE)的处理:

mmap(NULL, size, PROT, MAP_PRIVATE, fd, 0);

Page Fault:
  → do_file_fault()
  → 从 inode 读取文件内容
  → 分配页面,从磁盘读取数据
  → PTE 指向该页面(file-backed)

特点:
  - 内容来自磁盘文件
  - 页面在 inode 的 address_space 中(radix tree)
  - 写时 COW(私有映射)

Page Cache Statistics in /proc/meminfo

Bash
$ cat /proc/meminfo

MemTotal:       32768176 kB     // 总物理内存
MemFree:        12345678 kB     // 完全空闲
MemAvailable:   28901234 kB     // 空闲 + 可回收(可用的)
Buffers:           123456 kB     // 块设备缓冲(Page Cache)
Cached:           5678901 kB    // Page Cache + SwapCache
SwapCached:        234567 kB     // 已读入内存的 swap 页(不需要再读)

具体含义:
  Buffers:原始块设备的缓存(metadata)
  Cached:文件系统 Page Cache(文件数据)
  SwapCached:已换入内存的 swap 页面(在 LRU 上,不需要重新读 swap)

Active(page)/Inactive(page):File-backed 页
Active(anon)/Inactive(anon):Anonymous 页

$ cat /proc/meminfo | grep -E "^(Anon|File|Cached|Swap):"
AnonPages:       12345678 kB     // 匿名页(堆/栈)
Mapped:           1234567 kB     // 文件映射的页面(mmap 的文件)
Shmem:            234567 kB      // 共享内存(tmpfs)

Summary

  • mmap Lazy Allocation: mmap only creates VMA; does not allocate physical pages; pages allocated on demand during page fault
  • do_page_fault: Find VMA → check permissions → allocate by type (anonymous or file)
  • do_anonymous_page: Allocate zero-filled page from buddy → set PTE → add to LRU → flush TLB
  • MAP_ANON: Zero-filled page, no disk I/O required
  • MAP_FILE: Reads file content from disk into the page
  • /proc/meminfo: Active/Inactive split by anonymous and file-backed, counted separately

Next (K07): When a user calls malloc, how does the kernel participate? brk, mmap, and glibc’s ptmalloc allocator. Why frequent malloc/free slows down.


Last modified: 2024年7月7日

Author

Comments

Write a Reply or Comment

Your email address will not be published.