从 FS/IO 上下文使用的 GFP 掩码

日期:

2018 年 5 月

作者:

Michal Hocko <mhocko@kernel.org>

介绍

文件系统和 IO 栈中的代码路径在分配内存时必须小心,以防止因直接内存回收回调到 FS 或 IO 路径并阻塞在已持有的资源(例如锁——最常见的是用于事务上下文的锁)上而导致的递归死锁。

避免此死锁问题的传统方法是在调用分配器时清除 gfp 掩码中的 __GFP_FS 和 __GFP_IO(请注意,后者也意味着清除前者)。GFP_NOFS 和 GFP_NOIO 可以用作快捷方式。然而,事实证明,上述方法导致了滥用,即在不深入考虑的情况下“以防万一”地使用受限的 gfp 掩码,这会引发问题,因为过度使用 GFP_NOFS/GFP_NOIO 可能导致内存过度回收或其他内存回收问题。

新 API

自 4.12 版本以来,我们对 NOFS 和 NOIO 上下文都提供了通用作用域 API:memalloc_nofs_savememalloc_nofs_restorememalloc_noio_savememalloc_noio_restore。这些 API 允许将某个作用域标记为文件系统或 I/O 视角的关键区。在该作用域内的任何分配都将固有地从给定掩码中移除 __GFP_FS 或 __GFP_IO,因此任何内存分配都不会递归回 FS/IO。

unsigned int memalloc_nofs_save(void)

标记隐式 GFP_NOFS 分配作用域。

参数

void

无参数

描述

此函数标记 GFP_NOFS 分配作用域的开始。所有后续分配都将隐式地移除 __GFP_FS 标志,因此从分配递归的角度来看,它们对 FS 关键区是安全的。使用 memalloc_nofs_restore 结束作用域,并传入此函数返回的标志。

上下文

此函数在任何上下文中使用都是安全的。

返回

要传递给 memalloc_nofs_restore 的已保存标志。

void memalloc_nofs_restore(unsigned int flags)

结束隐式 GFP_NOFS 作用域。

参数

unsigned int flags

要恢复的标志。

描述

结束由 memalloc_nofs_save 函数开始的隐式 GFP_NOFS 作用域。请务必确保给定的标志是配对的 memalloc_nofs_save 调用返回的值。

unsigned int memalloc_noio_save(void)

标记隐式 GFP_NOIO 分配作用域。

参数

void

无参数

描述

此函数标记 GFP_NOIO 分配作用域的开始。所有后续分配都将隐式地移除 __GFP_IO 标志,因此从分配递归的角度来看,它们对 IO 关键区是安全的。使用 memalloc_noio_restore 结束作用域,并传入此函数返回的标志。

上下文

此函数在任何上下文中使用都是安全的。

返回

要传递给 memalloc_noio_restore 的已保存标志。

void memalloc_noio_restore(unsigned int flags)

结束隐式 GFP_NOIO 作用域。

参数

unsigned int flags

要恢复的标志。

描述

结束由 memalloc_noio_save 函数开始的隐式 GFP_NOIO 作用域。请务必确保给定的标志是配对的 memalloc_noio_save 调用返回的值。

FS/IO 代码随后只需在任何与回收相关的关键区开始之前调用相应的 save 函数——例如,与回收上下文共享的锁,或者当事务上下文可能通过回收嵌套时。在关键区结束时应调用 restore 函数。所有这些最好伴随着对回收上下文的解释,以便于维护。

请注意,save/restore 函数的正确配对允许嵌套,因此从现有的 NOIO 或 NOFS 作用域中分别调用 memalloc_noio_savememalloc_noio_restore 是安全的。

__vmalloc(GFP_NOFS) 如何?

自 v5.17 版本,特别是在 commit 451769ebb7e79 (“mm/vmalloc: alloc GFP_NO{FS,IO} for vmalloc”) 之后,[k]vmalloc 现在通过隐式使用作用域 API 来支持 GFP_NOFS/GFP_NOIO。

在早期内核中,vmalloc 不支持 GFP_NOFS 语义,因为分配器内部深处有硬编码的 GFP_KERNEL 分配。这意味着使用 GFP_NOFS/GFP_NOIO 调用 vmalloc 几乎总是一个错误。

在理想情况下,上层应该已经标记了危险上下文,因此不需要特殊处理,vmalloc 应该可以毫无问题地被调用。有时,如果上下文不明确或存在分层违规,那么(在 v5.17 之前的内核上)推荐的方法是用作用域 API 包装 vmalloc,并附带解释问题的注释。