本文要解决的是内核 api 设计在历史演进过程中的合理化问题。

大家都知道,内核的内存分配过程中,是要清楚的区分各种不同的情况的,例如:是否内存分配过程中会引起进程切换,大部分的驱动代码的中断处理函数中(面试中常问的问题,为什么中断上下文不能发生进程切换?)。所以,linux 内核的做法一直以来也就和我们大多数人在设计 api 的时候一样,即:加个 flag 吧,于是内核的内存分配函数变成下面的情况:

void *kmalloc(size_t size, gfp_t flags);

具体多少 flag, 看 <linux/gfp.h> 吧,处理内存分配中要区别处理的种种问题(这里面显然有些问题是 common 的,有些是 by design 的)

现在考虑下面三种情况:

  1. 当调用栈足够深的时候(内存分配 api 被多层封装):

Amalloc(…, flags) -> Bmalloc (…, flags) -> Cmalloc (…, flags) -> ………………. -> kmalloc(…, flags)

  1. 当调用在某个上下文频繁多次的时候:

kmalloc(…, flags) … kmalloc(…, flags) … kmalloc(…, flags) …

  1. 当底层调用需要修改 flags 的时候:

Amalloc (…, flags) -> kmalloc(…, new_flags) (比如:判断自己的上下文发生变化了)

我们看到了什么?很多重复的 flags 出现在连续的 api 调用里,还有就是上层 flags 未必能完全决定底层 flags, 显然功能都 ok 了,只是过了 n 年之后,现在的开发者觉得太 ugly 了。

解决方法?大概就是很直观的:

  1. 拿掉 API 里面的这些 flags (或者提供新的 api 来过渡) 2. 最底层分配函数处理的时候,通过访问某个全局的信息来拿到这个 flags 或者根据具体情况来调整这个 flags, 全部由底层调用决定

1 很简单了,2 这个全局的信息存哪呢,社区给出的部分做法是 task_struck 里面了,当然这个没问题,可以解决掉很多进程上下文的内存分配 flags 冗余问题(暂且就叫 flags 冗余吧),这应该是大头,因为在中断上下文中内存分配显然要简化很多,不过依然存在上述问题。

目前内核中给出了相应做法的例子:

PF_MEMALLOC_NOFS ==> task_struct -> flags

memalloc_nofs_save

… 这里面的内存分配,默认相当于增加 GFP_NOFS …

memalloc_nofs_restore

目前解决方法存在的问题:

  1. 我个人认为这也不是一个通用的方法,因为 flags 实在很多(就像我上面提到的有些是 common 的,有些是 by design 的),如果都这样单独处理难道不就是用另外一种复杂度替换之? 2. 历史问题太久远,很多已有的 callers (users) 依然需要支持,或者在很长一段时间内两种 api 要并存。 3. 并没有解决中断上下文的冗余问题

本文原作者提出了这样一个问题,也给出了一些解决方法,不过依然不完整,希望有兴趣的同学继续跟进内核社区在此处的进展,或者有更好的主意,也不妨自己改改。