カーネル内エラー処理

setlabel/gotolabelのもう1つの使い道がエラー処理である.カーネルのように複雑な処理をやっていると,普通,エラー処理はネストしてしまう.しかし,同じラベルに対してsetlabelを実行すると,前の値を書き潰してしまう.そこで,エラー処理用にerror,nexterror,waserror,poperror,nexterrorという関数またはマクロが提供されている.これらはProc構造体のラベルerrlab[]を利用する.

port/portdat.h
  579:         NERR = 64,

  600: struct Proc
  601: {

  683:         int    nerrlab;
  684:         Label  errlab[NERR];

  733: };

各関数,マクロは短いので順に見ていこう.

  pc/fns.h
  164: #define waserror()      (up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))

  port/portfns.h
  221: #define         poperror()             up->nerrlab--

  port/proc.c
 1424: void
 1425: error(char *err)
 1426: {
 1427:         spllo();
 1428: 
 1429:         assert(up->nerrlab < NERR);
 1430:         kstrcpy(up->errstr, err, ERRMAX);
 1431:         setlabel(&up->errlab[NERR-1]);
 1432:         nexterror();
 1433: }
 1434: 
 1435: void
 1436: nexterror(void)
 1437: {
 1438:         gotolabel(&up->errlab[--up->nerrlab]);
 1439: }

では,実際に使われている例を見ていこう.まず,nerrlabを0に初期化する.!waserror()は真を返すので,システムコール番号をインデックスにsystab[]に登録された関数を実行する.その関数内でerrorが呼ばれなかったら,poperrorを呼び,nerrlabを0に戻し,後始末をする.一方,errorが呼ばれた場合は,677行目のラベルにジャンプし,694行目以降のエラー処理を行う.

  pc/trap.c
  642: void
  643: syscall(Ureg* ureg)
  644: {

  675:         up->nerrlab = 0;

  677:         if(!waserror()){

  691:                 ret = systab[scallnr](up->s.args);
  692:                 poperror();
  693:         }else{
  694:                 /* failure: save the error buffer for errstr */
  695:                 e = up->syserrstr;
  696:                 up->syserrstr = up->errstr;
  697:                 up->errstr = e;
  698:                 if(0 && up->pid == 1)
  699:                         print("syscall %lud error %s\n", scallnr, up->syserrstr);
  700:         }

  737:         kexit(ureg);
  738: }

さらにエラー処理がネストした場合を見ていこう.839行目のwaserror以降でerrorが呼ばれ,ここにジャンプした場合,841行目のnexterror,すなわちgotolabelはtsleepを呼び出した関数のwaserrorにジャンプする.例えば,syscall関数内のものとか.

  port/proc.c
  824: void
  825: tsleep(Rendez *r, int (*fn)(void*), void *arg, ulong ms)
  826: {

  837:         timeradd(up);
  838: 
  839:         if(waserror()){
  840:                 timerdel(up);
  841:                 nexterror();
  842:         }
  843:         sleep(r, tfn, arg);
  844:         if (up->tt)
  845:                 timerdel(up);
  846:         up->twhen = 0;
  847:         poperror();
  848: }

カーネルは例外処理,エラー処理の塊ともいえるので,これらを簡単に処理したいという要求がある.setjmp/longjmpのような仕組みを使うのは,割と普通の考えだと思うが,WindowsNTは言語処理系に手を入れて,構造化例外という仕組みを使っている.Limboで実装されたInfornoは,もっとスマートにエラー処理をやっているんじゃないかなぁ(実態は全然知らないけど).