トップ «前の日記(2014年03月05日) 最新 次の日記(2014年05月01日)» 編集
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|10|12|
2009|02|03|06|07|10|11|12|
2010|01|02|03|04|07|09|10|11|12|
2011|01|03|04|05|06|07|08|10|
2012|01|06|08|09|10|12|
2013|01|02|03|04|07|09|11|12|
2014|01|03|04|05|06|09|
2015|04|
2016|01|08|
ここは旧えびめもです。えびめも2に移行します(2016/12/1)

2014年04月30日

プロローグ(追記)

twitterで皆様からコメントを頂き感謝しています。プロセスの停止にはSIGKILLではなくSIGTERMを用いるべきとのご指摘を頂きまして、確かにその通りですが書き直すのも面倒だし下名の誤りをそのまま残しておきますので以下SIGKILLはSIGTERMだと思って読んでください。


話の発端はあるサービスをrestartさせようとしたときに他のプロセスが死んでしまった。OOMでもないしシグナルを誤配信した可能性が否定できないシナリオについて想像いたしました。別にSIGTERMでもSIGHUPでもSIGUSRでもいいんですが、シグナルハンドラを実装していないプロセス(busyboxのappletにありがち)はシグナルを受けると容赦なく死んでしまうのでPID特定とシグナル配信のatomic性についての考察になります

プロセスの安全な停止はどうするんだろ

POSIX系OSにおいてプロセスを停止させるには ps で PID を調べて kill -9 とする。と覚えたはずである。例えば hoge プロセスを停止するには
$ ps ax | grep hoge
 1000 pts/0    S      0:10 hoge
なるほど PIDは1000だな。では kill しよう。。。 しかしグズグズしているうちに hoge プロセスは自主的に終了してしまうかもしれない。その間に全く無関係なプロセスが立ちあがり、新規に pid 1000番になる可能性は十分にある。ps で調べたのち時間が経過してから
# kill -9 1000
などしようものなら全く無関係な httpd やら sshd やら bash やらを killしてしまう可能性がある。そんなお話です。

プロセスの安全な停止その2

pidを調べてkillを送る。それを一発で行う pkill というコマンドがある。pkill にも同じ問題がある気がしてきたのでソース(debian wheezy procps-3.3.3)を眺めてみた。
案の定、pkill は proc/readproc.c によって
1) /proc の下を名前で検索してPID番号を得る
2) そのPID番号に kill() 関数で SIGTERM を送る
という手順で実装されている。ここで 1) 2) 間は atomic ではない。人間がpsで調べるのと原理は同じで、手で調べるよりは高速であるが時差は生じてしまう。てことはレースコンディションが存在する。
上記の通り PID=1000 番の hoge というプロセスを殺そうとする
# pkill hoge
1) で PIDは1000だと検出するが、その直後に hogeプロセスは自らexit()した場合において
かつ、その直後に全く無関係のプロセスが生成し、偶然 PID=1000番を取得する。

2) で PID=1000 プロセスを SIGTERM するので無関係なプロセスを殺してしまう。

procps-3.3.3 を見る限り対策は施されていないし、というよりもuserlandでこの問題は対策できそうもない。
kill直前に名前の一致をもう一度再確認しようとしても

if(名前の再確認){
   // この隙間時間
   kill();
}
がゼロにならないからだ(userlandから preemtion lockもできないし)

「カーネル側でプロセス終了直後のpid番号は一定時間(10秒程度)は払い出さず再利用しないようにする」と対策をすれば万全ではないにしろ確率が低下するけれども、それだとPIDを15bitで運用している環境では毎秒数百プロセスをfork/exitするとPID番号不足に陥る。

「pkill でプロセスを殺そうとすると無関係のプロセスを殺してしまう」 という不具合は存在し得ることになる。

存在しえる問題は、いつか必ず具現化する

不具合の原因を調べていったらレアケースでこれだった。と言う事もありえるわけで、これは大きな問題じゃないだろうか。
(同じ問題は/var/runのpidファイルを用いる start-stop-daemon でも言える)

私の勘違いであるなら、どなたかご指導お願いします。 (twitter @ebihara_nagoya )

pkill追記

カーネルソースを斜め読みしてみた。
do_fork()の方は alloc_pid()からの流れで pid_namespace内で last_pid+1 しているだけのように見えるなぁ。
do_exit()のほうは
        tsk->state = TASK_DEAD;
        schedule();
としてスケジューラを呼んでいる。EXIT_ZOMBIE => EXIT_DEAD => TASK_DEAD => task_struct 消滅まで時間がかかり、この間PIDは維持されるから上記問題は起こりにくいとされているのだろうか。

あまり議論されていない

杞憂かもしれないがこの件について議論しているスレッドを見つける事が出来なかった。pkillだけではなく、Cで普通にプログラムしても同じ理屈で
 signal(SIG_CHILD, SIG_IGN);
 child_pid = fork();
 
 ... snip ...
 
 kill(child_pid,SIGTERM);
が安全である保証はどこにもないよなぁ(特に組込み機器で何もかもrootで動いている場合)。。。SIG_IGN を使ってはいけないという事だね。ZOMBIEはこのためにあるのか。

pidを64bitにすれば解決するか

21世紀にもなってなぜpidが(defaultでは)わずか32767まで(15bit)でループするのか。pidの使用/未使用は1bit単位のmap_tableで管理されている。1PAGE=4Kbyte=4096byte=32728bitのフラグで管理している(当然これはPAGE単位で増やせるがdefaultでは1PAGEである)。確かに小メモリであるし、pid=1からインクリメンタルにサーチするよりも1bit調べればよいので時間は高速である。

いっそのことPIDを64bitにしてしまえば1秒間に4ギガ回プロセスをfork()してもPIDがロールオーバーするのに138年かかるので、この問題は解決すると言っても良い。またはuuidにするか。


いずれにしても15bitでロールする環境において、プロセスを安全にkillするにはどうしたらいいんだろう。思いつかない。

追記2014-5-1

原則に戻り、
1)なんでもrootで動かしてはいけない。サービス毎にuserを作りそのuser権限で動かせば他のサービスを誤ってkillしたりできない。
2)SIG_CHILDをSIG_IGNでデタッチしない。
でも /var/run の pidファイルを信用してのプロセスの停止は問題があることに変わらないな。

uuid

PID番号によるプロセス管理は簡単だから継続するとして、プロセスが生成される毎にuuidが付加されるようにならないかな。
/proc/PID/uuid
のように。そして
# kill --uuid <UUID>
とできるようになればよさそうなのだが。

コメントを頂きました

https://twitter.com/satoh_fumiyasu/status/461766533866192896
以下のような手順はいかがでしょうか。 
(1) SIGSTOP 送ってプロセスを止める。 
(2) 止まったプロセスが目的のものか確認する。 
(3) 目的のプロセスなら SIGKILL 送って殺す。 
目的のプロセスではなかったら SIGCONT を送って再開させる。