(第9回)セグメントエラーでダウンさせてみる

2007/11/04

あなたは 人目のお客様です.

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

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

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

で,そーいうふうに修正してみた.

(2009/04/10 ライセンスに関する文書として,KL-01とLICENSEを追加. 詳しくは第43回を参照)

以下は前回からの差分.

今回の修正は少なめだ. まず 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対応だ!


メールは kozos(アットマーク)kozos.jp まで