第 6 章シグナルを介したプロセス例外 (kern/sig. c) る。 trapsignal() は、指定されたシグナルを、現在のプロセスのコンテクストで処理するか、あるい は、適切なタイミングで処理されるように準備する。この関数は、たとえシグナルがユーザープログラ ムに対して即座に動作されるものでなくても、そのシグナルに関連するステートが必ず維持されるよう にする。シグナルを配送できない場合、そのプロセスは終了させられる。 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 89 の ] void trapsignal(struct proc *p, int sig, unsigned code) trapsignal() の引数は 3 つ、シグナルを受けるプロセスへのポインタと、送られたシグナルと、オ プションのトラップ毎のコードである。戻り値はない。この関数の副作用は、シグナルを処理するか、 ペンディングにするかの、どちらかである。 例外トラップはソフトウェアによって生成されるトラップと違って、無視できないハードエラーによっ て生成される場合がある。また、もっと悪いことに、シグナル処理の結果として発生することもある * 38 。 そして最後に、プロセッサ例外には、ソフトウェアが生成するシグナルにはない、 トラップに関する情 報が付随していて、条件が失われる前にそれをハンドラに届けなければならないことが、しばしばある。 また、 386 のような一部のプロセッサの場合、シグナルが非同期に届くという問題もある ( たとえば浮動 小数点演算コプロセッサの場合など ) 。しかし、このソフトウェアの現在のバージョンでは、これはまだ 完全にマシンに依存しない形では実装されていない。 ◆ 6.3.16.1 trapsignal() の実装 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 896 ] code ; / * XXX for core dump/debugger * / ps—>ps-code 指定されたトラップ毎のコードを、プロセスのシグナル動作構造に保存する。このデータ構造は、 グナルのための、もっと汎用的なソース記述の代用として使用されている細工である。 [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 9 の 9 ] if (p = curproc & & sig SIGSEGV & & vmspace-access (p—>p—vmspace , (caddr-t)ps—>ps-sigact [S 工 GSEGV] , ン sizeof (int) , PROT READ) = の sigexit (), sig) ; * 38 たとえば例外によるシグナルの中の例外といった再帰的なケ 346 ース。
第 9 章 POSIX オペレーティングシステム機能 (kern/execve. c 、 kern/descrip. c) ルの select によってエラーが発生した場合は、そのエラーの値を返す。 [ Fi1e : /usr/src/kernel/kern/descrip. c , line : 939 ] static int selscan(struct proc *p, fd set *ibits , fd—set *obits , int nfd, selscan() の引数は 5 個、 select の対象であるプロセスへのポインタと、チェックすべきファイル記 int *retval) を受け取るためのポインタである。この関数は、成功した場合は 0 を返し、ファイル記述子が存在しな の中でチェックすべきファイル記述子の合計数、そして select が成功するファイル記述子の数を示す値 述子集合のべクタへのポインタ、書き戻しに使用するファイル記述子集合のべクタへのポインタ、集合 switch (which) { for (which = ; which く 3 ; which + + ) { [ Fi1e : /usr/src/kernel/kern/descrip. c , line : 949 ] ◆ 9.3.16.1 selscan() の実装 い場合にはエラー (EBADF) を返す。 case の : flag case 1 : flag case 2 : flag FREAD ; break ; FWRITE ; break ; ; break; 最も外側のループによって、ファイル記述子ビットべクタの 3 つの集合 ( 入力、出力、例外 ) を順に走 査する。この switch ステートメントでは、それぞれのケースに従ってフラグが設定されるが、例外の 場合は 0 ( フラグなし ) となる点に注意してほしい。 [ Fi1e : /usr/src/kernel/kern/descrip. c , line : 961 ] for (i の ; i く nfd; i + = NFDBITS) { ibits [which] . fds—bits Ci/NFDBITS] ; bits while ( (j ffs(bits)) & & i + --j く nfd) { ~ ( 1 くく j ) ; bits & = fp = fdp->fd-ofiles[i + j] ; if ()p = = NULL) { error = EBADF ; break ; 508
5.3 プロセス終了の構造 : kern/exit. c これを捕獲してのリターンに成功することができる。 [ Fi1e : /usr/src/kernel/kern/exit . c , line : 314 ] nfound + + ・ / * 待つべき子がひとつもないときの wait * / it (nfound = = の return (ECHILD) ; ば、 wait() の対象となる子プロセスが存在しないので、エラー (ECHILD) を返す。 はすぐに実行される状態にあり、これらは終了ないし停止する場合がある。もしこれらが 1 個もなけれ 子プロセスが停止も終了もしていなければ、その数を数える。これらのプロセスは実行中か、あるい goto 100P ; return (error) ; if (error = tsleep( (caddr—t)q, PWAIT ー PCATCH, あるいはウェイクアップさせるまで待っ * / / * 子プロセスが終了するか、この親にシグナルを送るか return ( の ; retval [ の ] if (uap—>options & WNOHANG) { [ Fi1e : /usr/src/kernel/kern/exit . c , line : 322 ] 295 子の終了または停止によって起こされたプロセスとの見分けはつかない。 うに条件付けされる可能性もある。その場合は子プロセスの走査を最初からやり直すことになるので、 う、割り込みを許可しておく。ただしシグナルは、オプションによりシステムコールに割り込まないよ システムコールハンドラからリターンしたときに、送られたシグナルをすぐに受け取ることができるよ 合はプロックせずに、プロセス ID なし ( 0 ) を返す。スリープを行う場合は、現在のプロセスが wait4 ( ) の子を待ってスリープする。ただし、その前に WNIHANG オプションがないか確認し、指定されていた場 それ以外のケースで、いつかは終了ないし停止するはずの子プロセスがあれば、現在のプロセスは、そ
第 3 章 CPU 固有のプリミティブ ( i386 ap. c 、 i386/cpu. c) #endif 128 ある。 蛮な手段があるが、これは大変デリケートな操作であり、コネクタのピンを慎重に数えてから行うべきで * 16 ISA スロットにマイナスドライバを挿入して NMI を一時的にグラウンドにショートさせるという非常に野 trapsignal(), signo , ucode) ; if (signo) p¯>p-md. md—flags ト MDP—SIGPROC ; trap : [ Fi1e : /usr/src/kerne1/kern/i386/trap. c , line : 42 の ] ンする。 ドルからのフォールト処理は終了し、このハンドラから例外発生元に ( あるいは継承ハンドラに ) リター セスのコンテクストでシグナルをチェックする方法はないからだ。このため、割り込みレベルまたはアイ プロセスが存在しない場合、このフォールトについて、もう処理すべきことはない。存在しないプロ return ; = の [ FiIe : /usr/src/kerne1/kern/i386/trap ・ c , line : 416 ] ルト全般の処理に戻る。 これで、このトラッブハンドラの各フォールトごとのコードは終わりである。次は、ふたたびフォー はまだ実装されていない。 複雑な機構を実装すべきであり、カーネル以外のシステムの一部にも責任を分担させるべきだが、それ ーこには、もっと もある ( プロセッサをリセットすべきか、フォールトを無視するかを判定するため ) 。 的に移行するためのコードがあり、例外をバスアダブタおよびバッテリーパワー例外に分配するコード また、 I/O チャネルのチェックから NMI が生成される場合に備えて * 16 カーネルからデバッガへ強制 バスマシン一般に当てはまるわけではない。 では、フェイルセーフタイマを実装できるので、条件コンパイルできるようにしてあるが、これは ISA こで処理しているのは、これらのフォールトの一部である。 CompaqSystemPro NMI が発生する。 繋げている。そして ISA バスマシンの場合、 I/O チャネルのチェック信号をグラウンドに落としても、 の機能に集約されている。また、ほとんどのラップトップは、パワーフェイルの例外を、このラインに ト、リフレッシュタイムアウト、バスタイムアウトなど ) やメモリのパリティエラーなどが、すべてこ ンダによってさまざまである。マザーボードの各種のフォールト ( たとえばフェイルセーフタイムアウ で " その他のプロセッサ例外 " の一切に使われている。 IBMPC の場合も、 NMI を生成する原因は、べ 上に示したのは NMI(Non-MaskableInterrupt) 例外ハンドラである。 NMI は、多くのプロセッサ
3.1 プロセッサ例外とシステムコールエントリのハンドリング : i386/trap. c とが可能である。その結果として、カーネルバニックが起きてしまう。カーネルは、すべてが再入可能 な形で書かれているわけではないからだ。したがって、もしカーネルモードでトレーストラップが発生 し、それがシステムコール用のコールゲートに関わっていたら、トレース条件をクリアし、このイベン トを知らせる MDP ー SSTEP をプロセスのマシン依存の性質として記録する。 トレーストラップが他のどこかで発生した場合は、このルーチンのデバッガ用フィルタで捕捉されな かった以上、予期しないトラップであることは明らかであり、したがってターミナルエラーとみなされ る。プレークポイント (T_BPTFLT) またはトレース例外 ( T ー TRCTRAP ) がユーザーモードで発生した場 ロ、デバッガシグナルのケースをシミュレートするため、 SIGTRAP シグナルが送られる。このシグナ ルは、デバッグ対象の ptrace ( ) された子プロセスの場合、デバッガによって捕捉され、その子プロセ スを停止させる透過的なエントリとして使われる。プレークポイントがトレースされた場合は、プロセ スの EFLAGS レジスタの中の PSL ー T ビットがクリアして、同じ命令について二度目のトラップが起きな いようにする。 [ Fi1e : /usr/src/kerne1/kern/i386/trap. c , line : 381 ] #ifdef nope #include isa. h #if #ifdef #endif NISA > の case T_NM 工 : case T—NMII T-USER : FAILSAFE if (inb(Øx61) & 必 x4 の { extern int splstart ; failsafe-cancel(); printf(l%x %x:%x. splstart , frame. tf-eip, frame. tf—cs & Øxffff) ; #ifdef DDB / * NMI はデパッグ用の押しボタンスイッチに接続してもいい * ( 緊急の場合、中型のマイナスドライバを ISA スロットに * 挿入して NMI をグラウンドにショートしてもいいが・・・ * ただし細心の注意を払って慎重に行うこと ! -wfj) printf (NMI going t0 debugger\n) ; if (kdb-trap(type , code , &frame) ) #endif / * マシン / パリティ / 電源断 / その他のフォールト * / if(isa—nmi(code) = の else goto we—re—toast ; #endif 127
3.1 らせるシグナルを発生する場合があるので、 プロセッサ例外とシステムコールエントリのハンドリング : i386 / t 「 ap. c このフォールトのフォールトハンドラが登録されていれば、 そのハンドラが呼び出されることになるだろう。けれども、ハードウェアが存在しない場合は、ソフト ウェア浮動小数点演算工ミュレータをチェックして、そのフォールトを起こした命令をエミュレートでき るかどうか調べる。もし可能であれば、トラップディスパッチのループを終了する。以上 2 つのルーチン が存在しないか、あるいは成功しなかった場合、そのプロセスには浮動小数点関連シグナル ( SIGFPE ) が送られる ( シグナルが「プロセッサ不在トラップ」によって生成されたことを示す、マシン依存のコー ド ( FPE-FPU-NP-TRAP) が付加される。 [ Fi1e : /usr/src/kerne1/kern/i386/trap. c , line : 263 ] case T—BOUND ー T—USER : / * ucode SEGV_BOUND_TRAP ; * / signo = SIGSEGV; break; X86 の BOUND 命令によって範囲を超える値が発見されるとトラップが生成される。これによって、そ のプロセスにはセグメント違反のシグナルが送られる。このシグナルはさまざまな用途で使われているの で、バウンドトラップであることを示す特別なコード ( SEGV ー BOUND ー TRAP ) がトラップのソースフィー ルドに付加される。 [ Fi1e : /usr/src/kerne1/kern/i386/trap. c , line : 268 ] case T—OFLOW ー T—USER : UCOde SEGV_OFLOW_TRAP ; signo = SIGSEGV; break; X86 で計算された値が数値の範囲を超えた場合、オーバーフローが発生してトラップを生成する。これ によって、そのプロセスにはセグメント違反のシグナルが送られる。このシグナルはさまざまな用途で使 われているので、オーバーフロー状態のトラップであることを示す特別なコード (SEGV_OFLOW_TRAP) がトラップのソースフィールドに付加される。 [ Fi1e : /usr/src/kerne1/kern/i386/trap. c , line : 273 ] case T-ALIGNI T-USER : ucode = BUS—ALIGN_TRAP ; signo = SIGBUS ; break; プロセッサが 486 以上でアラインメントフォールト機能が許可されているとき ( コントロールレジスタ 0 の定義を参照 ) 、アラインメントフォールト T-ALIGN を捕捉して、そのプロセスにバスエラーのシグ 121
第 6 章シグナルを介したプロセス例外 (kern/sig. c) そういうわけで、この関数はシグナルを受けるプロセスのコンテクストにおいてのみ使用される。も しシグナルが特別なカーネル動作 ( プロセスのトレース、停止、続行 ) を要求する場合、その動作を完全 に実装して、ペンディングビットを削除する。また、もしシグナルがユーザープログラムの動作または 終了を要求する場合、 psig() に渡して処理できるように、そのシグナルのインデックスを返す。 [ Fi1e : /usr/src/kernel/kern/sig ・ c , line : 612 ] int issig(struct proc *p) issig() の引数は 1 つ、プロセスへのポインタである。戻り値は、処理すべきシグナルの値である。 処理すべきシグナルが存在しない場合、この関数は 0 を返す。この関数が処理を必要とするシグナルを 返したら、そのシグナルを処理する義務があることに注意してほしい。なぜなら、そのシグナルを処理 することを前提に、プロセスの状態を変更しているからである。 psignal() 、 issig() 、 psig() は、 それぞれお互いの動作を熟知したうえで書かれている。 ◆ 6.3.12.1 issig() の実装 [ Fi1e : /usr/src/kernel/kern/sig ・ c , line : 617 ] for ( ; mask = p->p-sig & ~ p->p-sigmask; if (p->p-flag & SPPWAIT) mask & = -stopsigmask ; if (mask の ) / * 送るべきシグナルがない * / S1g bit Pr0P return ( の ; ffs ( (long)mask) ; sigmask(sig) ; sigprop [sig] ; まずプロセスにシグナルがないか調べる * 29 。シグナルを受けるプロセスが vfork ( ) 処理 ( ker Ⅱ / fork. c ) を親のコンテクストで実行していたら、そのプロセスのシグナルマスクの内容に関わらず、ス トップシグナルは禁止される。このようなプロセスを停止させることは許されない。 その結果、マスクされたシグナルの集合に評価すべきシグナルが含まれていなかったら、この関数は 即座に 0 を返す * 30 。マスクの中に評価すべきシグナルがあるときは、集合の中の最上位のシグナルと、 そのビットおよびプロバティを、シグナルの評価のために取り出す。 * 29 現在の保留シグナルのマスクに対して、ペンディング中のシグナルをチェックする。 * 30 このケースは issig() の呼び出しの前に CURS 工 G マクロによってチェックされているので、こうなるのは マスクの中のシグナルが issig ( ) によって ( あるいは psignal ( ) によって ) 削除された場合である。 334
第 6 章シグナルを介したプロセス例外 (kern/sig. c) リープの効果を打ち消すことはできないという点に注意してほしい。なぜなら、シグナルの配送とはプ ロセスに対する割り込みにほかならないからである。このように、停止中のプロセスに送られたシグナ ルが認識されるのは、そのプロセスが続行されるか、あるいは割り込みを許可したスリープに戻り、そ れからさらに実行状態に戻るか、どちらかに限られる。 [ Fi1e : /usr/src/kernel/kern/sig ・ c , line : 58 の ] default : / * シグナルの評価を強制する * / cpu-signotify(p) ; goto out ; 他のすべての状態 ( 実行中ないし実行準備中 ) にあるプロセスには、シグナルの存在を知らせなければ ならない。これはマシン依存の関数、 cpu-signotify() を介して行われる ( i386 / cpu. 参照 ) 。 386 の場合、プロセスには非同期システムトラップ ( AST) のシミュレーションを行うというマークが付け られる。 AST は、プロセスが次にカーネルからユーザーモードに戻るときにシグナルの認識を必要とす る、一種のソフトウェア割り込みである。これは、プロセスがシグナルを認識する最も早い機会である。 これによって、プロセスは、トラップおよびシステムコールのハンドラの中で、プロセスをそのコンテ クストの中でアクセスするのが安全なときに、シグナルの評価を強制される。各種のプロセスステート こで終了する。 を変更するコードは、 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 586 ] runfast : / * プロセスにシグナルを配送するため優先順位を高める * / if (p->p-pri > PUSER) p->p-pri PUS ER ; setrun(p) ; run : out : SPIX(S); psignal() の終了方法は 3 種類ある。第一に、もしシグナルが配送されるか、あるいはプロセスを終 了させるとわかったら、この関数は、プロセスが低い優先順位に落とされていてシグナルの配送が送れな い場合に対処するため、ユーザープロセスとしては最高の優先順位に実行されるようにする (runfast)o psignal() の第二の終了方法では、シグナルを受ける前に設定されていた優先順位でプロセスを実 行させる ( run ) 。これは、実行中に停止されたプロセスに対して、単にトレースを使用したり停止状態 にあったからといって優遇することなく、同じ優先順位を保つようにするためである。このような場合、 プロセスはすぐに終了することなく、しばらくは状態を遷移しつつ生き残る場合が多いからだ 。こっし 332
第 9 章 POSIX オペレーティングシステム機能 (kern/execve. c 、 kern/descrip. c) する間、自分自身もアクテイプなので、衝突 (競合 (races) と言ったほうが通りがいい ) は起こり得る。 ビットをテストした直後にそのビットが ready になるような状況を防ぐために、現在発生しているイベ ントの数 ( 待っているうちに取り逃がした可能性もある ) を追跡する。待っている間にその数が変わった ら ( この数は、すべてのプロセスのすべての select が共有するグローバル変数にある ) 、取り逃がしたイ べントが存在する可能性があるので、再度走査しなければならない。次に、プロセスに対して select が 行われているというマーク (SSEL) を付け、ファイル記述子イベントの走査を実装するヘルバー関数の selscan() を呼び出す。 この走査でエラーないしイベントが見つかった場合は、このループから出る。そうでなければ時計を 調べて ( タイムアウトが指定されていた場合 ) 、走査の実行により、すでにタイムアウトを過ぎているか どうかチェックするとともに、衝突が発生していないか、プロセスがすでに select 状態を終えていない かをチェックする。タイムリミットを超過していた場合、プロセスフラグから select ビットをクリアし、 べースの割り込みレベルに戻って、ループから出る * 69 。 衝突が存在するか、あるいはフラグの select ビットがクリアされていたら、このループをもう一度最 初から行う。プロセスが select ビットを失っているのは、 selscan() 関数が呼ばれたあとに、待ってい たイベントが発生したことを意味する ( その発生を取り逃がした ) 。もし衝突の数が食い違っていたら、 誰か別の者が同じイベントを他のプロセスから監視していて、それが原因でイベントを見失ったことに なる。どちらの場合も、リトライが必要である。 これらのチェックを行う間、割り込みを禁止して、競合が発生したかどうかをチェックしている間にタ イマの更新や select イベントの状態が変わるなどの競合状態を防ぐ。これはコードのクリティカルセク ションの一例である。 select ビットをクリアし、プロセスはイベントを待って ( また、オプションでタイムアウトも待って ) スリープする。 select ( ) は、シグナルでウェイクアップされるように、割り込みを許可してスリープ する (PCATCH)0 この割り込み可能な sleep の設計上、即座に tsleep() に戻ってはならず、捕捉した シグナルを解釈しなければならない。このため、 PCATCH を指定してエラーが返された場合、 select は そこでリターンし、捕捉したシグナルが解釈できるようにする必要がある。その結果としてプロセスが 終了したり、あるいはユーザーモードのプロセスにシグナルを発行する可能性もある。 sleep は、 socket と vnode のどちらもウェイクアップできるように、固定の優先順位 ( PSOCK ) で行わ れる。この優先順位がシステムの wait 、 lock 、 pause よりも上にあるのは、その結果である「資源が利 用可能であること」というのもイベントの一種であるからだ。 sleep からリターンしたら、割り込みレベ ルをベースレベルに戻す ( 割り込みマスクの全解除 ) 。もし sleep からエラーが返されなかったら ( タイ ムアウトもなく、シグナルも届いていない ) 、再走査を行う。そうでなければ、このループは完了する。 こではエラーには 0 が設定されているが、それが正しいのは、これがタイムアウトなので「無視してい いエラー」だからである。結果的には EWOULDBLOCK 工ラーと同じ効果となり、その命令を追加してもしな くても結局は同じことなので、そのコードをコメントにしてある。 * 69 506
◆ 6.3.21.1 stop ( ) の実装 [ Fi1e : /usr/src/kernel/kern/sig ・ c , if (p->p-f1ag&SPPWA 工 T) return; 第 6 章シグナルを介したプロセス例外 (kern/sig. c) 6.3.21 stop ( ) とは何か stop() は、このファイルの中だけで使われるプライベート関数で、プロセスを停止状態にする。 れはジョブコントロールとプロセスのトレーシングで使用される。もしプロセスを停止することが可能 であれば、 stop() はそれを停止状態に移行させ、またオプションとして状態遷移の前に、親にそのこ とを知らせる。また、この関数はオプションとしてプロセスにプロセッサ制御権を強制的に棄てさせて、 子プロセスの状態の変化を即座に親に知らせることもできる。 [ FiIe : /usr/src/kernel/kern/sig. c , line : 1 の 43 ] static void stop(struct proc *p, int swtchit , int sig) stop() の引数は 3 つ、停止されるプロセスへのポインタ、プロセスにプロセッサ制御権を棄権させる オプション、そして停止を要求するシグナルである。戻り値はない。 stop ( ) は、現在実行中のプロセスのコンテクストから呼ばれることもあるし ( この場合はプロセッサ 制御権の破棄を要求するだろう ) 、他のプロセスから呼ばれることもある ( その場合、そのプロセスは実 行中のプロセスではないのでプロセッサ制御権を破棄させる必要はない ) 。後者の場合、シグナルは ( そ のシグナルを受けるプロセスではなく ) 現在実行中のプロセスのコンテクストで処理される。 = の line : 1 の 48 ] プロセスが vfork ( ) 処理を行って ( ker Ⅱ / f 。 rk. c ) 親からコンテクストを借りている場合、停止は 禁止される。そのようなプロセスは停止を受け入れるようなコンテクストを持っていないので、デッド ロックに陥るからだ ( 子をストップさせるのと同様である ) 。トレース中ないし停止された可能性のあ るプロセスから vfork ( ) されたプロセスについては、子プロセスが実行している vfork ( ) の動作全体 が、割り込みが許されない 1 個の巨大な命令のように扱われる。この考えにより、プロセスのエントリ は、子と共有する部分がなくなるまでは実行不可能であるという事実に伴う、数多くの不愉快なケース 352 psignal (p->p-pptr, SIGCHLD) ; = & & (p->p-pptr->p-flag & SNOCLDSTOP) if ( (p->p-flag & STRC) [ Fi1e : /usr/src/kernel/kern/sig. c , line : 1 の 52 ] を防ぐことができる。