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