PF_MEMDIEのあつかいがRHEL4とRHEL5でかなり違っていることに気づいたのでメモ

まずRHEL4(kernel2.6.9ベース)

フラグが立つのは以下。
つまり、OOM killで死ぬ事が決定したタスクに立つ
static void __oom_kill_task(task_t *p)
{
(略)
p->time_slice = HZ;
p->flags |= PF_MEMALLOC | PF_MEMDIE; // ★

/* This process has hardware access, be more careful. */
if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO)) {
force_sig(SIGTERM, p);
} else {
force_sig(SIGKILL, p);
}



んで、使っているところその①
OOM killだと殺すのにSIGKILL使っているので、KILL送っても即死するとは限らない
んで、死刑確定プロセスに何回も死刑宣告しても意味がないし、
そもそも、死体に死刑宣告してメモリ空いた気になって次の処理に進んでも有害無益なので
OOM kill対象に選ばれるべきではない
static unsigned long badness(struct task_struct *p, unsigned long uptime)
{
unsigned long points, cpu_time, run_time, s;

if (!p->mm)
return 0;

if (p->flags & PF_MEMDIE) //★ここ
return 0;


使っているところその②
OOM killする処理の途中でメモリ不足でタスクが寝てしまって処理が進まなくなると
いつまでたってもメモリが空かないのでよろしくない。
よって、メモリ確保ルーチンでボーナスを与えている

struct page * fastcall
__alloc_pages(unsigned int gfp_mask, unsigned int order,
struct zonelist *zonelist)
{
  (略)
/* This allocation should allow future memory freeing. */
if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) { //★ここ
/* go through the zonelist yet again, ignoring mins */
for (i = 0; (z = zones[i]) != NULL; i++) {
page = buffered_rmqueue(z, order, gfp_mask);
if (page)
goto got_pg;
}
goto nopage;
}


audit関係は興味がないので、割愛


次にRHEL5(kernel2.6.18ベース)

まず、フラグの名前からして違う
PF_MEMDIEはTIF_MEMDIEに変わっており、かつtask_structのフラグからthread_infoのフラグに
変更されている。
(ぶっちゃけ、引っ越した理由が理解できず・・・)

フラグが立つのは以下。
同じく、OOM killで死ぬ事が決定したタスクに立つ
static void __oom_kill_task(struct task_struct *p, const char *message)
{
(略)
/*
* We give our sacrificial lamb high priority and access to
* all the memory it needs. That way it should be able to
* exit() and clear out its resources quickly...
*/
p->time_slice = HZ;
set_tsk_thread_flag(p, TIF_MEMDIE); //★ここ

force_sig(SIGKILL, p);
}



フラグを見ている場所その①
まず目立つのは、基本的にループ回ってる最中に1つでもTIF_MEMDIEを見つけたら
いきなり関数抜けてOOM kill処理をスキップしちゃうこと。
これで、メモリ不足時に
1.システムがスローダウンしていて、なかなかOOM killが終わらない(メモリが空かない)
2.次々とプロセスがOOM kill対象になり、SIGKILLを送られまくる
3.大量虐殺
というコンボを防いでいる

PF_DEADはtask_struct以外は全部解放終わり、あと1回schedule()されたら死ぬよん
という、ホンマに死ぬ直前なので、恩赦対象

PF_EXITINGはexit()やsignalで死に始めてからPF_DEADになるまでの終了途中状態。
なんだけども、なんでこの時にbreakしてるのかが意味が分からん。
ここはtaskとthreadの2重ループなので、breakしても自タスク内スレッドループ抜けるだけで
処理は続くが、*ppointsはULONG_MAX入れてるので、これ以上のポイントを持つタスクが
見つかるはずもない。
大体、PF_EXITINGまで行ったやつにもう一度SIGKILL送っても無視されるだけだと思うので
そんなヤツ選択してなんか意味あるかぁ??

明日また考えよう ← 絶対やらない
static struct task_struct *select_bad_process(unsigned long *ppoints)
{
(略)
releasing = test_tsk_thread_flag(p, TIF_MEMDIE) || //★ここ
p->flags & PF_EXITING;
if (releasing) {
/* PF_DEAD tasks have already released their mm */
if (p->flags & PF_DEAD)
continue;
if (p->flags & PF_EXITING && p == current) {
chosen = p; //★ 自殺
*ppoints = ULONG_MAX;
break;
}
return ERR_PTR(-1UL); //★ OOM killしない
}


フラグを見ている場所その②
主にCPU1個のマシン対策。out of memory発生し自分が死亡対象じゃない
(=どこか別プロセスが死刑宣告された)
というときは、sigkillの動作的に一旦死亡対象プロセスがスケジューリングされないと
死に終わらない。
なので、ちょっとだけ寝てあげるとこの関数を抜けるまでに死に終わっている確率が上がり
無駄に再度のOOMを発生させてしまう事を防いでいる
void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask, int order)
{
(略)
/*
* Give "p" a good chance of killing itself before we
* retry to allocate memory unless "p" is current
*/
if (!test_thread_flag(TIF_MEMDIE))
schedule_timeout_uninterruptible(1);
}



使っている場所その③
RHEL4とおなじく、メモリ確保時のボーナス

struct page * fastcall
__alloc_pages(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist)
{
  (略)
/* This allocation should allow future memory freeing. */

if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))
&& !in_interrupt()) {
if (!(gfp_mask & __GFP_NOMEMALLOC)) {
nofail_alloc:
/* go through the zonelist yet again, ignoring mins */
page = get_page_from_freelist(gfp_mask, order,
zonelist, ALLOC_NO_WATERMARKS);
if (page)
goto got_pg;
if (gfp_mask & __GFP_NOFAIL) {
blk_congestion_wait(WRITE, HZ/50);
goto nofail_alloc;
}
}
goto nopage;
}



追記: このへん(http://www.ussg.iu.edu/hypermail/linux/kernel/0501.2/1542.html)にPF_MEMDIEがTIF_MEMDIEになった経緯が書いてあった。
ようするにtask_struct.flagsはふつうのulongでatomic操作できないんだから、自タスク以外が操作するフラグはTIF_XXにしないとダメってこと。

追記2: このへん(http://www.ussg.iu.edu/hypermail/linux/kernel/0501.2/1541.html)をみると、TIF_MEMDIEが見つかったらスキップしてるのは5秒ウェイトの変わりみたい。
なるほど、5秒じゃメモリ解放が終わらないときはどーすんねん。と