カーネル内エラー処理
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は,もっとスマートにエラー処理をやっているんじゃないかなぁ(実態は全然知らないけど).