🐧 第五章 内存管理
📄 一句话概括

物理帧装不下工作集时,缺页率立刻上来;但降到【每页第一次访问】就降不动了——那 8 次叫强制缺页,谁也免不了。 Swap 把【杀进程】换成【变慢】,但换多了就是抖动。

📝 本节和前面几节的关系

5.14 讲了页可以在进程之间共享,这一节讲页可以被挪到磁盘上再搬回来——这是虚拟内存最容易被误解的一个用途。

一、Swap 是什么

在被换出的页上,页表项的 P 位改成 0,原来的物理页被腾出来给别人用;内容写到磁盘上的交换区。 下次访问这一页:又触发缺页,内核从交换区读回来,填好页表。 所以 Swap 不是【扩大内存】,是【把内存里最不常用的部分暂存到磁盘】。 它的代价是【访问那一页从纳秒变成毫秒】——慢了六个数量级。

二、帧数决定缺页率

八个页、冷热不均地访问一千次:帧 1 缺页 807 次、帧 2 缺 593 次、帧 4 缺 289 次、帧 8 缺 8 次。 帧越少缺得越多,但降到 8 次就降不动了。 因为那 8 次是【每个页第一次访问】——叫强制缺页,谁也免不了。 能省的只有【重复访问】那一部分,而重复访问要靠帧装得下工作集。 所以【加大内存】能解决的是缺页率里【可省的那一部分】,不是全部。

三、有 Swap 和没 Swap

稍微超一点:有 Swap 就换出几页,活还能干完;没 Swap 就只能回收缓存。 超太多:没 Swap 就是 OOM Killer(快死);有 Swap 就是反复换入换出(慢死)。 所以【有 Swap 更安全】只在【轻度超额】时成立。重度超额时,有 Swap 往往更难受——磁盘一直转,什么请求都要等几秒。 这就是【抖动(thrashing)】:系统的全部时间都花在换页上,不干正事。

四、挑哪一页换出去

FIFO:换最早进来的——问题是最早的可能还在用。 LRU:换最久没访问的——听起来对,但要记每个页的访问时间,太贵。 时钟(clock):LRU 的近似——给每页一个【用过】标志,指针转一圈,遇到用过就清零、遇到没用过就换出去。 Linux 用的是近似 LRU:它不追求挑得最准,只求挑得快。 这又是那个熟悉的取舍:精确的代价往往比不精确的损失还大。

五、动手 Demo:Swap——内存不够时,把不用的页挪出去

这一节用一个 LRU 模拟器,把【物理帧数】当成变量,看缺页率怎么变,以及为什么它降不到零。

def lru(seq, frames):
    mem, faults = [], 0
    for p in seq:
        if p in mem:
            mem.remove(p)
            mem.append(p)
        else:
            faults += 1
            if len(mem) >= frames:
                mem.pop(0)
            mem.append(p)
    return faults

for f in (1, 2, 4, 8):
    print(f, "帧", lru(seq, f), "次缺页")

实跑输出:

--------------------------------------------------------------------
Swap——内存不够时,把不用的页挪出去
--------------------------------------------------------------------
① 1000 次访问,8 个页,冷热不均(前两个页占一半多)

  物理帧数     缺页次数     缺页率
  ---------------------------------
  1            807 次       80.7%
  2            593 次       59.3%
  4            289 次       28.9%
  8            8 次         0.8%

  **帧越少缺页越多,但降到 8 次就降不动了**——
  **因为那 8 次是【每个页第一次访问】,谁也免不了(叫强制缺页)。**
  **能省的只有【重复访问】那一部分,而重复访问要靠帧装得下工作集。**

② 有 Swap 和没 Swap,结局不一样

  情况                  内核怎么办                 用户感觉到什么
  ------------------------------------------------------------------
  有 Swap,稍微超了     换出最久没用的页           变慢,但活干完了
  没 Swap,稍微超了     只能回收缓存               磁盘 IO 变多
  没 Swap,超太多       OOM Killer                 进程直接没了
  有 Swap,超太多       换出太多,反复换入换出     **系统假死**

  **最后一行最坑**:**内存严重不够时,有 Swap 反而不如没有。**
  **因为它在【慢死】和【快死】之间选了慢死。**

③ 挑哪一页换出去

  算法              挑谁             问题
  ----------------------------------------------------------
  FIFO              最早进来的       可能换掉正在用的
  LRU               最久没用过的     要记访问时间,贵
  时钟(clock)     LRU 的近似       精确度差一点,便宜很多

  **Linux 用的是近似 LRU**——它不追求挑得最准,只求挑得快。

帧 1 缺 807 次、帧 8 缺 8 次——差两个数量级。 但帧 8 那 8 次是【每个页第一次访问】,再怎么加帧也降不到 0。 所以【内存不够】的判据不是【有没有缺页】,是【缺页率是不是一直很高】。 注意帧 4 就已经只有 28.9% 了——这说明【多给一点内存】的收益是递减的,不是线性的。 这也解释了【加到 32 GB 之后为什么没感觉】:因为可省的那部分早就省完了,剩下的全是强制缺页。

📋 Linux 上看有没有在换页:vmstat 1 的 si(换入)与 so(换出)两列——长期非零就是在换页,系统已经在拿磁盘当内存用。

30 秒实验

  1. vmstat 1 5,看 si 与 so 两列是不是 0。
  2. free -h,看 Swap 那一行的 used 是多少。
  3. cat /proc/sys/vm/swappiness,看内核有多愿意换出(默认 60,数据库常调低)。

常见误区

  1. 【Swap 是用来扩大内存的】。它是把不常用的页暂存到磁盘,访问那些页会慢六个数量级。
  2. 【有 Swap 就比没 Swap 安全】。只在轻度超额时成立;重度超额时抖动比 OOM 更难受。
  3. 【缺页率高就是内存不够】。还有强制缺页这一部分,加内存也降不掉。
自测三问(点开对答案) 1. Swap 换出一页时,页表项怎么变? —— **P 位改成 0**,物理页腾出来;**下次访问触发缺页,从交换区读回来。** 2. 为什么缺页率降不到零? —— **每个页第一次访问必然缺一次(强制缺页)**,这部分加内存也消不掉。 3. 什么情况下有 Swap 反而更糟? —— **内存严重不足时**:**反复换入换出(抖动)**,**系统全部时间花在换页上,用户感觉假死。**

相关笔记