关注了就能看到更多这么棒的文章哦~
Jonathan Corbet
Gemini translation
原文链接:https://lwn.net/Articles/1059201/
内核中那个虽不受宠但对性能至关重要的交换(swapping)子系统,近期正在经历多轮改进。最近的文章介绍了 引入交换表(swap table) 作为表示交换缓存(swap cache)状态的新方法,以及 移除交换映射(swap map) 这一追踪交换空间的方式。然而,该领域的工作尚未完成;由 Nhat Pham 提交的 这一系列补丁 旨在通过使用单一的虚拟交换空间(virtual swap space)替换新的交换表结构,来解决若干与交换相关的问题。
交换条目(swap entries)的问题
提醒一下,“交换条目(swap entry)”标识了交换设备上一个可用于存放数据页的插槽。它是一个 64 位的值,分为两个字段:设备索引(在代码中称为“类型” type )和设备内的偏移量。当一个匿名页(anonymous page)被换出到交换设备时,相关的交换条目会被存储到所有引用该页的页表条目(page-table entries,PTE)中。通过该条目,内核可以在需要将该页重新换入内存(RAM)时快速定位。
“交换表”实际上是一组表,系统中的每个交换设备各对应一个。转向交换表的设计大大简化了内核,但目前交换条目和交换表的设计将换出的页面与特定设备紧紧绑定在了一起。这给系统管理员和设计者带来了一些麻烦。
举一个简单的例子:移除交换设备。显而易见,在设备移除之前,存储在该设备上的所有数据页必须先换入内存;这是无法回避的。但还有一个额外的问题:一旦设备消失,指向这些交换插槽的页表条目将失效。为了解决这个问题,内核在移除设备时必须扫描系统中所有的匿名页表条目,并将它们更新到页面的新位置。这绝不是一个快速的过程。
正如 Pham 所描述的,这种设计也给 zswap 子系统 的用户带来了麻烦。Zswap 的工作原理是在换出过程中截获页面,并不将其写入磁盘,而是进行压缩并将结果存回内存。它与交换子系统的其他部分集成良好,是扩展系统内存容量的有效手段。当内存空间填满时,zswap 能够将页面推送到后端(backing)存储设备上。
问题在于,无论页面是仍在 zswap 中还是已被推送到较慢的存储中,内核都必须能够快速将其换回。为此,zswap 隐藏在后端设备的索引之后;无论页面是在内存中还是在后端设备上,都使用相同的交换条目。然而,为了让这个技巧奏效,在页面首次放入 zswap 时,就必须在后端设备中分配好插槽。因此,每次使用 zswap 都必须占用后端设备的空间,即便初衷是永远不在磁盘上存储页面。这导致了大量存储空间的浪费,并使得 zswap 在那些没有多余空间可供浪费的系统上变得难以甚至无法使用。
虚拟交换空间
Pham 提出的解决方案——正如该领域常见的做法——是增加另一个间接层(indirection)。这意味着将每个设备的交换表替换为独立于底层设备的单一交换表。当一个页面被添加到交换缓存时,会从该表中为其分配一个条目;此时交换条目的类型仅仅是一个整数偏移量。该表本身是一个 swp_desc 结构体数组:
struct swp_desc { union { swp_slot_t slot; struct zswap_entry * zswap_entry; }; union { struct folio * swap_cache; void * shadow; }; unsigned int swap_count; unsigned short memcgid:16; bool in_swapcache:1; enum swap_type type:2; };第一个联合体(union)告诉系统在哪里可以找到换出的页面;它要么指向特定设备的交换插槽,要么指向 zswap 缓存中的条目。这是虚拟交换插槽与真实位置之间的映射。第二个联合体包含页面在内存中的位置(更准确地说是它的 folio ),或者是内存管理子系统用于追踪页面换入速度的影子(shadow)信息。 swap_count 字段追踪有多少页表条目引用了该交换插槽,而当页面被分配到该插槽时会设置 in_swapcache 。管理此分配的控制组(control group,cgroup)(如果有的话)记录在 memcgid 中。
type 字段告诉内核该交换插槽当前代表哪种类型的映射。如果是 VSWAP_SWAPFILE ,则虚拟插槽映射到交换设备上的物理插槽(由 slot 字段标识)。如果变为 VSWAP_ZERO ,它代表一个被填满零且无需存储在任何地方的换出页面。 VSWAP_ZSWAP 标识 zswap 子系统中的一个插槽(由 zswap_entry 指向),而 VSWAP_FOLIO 则用于当前驻留在内存中的页面(由 swap_cache 指示)。
这种安排的最大优势在于页面可以轻松地从一个交换设备移动到另一个设备。例如,zswap 页面可以被推送到存储设备,而需要改变的仅仅是 swp_desc 结构中的两个字段。在决定推送页面之前,无需分配存储设备上的插槽;如果某个页面从未被推送出去,它就根本不需要存储设备上的插槽。如果移除一个交换设备,虽然需要修改一堆 swp_desc 条目,但无需扫描页表,因为虚拟交换插槽保持不变。
成本体现在内存占用的增加和复杂性的提高。原先交换表为每个交换条目占用一个 64 位字; swp_desc 结构体的大小则是其三倍。Pham 指出,增加的内存开销比看起来要小,因为该结构体持有了一些在当前内核中存储在别处的信息。尽管如此,在一个旨在腾出内存供其他用途的子系统中,这仍然是内存使用的显著增加。代码在各种基准测试中也显示出性能退化,尽管与之前版本的补丁集相比已有很大改善。
不过,虽然这项工作的价值显而易见,但目前尚不清楚它是否能达到合并(merge)的标准。承担了前几篇文章中大部分交换相关工作的 Kairui Song 对内存开销以及系统在压力下的表现 表达了担忧 。Chris Li 同样 担心开销问题 ,并表示该系列过于关注 zswap 的改进,却以牺牲其他交换方法为代价。因此,这项工作很可能需要经过多轮进一步开发,才能达到被更广泛接受的程度。
后记:分层交换(swap tiers)
还有一个独立的补丁集,看起来与虚拟交换空间的实现完全无关,但两者可能结合得很好:来自 Youngjun Park 的 分层交换(swap tiers)补丁集 。简而言之,该系列允许管理员将多个交换设备配置为不同的层级;高性能设备进入一层,较慢的设备进入另一层。当空间充足时,内核将优先交换到较快的层级。该方案提供了一组控制组钩子,允许管理员控制任何给定的进程组可以使用哪些层级,从而让对延迟敏感(或付费更高)的工作负载获得对快速交换设备的独占访问权。
虚拟交换表显然会是对这种安排的有力补充。Zswap 本身已经可以看作是分层交换的一个特例;Park 的基础设施将使这一概念更加通用。页面在不同层级之间的移动将变得相对容易,允许将冷数据推向较慢的存储。因此,如果这两套补丁都能继续推进,最终看到这一补丁系列与虚拟交换空间以某种方式绑定在一起也就不足为奇了。
总的来说,内核的交换子系统最近受到的关注比过去多年都要多。人们显然有兴趣在提高交换性能和灵活性、同时使代码在长期内更具可维护性方面做出努力。开发者们不敢涉足内存管理子系统这一部分的日志似乎已经一去不复返了。
LWN 评论概述:
讨论集中在 zswap 的实现细节、在后端设备上直接存储压缩页面的实际价值(例如 Btrfs 的做法)、tmpfs 与 zswap 的兼容性,以及在生产环境下动态移除交换设备的需求是否真的足以支撑如此大的架构变动。部分用户还讨论了 zram 与 zswap 的优劣对比。
全文完LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

22

被折叠的 条评论
为什么被折叠?



