从 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_save
、memalloc_nofs_restore
和 memalloc_noio_save
、memalloc_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_save
或 memalloc_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
,并附带解释问题的注释。