本文要解决的是内核 api 设计在历史演进过程中的合理化问题。
大家都知道,内核的内存分配过程中,是要清楚的区分各种不同的情况的,例如:是否内存分配过程中会引起进程切换,大部分的驱动代码的中断处理函数中(面试中常问的问题,为什么中断上下文不能发生进程切换?)。所以,linux 内核的做法一直以来也就和我们大多数人在设计 api 的时候一样,即:加个 flag 吧,于是å 核的内存分配函数变成下面的情况:
void *kmalloc(size_t size, gfp_t flags);
具体多少 flag, 看 <linux/gfp.h> 吧,处理内存分配中要区别处理的种种问题(这里面显然有些问题是 common 的,有些是 by design ç)
现在考虑下面三种情况:
- 当调用栈足够深的时候(内存分配 api 被多层封装):
Amalloc(…, flags) -> Bmalloc (…, flags) -> Cmalloc (…, flags) -> ………………. -> kmalloc(…, flags)
- 当调用在某个上下文频繁多次的时候:
kmalloc(…, flags) … kmalloc(…, flags) … kmalloc(…, flags) …
- 当底层调用需要修改 flags 的时候:
Amalloc (…, flags) -> kmalloc(…, new_flags) (比如:判断自己的上下文发生变化了ï¼
我们看到了什么?很多重复的 flags 出现在连续的 api 调用里,还有就是上层 flags 未必能完全决定底层 flags, 显然功能都 ok 了,只是过了 n 年之后,现在的开发者觉得太 ugly 了。
解决方法?大概就是很直观的:
- 拿掉 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
目前解决方法存在的问题:
- 我个人认为这也不是一个通用的方法,因为 flags 实在很多(就像我上面提到的有些是 common 的,有些是 by design ç),如果都这样单独处理难道不就是用另外一种复杂度替换之? 2. 历史问题太久远,很多已有的 callers (users) 依然需要支持,或者在很长一段时间内两种 api 要并存。 3. 并没有解决中断上下文的冗余问题
本文原作者提出了这样一个问题,也给出了一些解决方法,不过依然不完整,希望有兴趣的同学继续跟进内核社区在此处的进展,或者有更好的主意,也不妨自己改改。