https://lwn.net/Articles/732952/ 在4.13即将released的最后时刻,新引入了一个内存管理的重要修改,用于修复MMU notifier带来的regression.

内存管理子系统之前做了大量的工作用于正确的配置MMU,使得内核其他部分和用户态都不需要关心MMU管理的细节。但是随后的一些变化使得MMU概念有些不一样,最开始是虚拟化引入用于处理gust内存到host映射的shadow page table,最近也有其他一些设备开始有自己内存映射,类似GPGPU。在内存子系统执行修改之后,这些CPU之外的MMU也需要做出相应的更新。

为此Andrea Arcangeli在2008年2.6.27的合并窗口里面加入了MMU notifier机制,使得其他子系统能够在内存管理子系统中加入hook,当一个进程的页表发生修改时会执行hook的callback。

/当页表中address对应页的页表项被移除,同时该页还存在时,下面这个callback会被调用/ void (*invalidate_page)(struct mmu_notifier *mn, struct mm_struct *mm, unsigned long address);

/下面这个callback用于在start和end对应地址范围内存页的映射还存在时通知其他MMU移除这些内存页的映射/ void (*invalidate_range_start)(struct mmu_notifier *mn, struct mm_struct *mm, unsigned long start, unsigned long end);

/当start和end对应地址范围内存页的映射已经都移除时,下面这个callback用于通知其他MMU做些清理工作/ void (*invalidate_range_end)(struct mmu_notifier *mn, struct mm_struct *mm, unsigned long start, unsigned long end);

/当start和end对应地址范围内存页的映射摘除时,下面这个callback会被触发/ void (*invalidate_range)(struct mmu_notifier *mn, struct mm_struct *mm, unsigned long start, unsigned long end);

总共增加了有4个hook点,其中invalidate_range可以在invalidate_range_start和invalidate_range_end之间调用,也可以单独调用。其中invalidate_page是允许睡眠的,而invalidate_range不允许。

8月份Adam Borowski反馈当使用KVM时4.13-rc内核会触发host系统挂,同时也有其他一些反馈包括一个crash,通过排查发现该问题是由于之前为了修复持有spinlock情况下调用invalidate_page问题而引入的patch导致。最终的修复看起来并不太容易。invalidate_page允许睡眠这个从根本上就会导致各种race,Torvalds为此抱怨MMU notifier机制,并指出这些接口不应该允许睡眠才对。

但实际上如果不允许睡眠,类似支持GPU这种就没法实现。为了清除GPU的页表,需要将命令发送到GPU命令队列,并等待GPU通知页表/tlb以及cache已经清理完毕。

Torvalds最终还是做出来一点让步,并对两种情况做了区分,对虚拟地址和mm_struct相关的处理允许睡眠,同时对页表和页相关处理不允许睡眠,因此用于处理虚拟地址并且不会在持有spinlock情况下调用的invalidate_range_start()和invalidate_range_end()是允许睡眠的,invalidate_page和invalidate_range不允许睡眠。同时Torvalds指出invalidate_page从根本上就是设计错误,经过讨论,最好的处理办法就是彻底移除该函数。

Glisse快速的实现了上述想法,并解决了4.13-rc内核的问题,最终赶在4.13内核出release的倒数第三天合并进入内核。invalidate_page最后被彻底移除了,所有外部依赖该函数的模块都需要更新下,否则会失败。

4.13内核的MMU notifier还遗留有一个问题,oom里面用于回收杀死进程内存的代码里面并没有调用这些notifier。这会导致使用了notifier的系统在内存消耗完的时候出现问题。Michal Hocko正在着手修复,最终patch暂时还没有合并。