LWN:把atomic kmap改成local kmap!

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

Atomic kmaps become local

By Jonathan Corbet
November 6, 2020
DeepL assisted translation
https://lwn.net/Articles/836144/

内核中的 kmap()接口在某种意义上是个挺奇怪的 API,它的存在意义,完全只是用来克服 32 位 CPU 的虚拟寻址限制的,但是它影响了整个内核中各处的代码,而且对 64 位机器还有副作用。。最近一次关于内核内部的 preemption(抢占)的处理的讨论中,暴露出来一些需要注意的问题,其中之一就是 kmap()API。现在,人们提出了一个名为 kmap_local() 的 API ,对其进行扩展,从而解决其中的一些问题。它标志着内核社区在把 32 位机器从优先支持等级移除出去的过程又走出了一步。

Why we have kmap()

32 位处理器当然都会使用 32 位的指针,这就限制了内存的寻址空间在 4GB 之内。这 4GB 地址空间被分给用户空间(user space)和内核(kernel)。在最常见的分配方式中,内核分到了 1GB,这个空间内存放着内核的代码和数据、memory-mapped I/O 区域,以及让内核访问物理内存的 "direct map"。这个 direct map 肯定不能覆盖太多的内存,为了照顾到内核的其他需求,那么这里能映射到的物理内存就只能是远远少于 1GB 了。

因此,任何拥有 1GB 以上物理内存的系统,其部分内存都是没有在 direct map 中映射的,需要其他方式来管理。位于可以 direct map 的范围之上的内存称为 "high memory";在很多系统中,大部分内存空间都是 high memory。用户空间可以直接使用 high memory,不会感到任何区别,但内核方面就比较复杂了。每当内核必须访问一个位于 high memory 的 page 时(例如,在把一个 page 交给用户空间之前要把它清零),它必须首先为这个页面创建一个临时映射。kmap()接口就是用来管理这些映射的。

kmap()函数本身,会将一个 page 映射到内核的地址空间,然后返回一个指针,接下来就可以用这个指针来访问 page 的内容了。不过,用这种方式创建的映射开销是很大的。它们会占用地址空间,而且关于这个映射关系的改动必须要传播给系统的所有 CPU,这个操作开销很大。如果一个映射需要持续使用比较长的时间,那么这项工作是必要的,但是内核中的大部分 high memory 映射都是短暂存在的,并且只在一个地方来使用。这种情况下,kmap()的开销中大部分都被浪费了。

因此,人们加入了 kmap_atomic()API 作为避免这种开销的方法。它也能做到将一个 high memory 的 page 映射到内核的地址空间中,但是有一些不同。它会从若干个 address slot 中挑一个进行映射,而且这个映射只在创建它的 CPU 上有效。这种设计意味着持有这种映射的代码必须在原子上下文中运行(因此叫 kmap_atomic())。如果这部分代码休眠了,或被移到另一个 CPU 上,就肯定会出现混乱或者数据损坏了。因此,只要某一段在内核空间中运行的代码创建了一个 atomic mapping,它就不能再被抢占或迁移,也不允许睡眠,直到所有的 atomic mapping 被释放。

在 64 位系统上,调用 kmap()和 kmap_atomic() 的话实际上不需要做上面这些改变映射的动作了。64 位的地址空间足以涵盖人们在真实世界中任何系统上安装的内存了(或者说是目前足够了),所以所有的物理内存都出现在 direct map 中。但是调用 kmap_atomic()的话,无论如何都会禁用抢占,这主要是作为一种调试手段:确保在保持 atomic mapping 时发生睡眠的代码会在 64 位系统上产生错误,这意味着这种 bug 更容易在真正在 32 位系统上导致大问题之前被报出来,尤其是 32 位的系统现在很多开发人员测试不到了。

禁用抢占对于实时开发者来说是一个显眼的警告信号,他们多年来一直努力确保在任何时候所有 CPU 都可以被更高优先级的任务抢占进来。内核中成百上千的 kmap_atomic()调用点,每个都会产生一小段不可抢占的部分,这可能会导致不必要的延迟。上次提出这个话题的时候,曾有过一个简短的讨论,即希望从内核中完全取消对 high memory 的支持。此举会简化很多代码,肯定会很受欢迎,但也会破坏对现有系统的支持,如果这些系统仍会使用新的内核的话。所以,high memory 支持目前还不能完全从内核中移除。

Shifting the cost

因此,开发者不得不为这个问题找到一个次优的解决方案;这个解决方案很可能是 Thomas Gleixner 的 kmap_local() patch set。它提供了一组类似 kmap_atomic() 的新函数,但不需要禁用抢占。新函数是

void *kmap_local_page(struct page *page);
void *kmap_local_page_prot(struct page *page, pgprot_t prot);
void *kmap_local_pfn(unsigned long pfn);
void kunmap_local(void *addr);

前两个函数的输入参数就是一个指向与目标 page 所对应的 struct page 的指针,并会返回 page 映射上来之后的地址。第二个函数还额外允许调用者指定对这个映射上来的 page 要采用什么保护方式。如果调用者有一个 page-frame number,没有 struct page,那么可以使用 kmap_local_pfn()。无论 mapping 是用什么 API 如何创建的,都要用 kunmap_local()销毁。

这些 mapping 内部与用 kmap_atomic() 实现相同。但这个 patch set 对 kmap_atomic()的实现方式进行了重大修改。在当前的内核中,每个架构都有自己的实现,但几乎所有的代码都是一样的。Gleixner 清理了这种重复撰写的代码,并将这些统一成一个单一的、跨架构的实现。因此,这个 patch set 在增加新功能的时候,删除了 600 多行代码。

在这个公用实现完成之后,用于管理这些短期映射的 slot 的就会发生变化。在当前的内核中,这些 slot 被存储在一个 per-CPU 数据结构中,因此它们会被运行在同一个 CPU 上的所有线程共享。这也是为什么在持有 atomic mapping 时不能允许抢占的原因之一。正在运行的进程和抢占它的进程可能都会试图使用相同的 slot,结果一般都会导致出现各种错误了。在新的方案中,mapping 被存储在 task_struct 结构中,因此它们对每个线程来说是唯一的。

不过,(在 32 位系统上)用来做 mapping 的那个 page-table entry 可不能是 per-thread,所以在这种情况下必须做更多的动作才能安全地启用抢占。在上下文切换时,新的代码会查看退出或进入的这个 task 是否有仍然有效的 local mapping。如果有的话,退出的任务的 mapping 就会被移除掉,而进入的任务的映射则会被重新建立起来。这个动作会让上下文切换的速度变慢一些,但是,正如 Gleixner 所指出的那样:"显然这里会慢,但无论如何,访问 high memory 都是慢的".

local page mapping 仍然只会在 local CPU 上被建立起来,这意味着持有这种映射的进程如果要做迁移的话肯定是自找麻烦。因此,虽然当内核代码建立 local mapping 时抢占仍然是允许的,但从一个 CPU 到另一个 CPU 的迁移动作是被禁止的。值得注意的是,当前的内核并没有这样禁用迁移的机制,迄今为止这个功能仅限于 realtime kernel。Peter Zijlstra 一直在为一般情况下的迁移禁用实现添加支持,但尚未合并。这显然是 kmap_local()能够用起来的先决条件。

一旦一切就绪之后,kmap_atomic()和 kmap_local()之间唯一的区别将是持有 mapping 时的执行上下文了。atomic mapping 仍然禁用抢占,而 local mapping 只禁用迁移。除此以外,这两种 mapping 效果是相同的。这就引出了一个显而易见的问题:为什么不直接把所有地方都换成 kmap_local()呢?这的确是长期目标,但有一个小问题:一些 kmap_atomic()的调用点非常依赖于必须要关闭抢占,甚至也许开发者甚至没有意识到这一点。因此,数百个调用位置中的每一个都需要逐一进行核对和转换。

这项工作预计需要一段时间,但最终会有一天,kmap_atomic()不再被使用,从而可以从内核中移除。新的 API 保留了 32 位系统的功能,但它将部分成本转移到了这类系统上,而不是现在占主导地位的 64 位系统。这并不是取消对 high memory 的支持,而是表明使用 high memory 的系统越来越被视为一种小众场景,不会永远都能得到支持了。

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值