LWN: 冻结page引用计数!

关注了就能看到更多这么棒的文章哦~

Freezing out the page reference count

By Jonathan Corbet
December 6, 2024
Gemini-1.5-flash translation
https://lwn.net/Articles/1000654/

`page` 结构体是内核内存管理子系统的核心(目前是这样),其关键部分是存储在 `refcount` 中的引用计数。页引用计数告诉内核给定页有多少用户以及何时可以释放它。但是,并非系统中的每个页都需要此计数。Matthew Wilcox 最近 重新启用 了一个 旧补丁集,该补丁集扩展了“冻结”页的概念——一个不具备有意义的引用计数的页——这立即对 slab 分配器有利,但也服务于一个长期的目标。

内核负责管理在多个用户之间共享的资源。例如,匿名(数据)页和文件支持页都可以映射到一个或多个进程的地址空间中;每个映射都会增加相关页的引用计数,确保该页在需要时一直存在。引用计数可以是管理对象生命周期的一种相对有效的方法,但它们不是免费的。频繁的引用计数更改会导致缓存行抖动,并且更改引用计数所需的原子操作相对昂贵。因此,当有机会时,/不/使用引用计数是有价值的。

在 `struct page` 引用计数的情况下,它的使用已深深植根于内存管理子系统中,以至于它几乎维护系统中的所有页——即使对于不需要它的那些页也是如此。一个特别的例子是 slab 分配器,它分配一组页,将它们分割成更小的对象,并在请求时分发这些对象。调用 `kmalloc()` 是从 slab 分配器获取内存的最常见方法。由于它必须跟踪包含在一个页中的每个子对象的状体,因此 slab 分配器知道该页整体上是否正在使用;它不需要为此目的使用单独的引用计数。

即便如此,给予 slab 分配器的页仍然是引用计数的。维护该引用计数的开销似乎并不多,但它会累加起来,尤其是在大量使用 slab 分配器的负载下。鉴于为获得即使是很小的收益所付出的努力,消除这种可能代价高昂的原子操作本身似乎就是一个值得追求的目标。

冻结页

Wilcox 的补丁集扩展了“冻结”页的概念——一个其引用计数已被冻结(值为零)且未使用的页。在当前内核中,冻结页仅用于 hugetlbfs 子系统,该子系统维护一组巨大的页供应用程序使用。在这种情况下,冻结页用于在组装更大的页时避免操作引用计数,但该概念更有广泛的用途。

在当前内核中,页的引用计数使用 `set_page_refcounted()` 调用进行初始化,该调用将计数设置为 1。此初始化是在页分配路径的深处完成的;整个内核中只有三个调用点。补丁集的大部分内容,也许令人惊讶的是,致力于添加更多这样的调用点。简而言之,`set_page_refcounted()` 调用被推送到低级分配函数的调用者的调用堆栈中。此更改使存在根本不设置引用计数的分配路径成为可能。尽管如此,这似乎是一个适得其反的改变;`set_page_refcounted()` 变成了一个单赋值指令(初始引用不需要原子操作),并且在更改之前代码可以说是更简洁的。然而,这样做是有原因的,我们将会看到。

一旦这些调用都被推送到堆栈底部,就会添加一个新的分配函数(实际上是一个宏):

struct page *alloc_frozen_pages(gfp_t gpf_flags, unsigned int order);

除此之外,现有的 `free_unref_page()` 被重命名为 `free_frozen_pages()`。在后一个函数中可以找到更大的节省。释放页的正常函数(`__free_pages()` 或其调用者之一)通过递减传递给它的页的引用计数来工作;只有当计数变为零时,它才会实际释放页。`free_frozen_pages()` 可以避免对引用计数进行原子递减和测试操作,因为它知道没有其他对该页的引用。

完成这项工作后,最后一步是 一个小补丁,它导致 slab 分配器使用 `alloc_frozen_pages()` 和 `free_frozen_pages()` 而不是 `alloc_pages()` 和 `__free_pages()`。不必要的原子操作被消除,并且 slab 分配器可能因此变得更快——尽管没有包含基准测试结果来量化这一点。

光辉的未来

性能补丁通常应该包含基准测试,但是这里的性能改进实际上更多的是一种副作用;推动这项工作的真正目的是不同的。自从几年前 folio 转变 开始以来,最终目标之一就是减小 `struct page` 的大小。这个结构体已经尽可能小了,但是由于系统中每个页都有一个这样的结构体,`page` 结构体最终仍然占据了系统中大约 1.5% 的内存。回收一些开销用于生产性用途是一个有吸引力的前景。

长远目标是将 `struct page` 缩减为单个 64 位 内存描述符,它指示每个页的使用方式,并包含一个指向包含该使用方式所需信息的结构体的指针。例如,slab 页可以使用 `struct slab` 来描述;该结构体存在于当前内核中,但被精心设计为 `struct page` 之上的一个覆盖层。在未来的世界中,单个 `struct slab` 可以存在于整个 folio 的 slab 页中,从而减少这些页的内存管理开销;一个指向该结构体的指针将被放置到每个相关的内存描述符中。

在当前内核中,`struct slab` 作为 `struct page` 的覆盖层,包含 `refcount` 字段;即使 Wilcox 的补丁集被合并,这也将保持不变。但是,在未来 `struct slab` 不再需要覆盖 `struct page` 的情况下,可以移除该引用计数,从而缩小 `struct slab` 的大小。这确实是 Wilcox 所展望的未来:

这个补丁集也是朝着光辉的未来迈出的一步,在这个未来中,`struct page` 没有引用计数;需要引用计数的用户将在他们的每个分配内存描述符中拥有一个。 (This patchset is also a step towards the Glorious Future in which struct page doesn't have a refcount; the users which need a refcount will have one in their per-allocation memdesc.)

许多朝着“光辉的未来”迈出的步骤已经采取;一些特定类型的内存描述符(例如 `struct slab`)已经合并,其他一些正在进行中。一直以来都在努力将信息从 `struct page` 移动到 `struct folio` 中(在适当的情况下)。其他项目,例如 从 `struct page` 中移除 `index` 成员,也在进行中。这个成员用于页面缓存页,它告诉内核它在磁盘上表示的文件中的偏移量;它正在转移到 `folio` 结构体中。这项工作反过来取决于正在进行中的 zswap 内存描述符转换。还有许多其他步骤有待采取,但是在这项工作结束时,`struct page` 应该大部分已经消失了。

因此,虽然从 slab 分配器的页释放路径中移除单个原子操作并不那么令人兴奋,但这组补丁作为更大的内存描述符项目中的一部分,则更有趣。内核内存管理子系统的核心正在经历一个激进的(且非常必要的)变革时期,这将使其在长期内变得更高效、更灵活和更易于维护,并且大多数用户甚至都没有注意到。这项任务有点像在摩天大楼保持营业的同时更换其地基;然而,在内核社区中,这只是平常的开发。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

d47e95f9753c83e3c6c88754c19607d6.jpeg

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值