K05 — Page Cache: A Unified Cache for File Data and Anonymous Memory

Key Insight: Linux does not waste any memory. It turns all free memory into cache.


The Concept of Page Cache

Page Cache is Linux’s mechanism for caching file data and anonymous memory. It turns physical memory into a cache for disk blocks, accelerating file I/O and managing process anonymous memory.

Bash
Page Cache 的核心思想:

1. 所有磁盘 I/O 都经过 Page Cache
   - 读文件:先查 Page Cache,命中则直接返回
   - 写文件:先写入 Page Cache,定期写回磁盘

2. Page Cache 和 buddy system 的关系
   - Page Cache 的页面来自 buddy system
   - 释放 Page Cache 时,页面归还 buddy system

3. 两种页面类型:
   - file-backed page:映射到文件的页面
   - anonymous page:进程的堆/栈,没有文件对应

两者都缓存在 Page Cache 中,都受 LRU 管理。

Page Cache Flow for File Reads and Writes

Bash
读取文件时(read syscall):

1. 磁盘文件 → Page Cache → 用户 Buffer

   read(fd, buf, size):
   → VFS 接收请求
   → 查找 Page Cache(radix tree)

   Cache Hit:
     → 直接从 Page Cache 读取(无磁盘 I/O)
     → 耗时:~100ns(内存访问)

   Cache Miss:
     → 从 buddy system 分配页面
     → 发起磁盘 I/O(读写磁盘)
     → 数据写入 Page Cache
     → 返回给用户

2. 写文件时(write syscall):

   write(fd, buf, size):
   → 直接写入 Page Cache(不立即写磁盘)
   → 设置页面为 dirty
   → 标记 inode 为 dirty
   → 立即返回(快速写入)
   → 后台 pdflush/flush 线程写回磁盘(延迟写回)

   好处:写入比实际磁盘 I/O 快 100~1000

Radix Tree: Page Cache Index Structure

Bash
Page Cache 用 radix tree 快速查找:

struct address_space {
    struct radix_tree_root page_tree; // radix tree 根
    spinlock_t tree_lock;             // 保护锁
    unsigned long nrpages;            // 页面总数
    // ...
};

查找 key = (inode, offset):
  → 线性扫描太慢 → 用 radix tree
  → O(log N) 查找,N = 页面数量
  → 每个文件一个 radix tree

radix tree 的特点:
  - 稀疏数组(sparse array)
  - 只有存在的页面才占用节点
  - 节省内存(不需要预分配整个数组)
  - 查找:给定文件偏移,快速找到对应页面

radix_tree_insert():
  → 把 page 对象插入 tree
  → key = offset >> PAGE_SHIFT

radix_tree_lookup():
  → 给定 offset,查找 page
  → 返回 struct page *

Anonymous Pages and Page Cache LRU

Bash
匿名页面(anonymous pages)也走 LRU:

当物理内存紧张时,kswapd 回收页面:
  - 优先回收文件页(干净 → 直接释放,脏 → 写回)
  - 其次回收匿名页(需要 swapout 到磁盘)

LRU 链表结构:

struct zone {
    // 文件页链表
    struct list_head[NR_LRU_LISTS] fileInactive;
    struct list_head[NR_LRU_LISTS] fileActive;

    // 匿名页链表
    struct list_head[NR_LRU_LISTS] anonInactive;
    struct list_head[NR_LRU_LISTS] anonActive;
};

LRU_LIST 分类:
  - LRU_INACTIVE_ANON:匿名冷页(最近没访问)
  - LRU_ACTIVE_ANON:匿名热页(最近访问过)
  - LRU_INACTIVE_FILE:文件冷页
  - LRU_ACTIVE_FILE:文件热页

回收优先级(默认):
  1. file cold(最优先,因为直接释放)
  2. anon cold(需要 swapout)
  3. file hot(脏文件先写回)
  4. anon hot(最不愿意回收)

Page Cache Read-Ahead

Bash
Page Cache 的预读(readahead):

预读的原理:
  - 应用可能顺序访问文件
  - 内核预测下一步访问,提前把页面加载到 Page Cache
  - 减少磁盘 I/O 等待

预读触发(典型):
  1. 应用顺序读文件(read(fd, buf, size))
  2. Page Cache miss → 分配页面 → 从磁盘读
  3. 检测到顺序访问模式
  4. 预读 2~4 个额外页面(提前到 Page Cache)

查看预读效果:
  $ strace -e trace=read ./program 2>&1 | head -20

  read(3, "hello world...", 4096) = 4096
  → 每次 read 4KB

  $ cat /proc/self/maps
  7f... rw-p ... // 内存映射文件

  如果是 mmap,顺序访问会触发 readahead:
  → 内核自动把后续页面读入 Page Cache

Summary

  • Page Cache: Cache for all disk I/O; all file reads and writes go through it
  • Cache Hit/Miss: Hit returns directly (~100ns); Miss allocates page → disk I/O
  • radix tree: One radix tree per file; key = offset; O(log N) lookup
  • Anonymous Pages: Anonymous pages (heap/stack) also use LRU; reclamation requires swapout
  • LRU Classification: file cold > anon cold > file hot > anon hot (reclaim priority)
  • Readahead: On sequential access, kernel pre-loads subsequent pages into Page Cache

Next (K06): When a user program calls mmap, how does the kernel allocate physical pages step by step? Demand paging, page fault handling, and anonymous mapping allocation flow.


Last modified: 2024年5月10日

Author

Comments

Write a Reply or Comment

Your email address will not be published.