在最新的2.6.29内核中,对get_user_pages做了一个改善,主要就是颗粒问题,以前的实现中,在get_user_pages之后才可以处理诸如“本进程已经死亡”的信息,这样的话就做了很多无用功,这在古老的系统问题不大,但是现在都是大并发大负载的系统,任何处理都要很大的付出,因此在目前的情况下,必须改善一些情况。试想一种情况,如果一个进程p已经被oom_killer选中,oom_killer在一个cpu运行,而p在另外cpu运行,它们是并发的,那么p必然在返回用户空间时才可以得知自己被杀,那么处于内核的p如果要是要调用get_user_pages的话,那么将会为它分配很多它根本没有机会再用的页面,这些页面将瞬间被释放。为了不让这种情况发生,在调用get_user_pages的时候,在get_user_pages函数的最前面,先要判断当前进程的状态,然后再决定是否进行这一次的get_user_pages操作,于是就有了2.6.28中的函数有以下形式:

int __get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, ...)

{

...//以下的if语句在__get_user_pages每要获取一个页面的时候都要判断从而使得检查点比较细而不是在函数最开始做一次判断。

if (unlikely(test_tsk_thread_flag(tsk, TIF_MEMDIE))) //察看是否已经被杀,如果是的话就不再继续下去了,继续下去也是没有意义的。

return i ? i : -ENOMEM;

...

}

这样做的话,看起来已经很不错了,但是真的就完美了吗?还差得远,想象一下在何种情况下进程p会被杀死,那情况当然多了去了,不一定非要是oom_killer将进程p杀死,比如在多核心处理器中,随时都有可能出现在一个cpu上运行的进程杀死在另一个cpu的核心态运行的进程,而这个核心态的进程或许就要调用get_user_pages函数,于是这个问题在多核心处理器的系统上更加明显,试想一种真实的情况,那就是一个监测进程发现另一个进程占用了过多的内存,于是根据用户设置的策略,它要杀死这个占用内存过多的进程而不管这个进程处于什么状态,于是很有可能这个进程马上就要调用get_user_pages了,2.6.29对get_user_pages新的补丁可以捕捉到这种情况从而避免了做无用功,于是新的补丁如下:

int __get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, ...)

{

...//以下的if语句替换2.6.28中的判断TIF_MEMDIE标志的语句,不仅仅在oom时退出,只要是被kill了都不再继续

if (unlikely(!ignore_sigkill && fatal_signal_pending(current)))

return i ? i : -ERESTARTSYS;

...

}

fatal_signal_pending的意义很明显,就是被SIGKILL附体了,马上就命归黄泉了,那么ignore_sigkill是做什么的呢?我们看一下它的定义:

int ignore_sigkill = !!(flags & GUP_FLAGS_IGNORE_SIGKILL);

这个定义出现在__get_user_pages最开始的地方。这个ignore_sigkill到底是什么意思呢?从那个if判断可以看出在ignore_sigkill为真的情况下,即使有SIGKILL附体也无所谓,继续进行而不退出,这就使得在SIGKILL附体的情况下出现一个不退出函数的空子,那么这个空子在什么时候可以钻呢?我们想象一种情况,就是在进程正在解除页面锁定的情况下被杀死了,被SIGKILL附体的时候它已经进入了__mlock_vma_pages_range但是还没有进入__get_user_pages,如果这种情况下在__get_user_pages里面直接退出的话,将会导致一些页面无法解除锁定,其实如果这些页面仅仅归被杀进程所有,那么这无关紧要,毕竟是要死的人了,咋折腾都可原谅,并无大碍,可是如果是共享页面的话,这将是不希望的,因为可能仅此被杀进程需要锁定这些页面,比如一个实时进程需要锁定一些共享库,但是别的共享此库的进程并不需要锁定库页面,那么如果这个实时进程被杀而且没有来得及解除锁定那些库的页面,那么这些页面将永远常驻内存,这虽然没有什么大问题但是毕竟不是系统的本意。实际情况应该是,一个参与者在参与一件事时如果做了什么,那么它退出的时候一定要清理,使得现场和它没有来的时候一样。于是2.6.29就有了以下补丁:

static long __mlock_vma_pages_range(struct vm_area_struct *vma..., int mlock)

{

...

if (!mlock)

gup_flags |= GUP_FLAGS_IGNORE_VMA_PERMISSIONS | GUP_FLAGS_IGNORE_SIGKILL;

...

}

于是,添加了这些代码以后,get_user_pages就不会在SIGKILL附体的情形下耽搁过久的时间了,唯一的例外就是在unlock页面的时候会在SIGKILL的情况下也会放get_user_pages一马,让它做完善后工作。为何看起来,get_user_pages和这个补丁很“亲热”,因为在get_user_pages中可能会分配页面,而在此时分配页面或者曾经的分配页面操作中可能会oom,从而很有可能当前进程已经被SIGKILL附体,其实,只有在物理页面相关的机制操作中才可能进行oom_killer操作,进而使SIGKILL附于某个进程,很有可能就是正在分配物理页面的进程,而get_user_pages就是这样一个需要分配页面的函数。

加上这个补丁后不见得性能会有多少提高,但是可以保证的是,系统可以将一些做无用功的时间用来做正经的事情,系统不一定会有做无用功的情况,但是一旦有了,而且在get_user_pages中,那么这个补丁就有了意义。