你可以把一台物理服务器想象成一间合租大屋:几十个租客(虚拟机)各自有房间,房间里的桌椅书架都是内存页。管理员发现,每个房间都放着一本一模一样的《新人手册》,与其买几十本占地方,不如只留一本放在公共书架上,谁要看就去翻——这就是内存去重。另一个场景是,有的房间空荡荡,浪费了不少空间;管理员拿出一个大号气球塞进房间,把没人用的空位占住,再把省下的空间借给隔壁房间;等屋主需要时,放掉气球把空间还回去——这就是内存气球。两种机制一静一动、一省一挪,让有限的物理内存在大量虚拟机之间高效流转。

岗位关联

对 AI 从业者而言,理解 KSM 与 Ballooning,意味着你能看懂云平台“内存超售”背后的底层逻辑,也是排查虚拟机性能抖动、设计高密度资源池的基本功。

目录
  1. 可视化演示
  2. KSM去重原理
  3. stable树机制
  4. 写时复制回退
  5. 气球驱动原理
  6. 宿主调度协作
  7. 开销与权衡
  8. 应用场景优化
  9. KSM与气球协同
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

KSM去重原理

假设你住在一间合租大屋里,每位室友床头都放着一模一样的《新人手册》。管理员巡视一圈后发现,与其买十本占地方,不如只留一本放到公共书架上,大家要看随时来翻。在 Linux 内核里,KSM(Kernel Samepage Merging)做的正是这件事:它把一个进程或虚拟机使用的匿名内存页当作“书页”,周期性扫描,凡是内容完全相同的页,就合并成同一个物理页,然后在多个虚拟地址映射到这份唯一副本。被合并的页是只读的,后续的写操作会走写时复制。

从成本角度看,合并省下的内存等于重复页数乘以单页大小。比如重复了 8 页,每页 4KB,就省下 32KB。现实中虚拟机的内核代码段、共享库、空白页到处都是“同款书”,KSM 能在宿主机上省出 GB 级内存。这也是云平台敢做超售的底气之一。

回到合租屋的比喻:管理员把十本《新人手册》收掉九本,省下的书架空间,正好就是“重复书本数 × 每本书厚度”。KSM 的收益底层逻辑也是如此直白。

Nsaved=Nduplicate×Spage\displaystyle N_{saved} = N_{duplicate} \times S_{page}
公式把“去重省下的内存”拆成两件事:到底有多少页重复,以及每一页有多大。
  1. 重复页数 N_duplicateKSM 扫描到的、与其他页内容完全相同的匿名页数量。
    NduplicateN_{duplicate}
  2. 单页大小 S_page每个内存页固定的容量,x86_64 上通常为 4KB。
    SpageS_{page}
  3. 节省内存 N_saved相同页合并后物理内存只剩一份,省下的字节数等于重复页数乘页大小。
    NsavedN_{saved}

stable树机制

公共书架上的书越来越多,管理员不能每收一本都跟所有书逐字对比,否则会累死。于是,他为每一本已经稳定的书编了一份目录,按内容的特征快速定位。KSM 把合并后的稳定共享页挂在一棵红黑树(stable 树)上,先用内容哈希值作为“目录编号”找到候选节点,再做完整字节比较确认,避免哈希碰撞导致误合并。

红黑树是平衡二叉搜索树,每次查找最多花费 O(log n) 步,这里的 n 指 stable 树中共享页节点的数量。页表改指向后,这些节点会长期存在,成为后续扫描的参照物。换句话说,stable 树既是“目录”,也是“已合并书页的集合”。

对照生活经验:图书馆的目录卡片不是按书名逐页翻出来的,而是按字母排序后折半查找。stable 树就是这本“会自动平衡的目录”,让管理员在成千上万本书中也能一眼定位。

Tsearch=O(log⁡n)\displaystyle T_{search} = O(\log n)
有了 stable 树,KSM 查找匹配页不需要遍历所有页,时间复杂度是对数级别的。
  1. 查找时间 T_search一次 KSM 扫描中,从 stable 树定位匹配页所需的比较次数。
    TsearchT_{search}
  2. 节点数量 nstable 树中已合并的不同内容共享页数量。
    nn
  3. 对数复杂度 O(log n)红黑树保持平衡,搜索路径长度与树高同级,即节点数量的对数。
    O(log⁡n)O(\log n)

写时复制回退

公共书架上唯一一本《新人手册》不能让大家随便写,只能看。如果某位室友想在书上画重点,管理员会先复印一本给他,再让他在复印件上写;其他人仍看原版。KSM 的合并页也是这样:物理页只读,页表项去掉写权限。

当某个进程真的写共享页时,CPU 触发写保护异常,内核分配一个新物理页,把原共享页内容复制过去,并让发起写入的进程页表指向新页,随后重新以可写方式执行;其他进程仍指向原共享页。这个机制叫写时复制(Copy-on-Write,COW)。发生写回退的页越多,说明这个共享页的“稳定性”越差,KSM 的收益也就越打折扣。

回到合租屋:如果大家只是翻书,一本公共书足够;如果每个人都抢着在书上写心得,管理员就得不停复印,最终反而浪费纸。COW 的价值,就是让“看的人多”和“写的人少”这两个前提成立。

Nprivate=Nshared×rwrite\displaystyle N_{private} = N_{shared} \times r_{write}
被拆成私有页的数量,取决于共享页数和写入频率之间的乘积。
  1. 共享页数 N_shared当前被多个进程映射的 KSM 合并页数量。
    NsharedN_{shared}
  2. 写回退比例 r_write在一段观察期内,共享页被写入并触发 COW 的概率或比例。
    rwriter_{write}
  3. 私有页数 N_private因写操作从共享页拆出来的新物理页数量。
    NprivateN_{private}

气球驱动原理

管理员还有一个大号气球。他会把气球塞进某个房间,在房间里充气,占掉屋主暂时用不到的空地;这些被占的地方并不是偷来的,而是屋主暂时不需要的闲置区域。等屋主需要时,管理员再放气,把空间完整还回去。Ballooning 的原理与之几乎一样。

Guest 虚拟机内部有一个气球驱动模块,通过虚拟设备与宿主机通信。当 Host 内存紧张时,Host 请求充气:Guest 中的气球驱动申请并锁定一批内存页,使 Guest 可用内存减少,同时这些页被释放给 Host 复用。当 Guest 内存不足时,Host 请求放气:气球驱动释放锁定的页,归还给 Guest。气球当前占用的净内存,就是累计充气量减去累计放气量。

这和合租屋完全一致:管理员不会搬走你正在用的床和桌子,只会把角落里常年不用的空箱子收走,记一笔“暂借账”。等你说“我要用箱子了”,他立刻放气归还。

Mballoon=Minflate−Mdeflate\displaystyle M_{balloon} = M_{inflate} - M_{deflate}
气球不是一个“真实物体”,而是一笔动态借还账:充进去的减去放出来的,才是当前从 Guest 借走的内存。
  1. 累计充气量 M_inflate气球驱动从 Guest 回收并锁定的内存总量。
    MinflateM_{inflate}
  2. 累计放气量 M_deflate气球驱动释放并归还给 Guest 的内存总量。
    MdeflateM_{deflate}
  3. 气球净占用 M_balloon当前 Guest 暂时让出给 Host 的内存大小。
    MballoonM_{balloon}

宿主调度协作

房东管家会盯着整栋楼的水压表:如果某几间房同时水龙头大开,压力就不够用了。他会到闲置较多的房间充气球,把空间“借调”给紧缺房间;等紧缺缓解或对方要用水,再放气还回去。宿主机上的内存调度也是如此。

宿主机监控全局内存水位,当可用内存低于阈值,便向 Guest 发送充气请求,引导 Guest 归还空闲页;多个 Guest 之间可以错峰借还。Ballooning 最大的价值是让动态内存分配不必走“关机、拔内存条、重启”的老路,也不需要热插拔虚拟内存条。配合超售策略,宿主机可以在不重启虚拟机的情况下,把一台 Guest 的空闲内存调度给另一台更需要的 Guest。

这个机制帮宿主“削峰填谷”:有的 VM 内存闲着,有的 VM 内存吃紧,气球就像管道一样把空闲内存导过去。当然,管道不能超过物理总量,否则水压问题依然会爆发为 OOM。

Mhostfree=Mhostbase+Mballoon\displaystyle M_{host}^{free} = M_{host}^{base} + M_{balloon}
宿主可用内存,等于原本的基础空闲内存,加上从 Guest 气球里借来的内存。
  1. 基础空闲内存 M_host_base未借助气球时,宿主机自身空闲的物理内存。
    MhostbaseM_{host}^{base}
  2. 气球净借入 M_balloon所有 Guest 当前让给 Host 使用的内存总和。
    MballoonM_{balloon}
  3. 宿主可用内存 M_host_free经过调度后,宿主机实际可用于新负载的内存总量。
    MhostfreeM_{host}^{free}

开销与权衡

管理员来回翻书、搬书、复印都要花时间;如果大家整天在公共书架上涂改,管理员反而更累。KSM 并不是免费的:扫描内存页、计算哈希、更新 stable 树、修改页表都会消耗 CPU,页表竞争锁也可能拖慢内核路径。

更关键的是延迟敏感负载。数据库等应用的高频写入会让 KSM 合并的页反复触发 COW,产生缺页异常和内存复制,造成延迟抖动。因此,KSM 是否开启要看收益模型:重复内容多、写入少,收益远大于开销;反之,管理员就该“罢工”,通过 sysfs 将 /sys/kernel/mm/ksm/run 置为 0,关闭 KSM。

回到合租屋:如果公共书架上全是大家每天都要涂改的笔记,管理员不停复印反而把纸都浪费了。与其强行共享,不如让每个人各买各的书。延迟敏感负载正是“天天涂改的室友”,KSM 在他们身上常常得不偿失。

Coverhead=αNscan+βNmerge\displaystyle C_{overhead} = \alpha N_{scan} + \beta N_{merge}
总开销由两部分组成:扫描页面的成本,以及合并/写回退操作的成本。
  1. 总开销 C_overheadKSM 运行消耗的 CPU 时间与其他资源代价。
    CoverheadC_{overhead}
  2. 扫描页数 N_scanKSM 周期性扫描的内存页数量。
    NscanN_{scan}
  3. 合并事件数 N_merge包括成功合并和写回退在内的页操作次数。
    NmergeN_{merge}
  4. 系数 α、β两种操作各自的单位成本,表示扫描一次和合并一次需要付出多少 CPU 代价。
    α,β\alpha, \beta

应用场景优化

不是所有宿舍都适合放共享书架:有些房间全是白墙,有些堆满私人物品,管理员需要分类施策。KSM 适合大量相同镜像/操作系统的场景,例如桌面虚拟化、云主机池和容器集群,因为 Guest 的内核、共享库、空白页高度重复,去重收益非常大。

Ballooning 则更适合动态负载明显的场景,比如多租户超售环境。实际优化时要注意:透明大页(THP)与大页的引入会改变页粒度,KSM 通常只处理 4KB 匿名页;NUMA 拓扑下,跨节点访问共享页会引入远端内存延迟,需要结合 CPU 亲和性做取舍。最终是否开启去重,可以用“净节省”来评估。

运维同学可以定期读取 KSM 的统计节点,观察 pages_shared 和 full_scans,如果发现合并页少而 CPU 占用高,就应该果断关闭或缩小扫描范围。这就像管理员定期盘点:共享书架的借阅量太低,就该撤掉,把地方腾出来放别的东西。

Snet=Nduplicate×Spage−Coverhead\displaystyle S_{net} = N_{duplicate} \times S_{page} - C_{overhead}
去掉维护成本之后,剩下的才是真正落袋为安的内存收益。
  1. 毛收益 N_duplicate × S_page理论上合并重复页后省出的内存空间。
    Nduplicate×SpageN_{duplicate} \times S_{page}
  2. 运行开销 C_overheadKSM 扫描、树操作、页表更新等付出的 CPU 成本。
    CoverheadC_{overhead}
  3. 净节省 S_net把成本扣除后,系统实际获得的内存收益。
    SnetS_{net}

KSM与气球协同

管理员的最优操作是先整理书架,再吹气球:先把重复的书合并成一本,空出书架;再把房间里真正没人用的空箱子收进气球,借给隔壁。KSM 和 Ballooning 协同,正是“一减一挪”。KSM 先合并内容相同的页,使每个 Guest 实际占用的物理内存下降;Ballooning 再把 Guest 的空闲页回收给 Host,放进统一的内存池。

两者结合时要注意顺序:最好先让 KSM 稳定合并重复页,再让 Ballooning 回收那些没有被映射的空闲页。如果先放气/回收了共享页映射,可能触发 COW,导致重复页重新出现,反而增大内存压力。实践中,KSM 减少了 Guest 的工作集,Ballooning 的充气压力也会变小;Ballooning 释放出的内存,又能支撑更多新 Guest。两种机制互相成就。

合租大屋的两位管理员终于配合默契:一位负责把重复的书变成公共书,一位负责把闲置的空地变成可调度的气球。物理内存还是那么多,但每一块都被安排得明明白白。

Mtotal=Mksm+Mballoon\displaystyle M_{total} = M_{ksm} + M_{balloon}
内存总收益 = KSM 去重省下的内存 + 气球借出的内存。
  1. KSM 节省 M_ksm通过合并相同内容页所释放的物理内存。
    MksmM_{ksm}
  2. 气球节省 M_balloon气球从各 Guest 手中借出、供 Host 统一调度的内存。
    MballoonM_{balloon}
  3. 总收益 M_total去重与借还叠加后,宿主机额外可用的内存空间。
    MtotalM_{total}

AI 应用场景

虚拟化平台内存超售

阶段:资源池构建与调度

解决的问题:传统方式下,虚拟机数量受物理内存限制,内存利用率低。KSM 将同镜像虚拟机的重复页合并,Ballooning 再回收各 Guest 的空闲页,让超售比例安全提升。

具体案例:某云平台部署 100 台相同 CentOS 虚拟机,开启 KSM 后,共享库和内核页大量合并,宿主空闲内存增加约 30%;再配合 Ballooning,将低负载 VM 的内存临时调度给高峰业务,虚拟机密度提升近一倍。

延迟敏感数据库保护

阶段:性能保障与调优

解决的问题:数据库内存页频繁写入,KSM 的写时复制会造成抖动;传统做法只能关闭 KSM,但浪费了重复页收益。通过内存 cgroup 隔离、NUMA 亲和性设置以及限制 Ballooning 收页速率,可以兼顾内存利用率与尾延迟。

具体案例:金融云数据库实例通过将关键 VM 标记为“不可合并”,保留 KSM 用于其他普通 VM;同时设置气球充气上限,避免突发回收影响 SQL 查询延迟。

Serverless与容器运行时

阶段:运行时隔离与弹性伸缩

解决的问题:Serverless 平台中大量函数实例运行在轻量虚拟机里,重复运行时库很多。KSM 可以跨实例合并相同代码页,Ballooning 则负责在函数实例缩容后把内存还给宿主,降低冷启动带来的内存峰值。

具体案例:Kata Containers 安全容器场景下,相同基础镜像启动的 Pod 共享内核页,配合 Ballooning 在 Pod 销毁后快速回收内存,使单机 Pod 密度提升 40% 以上。

Python 演示

python52 行
import hashlibfrom collections import defaultdict PAGE_SIZE = 4096 class MemoryPage:    def __init__(self, data):        self.data = data        self.digest = hashlib.sha256(data).hexdigest() def ksm_merge(pages):    groups = defaultdict(list)    for idx, page in enumerate(pages):        groups[page.digest].append(idx)     owner_map = {}    merged = 0    for digest, indices in groups.items():        owner = indices[0]        for idx in indices[1:]:            owner_map[idx] = owner            merged += 1    freed = merged * PAGE_SIZE    return freed, merged, owner_map def balloon_reclaim(guest_free_mb, need_mb):    inflate = min(guest_free_mb, need_mb)    deflate = 0    return inflate, deflate def main():    pages = []    for i in range(100):        if i % 4 == 0:            data = b"same" * 1024        else:            data = bytes([i]) * 4096        pages.append(MemoryPage(data))     freed, merged, owner_map = ksm_merge(pages)    print("merged pages:", merged)    print("owner map sample:", dict(list(owner_map.items())[:3]))     inflate, deflate = balloon_reclaim(guest_free_mb=512, need_mb=300)    net_balloon = inflate - deflate    print("balloon net MB:", net_balloon)     total_mb = freed / (1024 * 1024) + net_balloon    print("total saved MB:", total_mb) if __name__ == "__main__":    main()
目的:用 Python 模拟 KSM 的“按内容哈希分组、相同页只留一份”逻辑,并模拟气球驱动从 Guest 空闲内存中回收内存的过程。
  1. groups[page.digest].append(idx)按页内容哈希分组,就像把内容相同的“书页”归到同一个书架。
  2. owner = indices[0]每个分组的第一页作为保留下的共享页,其余页都指向它。
  3. owner_map[idx] = owner记录重复页真正所有者的映射,模拟 KSM stable 树中的页表改指向。
  4. freed = merged * PAGE_SIZE重复页数乘页大小,就是去重省出的内存字节数。
  5. inflate = min(guest_free_mb, need_mb)气球最多只能拿走 Guest 的空闲量,不能强抢正在使用的页。
  6. total_mb = freed / (1024 * 1024) + net_balloon把 KSM 去重收益与气球借还收益合在一起,看到整体节省。

常见误区

误区KSM 能把所有相同内容的内存都合并,所以应该无条件开启,参数设得越激进越好,反正内存越多越划算。

正解KSM 只能处理可写的匿名页,且要经过扫描、stable 树匹配和页表更新;内核页、页缓存等不在合并范围内。更关键的是,合并后任何写入都要触发写时复制,频繁写入会让 CPU 开销暴涨。是否开启应看重复页收益与扫描成本之间的净差额。

为何常见:因为“相同页=省内存”太直观,初学者容易忽略扫描与 COW 带来的性能税。

误区气球充气时,宿主机就像强盗一样把 Guest 正在运行的内存页抢走,虚拟机随时会卡死或崩溃。

正解Ballooning 遵循协商机制:Guest 只把宣告为空闲的页交给气球驱动,正在被进程使用的页不会被强行夺走。如果 Guest 内存紧张,Host 会放气归还,甚至 Guest 可以拒绝继续充气。它更像管理员搬走空箱子,而不是抢你手里的手机。

为何常见:把“向 Guest 要内存”理解成“强行剥夺”,忽略了气球驱动是一个双向协商的虚拟设备。

误区只要同时开启去重和气球,宿主机内存就永远够用,超售多少都行,不会再出现内存耗尽。

正解KSM 和 Ballooning 只是提高内存利用率,并不会凭空增加物理内存总量。如果超售比例超过真实物理容量,又赶上所有虚拟机同时进入高峰期,宿主机依然会触发 swap 甚至 OOM Killer。两者是“省着花”和“拆东墙补西墙”,不是印钞机。

为何常见:把内存优化当成内存扩容,混淆了“利用率”和“容量”两个概念。

误区既然大家共享同一个物理页,那么某个进程写入后,其他共享进程也能直接看到新数据,性能和共享内存一样。

正解KSM 的共享页是只读的,任何写入都会先触发 COW:内核复制一个私有页,再让写者映射到新页面并写入。其他进程仍然指向原共享页,看到的还是旧内容。多进程之间不会互相污染,否则数据安全就完全失控了。

为何常见:把 KSM 的“只读共享”和传统 IPC 的“可写共享内存”混为一谈,忽略了页表权限这一层保护。

面试题

Q1. 下列哪类内存页最容易被KSM合并?

单选题 · easy

  • A.完全相同的匿名页← 正确答案
  • B.同一进程的栈页
  • C.页缓存中的文件页
  • D.互不相同的纹理页

答案:A。KSM 扫描的是匿名内存页,且要求两个页内容逐字节完全相同。就像管理员只把同款书合并,不同内容的书不能硬塞到公共书架。栈页通常频繁变化,文件页归页缓存管理,纹理页内容各异,所以 A 最合适。

Q2. KSM把相同页合并后,物理页的权限被标记为什么?

单选题 · easy

  • A.标记为只读页← 正确答案
  • B.标记为读写页
  • C.标记为只执行页
  • D.标记为全局页

答案:A。合并后的物理页被多个进程共享,必须只读,否则一个进程写入会污染其他进程。一旦有人要写,就触发写时复制,拆出一份私有页再写。

Q3. stable树在KSM中的主要作用是?

单选题 · mid

  • A.快速查找可合并的稳定页← 正确答案
  • B.管理虚拟CPU调度队列
  • C.跟踪磁盘块缓存的信息
  • D.维护进程地址空间链表

答案:A。stable 树保存已经合并的共享页,KSM 扫描候选页时通过哈希和树搜索快速判断能否合并。它不管理 CPU 调度、磁盘缓存或进程链表,所以选 A。

Q4. 下列哪些情况会增加KSM的CPU开销?

多选题 · mid

  • A.页内容频繁变化引起写回退← 正确答案
  • B.多个进程共享后反复触发COW← 正确答案
  • C.可合并页数量大导致树操作多← 正确答案
  • D.物理内存长期处于空闲状态

答案:ABC。A 和 B 都是写时复制回退,分配新页、复制内容、改页表都要 CPU;C 中大量可合并页让 stable 树查找和插入变频繁。D 空闲状态下 KSM 也要扫描,但不会大量合并和写回退,整体开销相对不显著,所以选 ABC。

Q5. 某共享页首次被进程写入时,写时复制回退的第一步是什么?

单选题 · hard

  • A.分配新页并复制原内容← 正确答案
  • B.将原物理页内容清空
  • C.立即撤销所有页的映射
  • D.向用户进程发送信号

答案:A。写共享只读页会触发缺页异常,内核先分配一个私有物理页,把原共享页内容复制过去,再更新页表并重试写指令。清空原页或撤销所有映射会让其他进程失去公共书,信号机制也不是 COW 的步骤。

Q6. Ballooning充气时,Guest会把哪些内存页交给Host?

单选题 · mid

  • A.自己标记为空闲的页← 正确答案
  • B.内核刚启动时的页
  • C.正在DMA传输中的页
  • D.所有只读属性的页

答案:A。气球驱动遵循协商流程:Guest 认为暂时不用的空闲页才会被锁定并移交给 Host。正在 DMA 或内核使用的页不会交出,只读属性也不代表空闲。

Q7. Host与Guest中的balloon驱动之间通常通过什么通信?

单选题 · mid

  • A.虚拟PCI设备通道← 正确答案
  • B.网络套接字消息
  • C.共享内存信标区
  • D.硬件中断线触发

答案:A。Balloon driver 在 Guest 内部以虚拟 PCI 设备的形式存在,Host 通过该设备发送充气/放气请求。网络套接字、共享内存和普通硬件中断都不是标准的 balloon 通信机制。

Q8. 以下关于KSM与Ballooning协同的说法,正确的是?

多选题 · hard

  • A.KSM去重可减少Guest实际页占用← 正确答案
  • B.Ballooning可回收空闲页给Host← 正确答案
  • C.写回退会使回收页彻底失效
  • D.二者都增加物理内存总量

答案:AB。KSM 把相同页合并成一份,Guest 实际占用下降;Ballooning 再回收空闲页,两者是“去重+借还”的配合。写回退只是把某个共享页拆成私有页,不会让整个回收机制失效;它们都不能增加物理内存总量。

Q9. 下列哪种场景更适合关闭KSM?

单选题 · easy

  • A.高频写入的延迟敏感数据库← 正确答案
  • B.大量相同虚拟机镜像的场景
  • C.只读共享库为主的容器
  • D.CPU核数较少的宿主主机

答案:A。数据库页频繁写入,KSM 合并后写回退频繁,省下的内存抵不上抖动代价。B 和 C 都是重复内容多、写少,适合开启 KSM;D 不直接决定是否关闭。

Q10. 当Host内存严重不足且Ballooning无法从Guest回收足够页时,可能发生什么?

多选题 · extreme

  • A.Guest可能触发回收与swap← 正确答案
  • B.Balloon强制锁定正在使用页
  • C.Host最终会调用OOM Killer← 正确答案
  • D.内存总线频率自动提升

答案:AC。Guest 收到充气请求后如果空闲页不够,会先做内部内存回收和 swap,这是“交不出书就先整理房间”。Balloon 不会强制抢正在使用的页;Host 若依然缺页,最终会触发 OOM Killer 终止进程。D 与内存总线无关。