B09 — What Happens When Memory Runs Low: kswapd and Page Frame Reclaim

Key Insight: When memory runs tight, the system quietly moves infrequently used data to disk. This is swapping.


Sources of Memory Pressure

Linux always starts tidying up when memory approaches full. It swaps out infrequently used pages to disk, making room for new allocations.

Bash
什么时候会触发内存回收:

1. 水位线(Watermark)检测:
   - 内存有三个水位:pages_high, pages_min, pages_low
   - 当可用内存 < pages_low,开始回收
   - 当 < pages_min,强制回收(Direct Reclaim)

2. 分配请求失败(GFP_ATOMIC):
   - 分配请求在 fast path 失败
   - 触发 direct reclaim(同步回收)
   - 进程会阻塞等待

3. 周期性回收(kswapd):
   - kswapd 是内核线程,持续运行
   - 当内存低于 pages_high 时开始
   - 异步回收,慢慢把内存降到 pages_high 以上

watermark 定义(/proc/sys/vm/):
  watermark_scale_factor:默认 10(即 pages_high = 最低 + 总内存/10)
  min_free_kbytes:最小保留内存(用于紧急情况)

kswapd: The Background Memory Reclaim Daemon

Bash
kswapd 的工作方式:

kswapd 是 Linux 的页面回收线程:
  - 每个 NUMA 节点一个 kswapd(kswapd0, kswapd1, ...)
  - 处于休眠状态(TASK_INTERRUPTIBLE)
  - 被唤醒的条件:可用内存 < pages_high

kswapd 的回收算法:

  while (需要回收) {
      // 1. 尝试回收冷页面(最近未被访问)
      //    遍历 LRU(Least Recently Used)链表
      //    页面引用计数 = 1(只有 page cache 持有)→ 可以回收

      // 2. 如果不够,扫描更老的页面
      //    pageout():写回脏页(到磁盘),释放干净页

      // 3. 如果仍然不够,触发 OOM Killer
      //    选择最"坏"的进程杀掉,释放其页面
  }

回收的优先级(从低到高):
  1. anonymous cold pages(匿名冷页:进程的堆/栈)
  2. file-backed cold pages(文件冷页:mmap 的文件)
  3. file-backed dirty pages(脏文件页:需要先写回磁盘)
  4. anonymous hot pages(匿名热页:不太容易回收)

LRU (Least Recently Used) Linked List

Bash
Linux 用 LRU 链表来追踪页面的访问热度:

struct zone {
    // 活动页面链表(Active)
    struct list_head active_list;     // 最近被访问的页面
    unsigned long nr_active;          // 活动页面数量

    // 非活动页面链表(Inactive)
    struct list_head inactive_list;   // 最近未被访问的页面
    unsigned long nr_inactive;        // 非活动页面数量

    // 链表按类型分类:
    // file-backed vs anonymous
    // referenced vs unreferenced
};

LRU 链表的工作流程:

1. 页面访问时:
   page_referenced() → 如果被访问,加入/保持在 active_list
   如果未访问,在 active_list 中时间够长,移到 inactive_list

2. 回收时(kswapd):
   优先从 inactive_list 取页面(更容易回收)
   检查 referenced 位 → 如果没人引用 → 回收
   如果有引用(被进程持有)→ 不能回收

3. anonymous vs file-backed 的优先级:
   anonymous:需要 swapout(写磁盘),成本高
   file-backed:干净页直接回收,脏页写回后回收
   → file-backed 通常比 anonymous 先回收

Page Frame Reclaim Algorithm(PFRA)

Bash
PFRA(页面框回收算法)的详细流程:

Step 1:检查内存水位
  if (free_pages > pages_high) {
      // 内存充足,休眠
      schedule();
      return;
  }

Step 2:开始扫描(scan)
  for (each page in LRU) {
      // 检查 page 的类型和状态

      if (PageAnon(page) && !PageSwapBacked(page)) {
          // anonymous page,需要 swapout
          // 写页到 swap 设备
          // 释放物理页
      }
      else if (PageDirty(page)) {
          // 脏文件页,需要写回
          // 调用 filemap_write() 写盘
          // 写完后释放
      }
      else {
          // 干净的文件页
          // 直接释放回 buddy system
      }

      if (sufficient_memory_reclaimed()) break;
  }

Bash
shrink_page_list() 的实现:

int shrink_page_list(struct list_head *page_list, ...)
{
    LIST_HEAD(ret_pages);
    int nr_reclaimed = 0;

    list_for_each_entry(page, page_list, lru) {
        // 尝试锁定页面(避免并发访问)
        if (!trylock_page(page))
            continue;

        // 检查引用计数
        // page_count(page) = 1 表示只有 page cache 持有
        if (page_count(page) == 1 && !PageDirty(page)) {
            // 干净且无引用 → 直接释放
            unlock_page(page);
            free_page(page);
            nr_reclaimed++;
            continue;
        }

        // 如果是 anonymous,需要写 swap
        if (PageAnon(page)) {
            if (!PageSwapBacked(page)) {
                // 首次回收 anonymous page
                // 添加到 swap
                add_to_swap(page);
            }
            // 写页到 swap
            swap_writepage(page);
        }

        // 如果是脏文件页
        if (PageDirty(page)) {
            // 写回磁盘
            writepage(page);
        }

        unlock_page(page);
    }

    return nr_reclaimed;
}

swappiness: Anonymous vs File-Backed Page Reclaim Preference

Bash
swappiness 参数(/proc/sys/vm/swappiness):

swappiness = 60(默认值,0~100)

含义:
  - 100:优先回收 anonymous pages(匿名页优先)
  - 0:尽量不回收 anonymous pages(只回收文件页)
  - 6060% 的扫描时间用于 anonymous,40% 用于 file-backed

实际效果:
  swappiness 高 → anonymous 更容易被 swapout
  swappiness 低 → anonymous 保留更久,文件页先被回收

不同场景的选择:
  - 服务器(文件缓存为主):swappiness = 10~30
  - 桌面(匿名内存多):swappiness = 60~100
  - 数据库(大量 anonymous heap):swappiness = 10(避免 swap)

swappiness 调优:
  $ sysctl vm.swappiness=10

  或者在 /etc/sysctl.conf 中:
  vm.swappiness = 10

OOM Killer: When Reclaim Is Not Enough

Bash
当 kswapd 和 direct reclaim 都无法释放足够的内存时,
Linux 会调用 OOM Killer(Out of Memory Killer):

触发条件:
  - 可用内存 < pages_min(强制回收水位)
  - direct reclaim 失败
  - 分配请求无法满足(GFP_NOFS/GFP_NOIO 等)

选择进程的策略(oom_score):

1. 内存消耗(RSS):
   - 内存消耗越多,越容易被选中杀掉

2. 存活时间(oom_score_adj):
   - 动态调整:recently oom 的进程降低 score
   - /proc/PID/oom_score_adj 可以手动调整(-10001000)
   - -1000 = 完全免疫,1000 = 更容易被杀

3. 其他因素:
   - 进程优先级(低优先级更容易)
   - 进程是 root 还是普通用户
   - 进程是否是系统服务(systemd, kernel threads)

OOM Killer 的执行:
  - 选择最"坏"的进程
  - 发送 SIGKILL(无条件杀掉)
  - /var/log/messages 记录(dmesg | grep -i "out of memory"

Bash
观察 OOM Killer:

$ dmesg | grep -i "out of memory"
[  123.456] Out of memory: Killed process 1234 (my-program)
            total-vm:2048576kB, anon-rss:1048576kB, file-rss:0kB

$ cat /proc/PID/status | grep -i vm
VmPeak:  1048576 kB
VmSize:  1048576 kB
VmRSS:   512000 kB
VmSwap:  256000 kB

Summary

  • Memory Reclaim Trigger: Watermark detection (pages_high/low/min); allocation failure triggers direct reclaim
  • kswapd: Kernel thread; asynchronous reclamation; starts working when memory < pages_high
  • LRU Lists: active/inactive; classified by anonymous/file-backed, clean/dirty, referenced/unreferenced
  • PFRA Flow: Check page type → anonymous to swap, dirty file to disk, clean file directly freed
  • swappiness: 0-100; prefers anonymous (100) or file-backed (0); lower for servers
  • OOM Killer: When kswapd is insufficient, kills the worst process (highest oom_score); sends SIGKILL

Next (B10): How are pages copied during fork? Copy-On-Write mechanism. Why fork is fast, and why memory modification triggers actual copying.


Last modified: 2024年3月16日

Author

Comments

Write a Reply or Comment

Your email address will not be published.