(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

えーと本当はgdb対応をやろうかと思っていたのだけど,その前にスレッドのダウンについてちょっと説明.

現状,たとえばどれかのスレッドが不正アドレス参照とかでsegmentation fault とか bus error とかになった場合には,KOZOS全体が落ちてしまう.

しかしこれらのエラー時にはシグナルが発行されるので,それを割り込みとしてとらえて,当該のスレッドだけ落とす,という動作ができるはずだ.

で,そーいうふうに修正してみた.以下は前回からの差分.

今回の修正は少なめだ.まず thread.c だが,実は SIGSEGV とかを受け取ったときにはスレッドをスリープするような処理が既に入っている.これは thread_intrvec() の以下の部分だ.

case SIGBUS: /* ダウン要因発生 */
case SIGSEGV:
case SIGTRAP:
case SIGILL:
{
fprintf(stderr, "error %s\n", current->name);
/* ダウン要因発生により継続不可能なので,スリープ状態にする*/
getcurrent();
}

getcurrent()によりカレントスレッドをスリープ状態にして,あとはそのままなので,SIGSEGVなどを出したスレッドがスリープ状態になってあとは他のスレッドがディスパッチされて動き続けることになる.

ただ,スリープさせてもとくにすることはないし,UNIXでも segmentation fault が発生したらプロセスをダウンさせてしまうのが普通なので,スレッドを終了させてしまうように修正しよう.

case SIGBUS: /* ダウン要因発生 */
case SIGSEGV:
case SIGTRAP:
case SIGILL:
{
- fprintf(stderr, "error %s\n", current->name);
+ fprintf(stderr, "error thread \"%s\"\n", current->name);
/* ダウン要因発生により継続不可能なので,スリープ状態にする*/
getcurrent();
+#if 1 /* スレッド終了する */
+ thread_exit();
+#endif
}

あとKOZOSの起動時に,エラー関連のシグナルを受信できるように設定しておく.

static void thread_start(kz_func func, char *name, int pri, int argc, char *argv[])
{
memset(threads, 0, sizeof(threads));
memset(readyque, 0, sizeof(readyque));
memset(sigcalls, 0, sizeof(sigcalls));

timers = NULL;

signal(SIGSYS, thread_intr);
signal(SIGHUP, thread_intr);
signal(SIGALRM, thread_intr);
+ signal(SIGBUS, thread_intr);
+ signal(SIGSEGV, thread_intr);
+ signal(SIGTRAP, thread_intr);
+ signal(SIGILL, thread_intr);

KOZOSに対する修正はこれだけだ.これだけで,不正アドレス参照などでのsegmentation fault 発生時に,当該のスレッドを終了させることができるようになる.

実験用に segmentation fault を手動で発生させることができるように,telnet に break コマンドというのを追加しよう.

diff -ruN kozos08/telnetd.c kozos09/telnetd.c
--- kozos08/telnetd.c Sun Nov 4 11:27:46 2007
+++ kozos09/telnetd.c Sun Nov 4 11:27:46 2007
@@ -13,6 +13,7 @@
#define PORT 20001

int telnetd_id;
+int telnetd_dummy;

static int command_main(int s, char *argv[])
{
@@ -41,6 +42,9 @@

if (!strncmp(buffer, "echo", 4)) {
write(s, buffer + 4, strlen(buffer + 4));
+ } else if (!strncmp(buffer, "break", 5)) {
+ int *nullp = NULL;
+ *nullp = 1;
} else if (!strncmp(buffer, "date", 4)) {
time_t t;
t = time(NULL);
@@ -51,7 +55,7 @@
int i;
for (i = 0; i < THREAD_NUM; i++) {
thp = &threads[i];
- if (!thp->id) break;
+ if (!thp->id) continue;
write(s, thp->name, strlen(thp->name));
write(s, "\n", 1);
}

telnet 接続して break コマンドを入力した場合には,NULLポインタアクセスでsegmentation fault が発生する.ついでに threads コマンドで,終了したスレッドがあるとそこで表示が終了してしまうバグを修正.あと telnetd_dummy はこのあとの gdb 対応のためのものなので,まあ気にしないで.

メインの関数は以下になる.実行してみよう.

% ./koz
Sun Nov 4 11:38:10 2007
Sun Nov 4 11:38:11 2007
Sun Nov 4 11:38:12 2007
Sun Nov 4 11:38:13 2007
Sun Nov 4 11:38:15 2007
...

時刻表示は正常に動作している.

telnetで接続してみよう.

% telnet 192.168.0.3 20001
Trying 192.168.0.3...
Connected to 192.168.0.3.
Escape character is '^]'.
> date
Sun Nov 4 11:38:34 2007
OK
>

もうひとつ,追加で telnet 接続する.

% telnet 192.168.0.3 20001
Trying 192.168.0.3...
Connected to 192.168.0.3.
Escape character is '^]'.
> date
Sun Nov 4 11:38:45 2007
OK
> threads
command
extintr
outlog
idle
clock
telnetd
httpd
command
OK
>

threads によるスレッド一覧表示で,command スレッドが2つ表示されていることに注目してほしい.2箇所から telnet 接続しているので,command スレッドが2つ起動しているわけだ.

ここで,片方の telnet 接続で break コマンドを実行してsegmentation fault を発生させてみる.

% telnet 192.168.0.3 20001
Trying 192.168.0.3...
Connected to 192.168.0.3.
Escape character is '^]'.
> date
Sun Nov 4 11:38:34 2007
OK
> break


Sun Nov 4 11:39:14 2007
Sun Nov 4 11:39:15 2007
Sun Nov 4 11:39:16 2007
Sun Nov 4 11:39:17 2007
error thread "command"
Sun Nov 4 11:39:18 2007
Sun Nov 4 11:39:19 2007
Sun Nov 4 11:39:20 2007
Sun Nov 4 11:39:21 2007

command スレッドでエラー発生したというログが出力され,時刻表示は継続している.segmentation fault が発生しても,別スレッドである時刻表示は動き続けているという点に注目.

もう1箇所の telnet 接続で,threads コマンドによってスレッド一覧を確認してみよう.

> threads
extintr
outlog
idle
clock
telnetd
httpd
command
OK
>

command スレッドがひとつだけになっている.つまり,segmentation fault を出したスレッドだけ終了して,他のスレッドはそのまま動き続けているわけだ.まあ本来ならばスレッド終了時にソケットのクローズとかを行うべき(なので,telnet している側が固まってしまう)なのだけど,まあ実験なのでとりあえずよしとする.

こんな感じで,segmentation fault や bus error の発生時には,当該のスレッドだけ落として処理を継続することができる.うーん,OSっぽい.他にもゼロ除算とかでも応用できるだろう.

ていうか,OS上で動くプロセスのレベルでも,ここまでできるんだなあ...我ながらちょっと関心.segmentation fault が起きたらcoreダンプして落ちてあとはgdbでデバッグ,というのが普通の手順だけど,こーいう対処もできるということだ.これってちょっと便利なんではなかろうか?なんか応用できないかなあ.他のスレッドライブラリとかって,このへんの動作はどうなっているんだろうか?pthreadとか.誰か知ってたら教えて.

次は今度こそ,gdb対応だ!