第 6 章 シグナルを介したプロセス例外 (kern/sig. c) return (sig) ; p¯>p—sig & = -bit; / * シグナルを受けとった * / シグナルがこのプロセスで捕捉される場合、これ以上の評価は不要であり、 psig() が処理できるよ うに、その値を返す。透過的なシグナルが、以上の switch 文から降りてきた場合、 ( もう issig() に よって処理したので ) そのシグナルはペンディングマスクから外し、他のシグナルを評価するためにルー プを最初から再開する。 6 金 13 psig() とは何か 「プロセスのコンテクストでシグナルを処理する」という意味で名付けられた psig ( ) は、 issig() と共に用いられるカーネル内部関数で、デフォルトであるプロセスの終了か、ユーザープログラムのシ グナルハンドラへのシグナル配送という結果に終わるシグナル動作を処理する。 psig ( ) が実装するの はシグナルの捕獲またはプロセス終了である。シグナルによってプロセスが終了する場合、 psig() か らリターンすることはない。それ以外の場合、 psig() は、捕獲したシグナルを受信するように、プロ セスのユーザーコンテクストを変更する。また、この関数には、シグナルが一度だけ配送されるように シグナルハンドラによって捕獲される間、そのシグナルをマスクしておく仕事もある。 psig() は、以上の動作をユーザープロセスのコンテクストにおいて行うが、シグナルに対応するべン ディングシグナルビットを認識し、それを削除する。シグナル機構のすべては、 psignal() 、 issig() 、 psig() の三者によって実装されている。 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 744 ] VOid psig(int sig) psig() の引数は 1 つ、配送すべきシグナルである。戻り値はない。この関数の副作用は、プロセス の終了またはシグナルの配送である。シグナルは、暗黙の了解により、現在実行中のプロセスによって 処理される。 ◆ 6.3.13.1 psig() の実装 [ Fi1e : /usr/src/kernel/kern/sig ・ c , p->p-sig & = &bit ; 338 : 757 ]
6.3 kern / sig. c の関数とファイル構成 [ Fi1e:/usr/src/kerne1/kern/sig ・ c, line: 682 ] switch ( (int)p—>p-sigacts->ps-sigact [sig] ) { case SIG_DFL : * システムプロセスにはデフォルト動作を行わない if (p->p-flag & SSYS) line : 698 ] 現在評価中のシグナルの動作をチェックする。ます最初に、システムプロセス ( SSYS ) に対してデフォ ignore * / [ Fi1e : /usr/src/kernel/kern/sig ・ c , ルト動作 ( つまり終了 ) を行おうとしていたら、それを無視する。 break ; 337 * psig に処理させる。 / * このシグナルには動作がある。 default : [ Fi1e : /usr/src/kernel/kern/sig. c , line : 727 ] すべて評価した。 て処理されるように、そのシグナルの番号を示す値を返す。以上で、デフォルトで透過的なケースは、 視する。しかし、どちらのケースにも該当しないシグナルは配信しなければならない。 psig() に渡し によって生じたものであれば、無視のプロバティに残されているシグナルと同じく、そのシグナルは無 動作が、孤児のプロセスグループに接続されていない状態で、残されているプロセスに対する端末停止 関数 stop() を使ってプロセスを停止させ、シグナル動作をこの関数で完全に実行する。もしその停止 デフォルト動作が停止シグナルの送信であり、しかもプロセスがトレースされている場合、ヘルバー } e1se こには来ない return (sig) ; ignore * / break; / * * デフォルト動作は無視である * SIGCONT 以外では、 } else if (prop & SA-IGNORE) { break ; stop(), 1 , sig); ignore * / break; / * prop & SA-TTYSTOP) ) (p->p-pgrp¯>pg-j 0bc if (p->p-flag & STRC Ⅱ if (prop & SA-STOP) {
6.3 kern / sig. c の関数とファイル構成 もしシグナルを送られたのがカレントプロセスで、シグナルカ坏正なアドレス参照によるもの ( SIGSEGV ) であり、シグナルの動作によって不正なハンドラアドレスが参照されることが予測される場合 ( すなわ ち、再帰的なシグナルになる可能性があるとき ) 、 sigexit() によって、このプロセスを終了させる。 p¯>p—stats—>p—ru. ru—nsignals + 十 ; if ( (p->p-sigmask & bit) / * シグナルがプロックされていなければ、ユーザープログラムに渡す ... (p—>p-sigcatch & bit) ! = の { if (p curproc & & (p->p-flag & STRC) [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 918 ] if if #ifdef KTRACE #endif (KTRPOINT(p, KTR PSIG) ) ktrpsig(p->p-tracep, sig, ps->ps-sigact [sig] , p->p-sigmask , code) ; / * 配送に失敗したらプロセスを強制終了 * / (cpu-signal(p, sig, p->p-sigmask) ) sigexit (), sig) ; 殤 / * XXX for core dump/debugger * / ps—>ps—code 347 からプレークポイント命令を実行すると、このようなことが起きる。この機構がないと、このプレーク ルの処理を続けることは不可能である ) 、プロセスが、 SIGTRAP シグナルをマスクするように設定して に終了させる。これではデフォルトの終了が宣言されたようなものだが ( 保留されたターミナルシグナ マスクできないはすの、このシグナルがプロセスによってマスクされていたら、そのプロセスは即座 グナルの発生時にごく細かくマスクされるべき全シグナルに対応させる。 れないようにする。次にシグナルマスクを更新して、いま処理したばかりのシグナルと同時に、そのシ 使用後のトラップ毎のコードをクリアして、このプロセスで次に発生するシグナルで間違って参照さ を呼び出して、このプロセスを止める。そして、このシグナルは処理されたものとする。 再帰的呼び出しを誘発させる不正なメモリ参照によって、シグナルの配送に失敗した場合、 sigexit() を直接使ってシグナルを送るという直接的な手段で実装される。もし cpu-signal() が、この関数の プログラムに送る処理は、 psig() をバイバスし、マシン依存の cpu-signal() 関数 ( i386 / cpu. c ) なら、そのシグナルはこの関数で処理される。シグナルが保留されていなければ、そのシグナルをユーザー シグナルを受けたプロセスがカレントプロセスで、トレース中でなく、シグナルを捕獲することが可能 sigexit (), sig) ; else / * ... そうでなければプロセスを強制終了 * / p—>p-sigmask ト ps->ps-catchmask [sig] ー bit ;
第 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) if (prop & SA-TTYSTOP & & p->p-pgrp->pg_j obc = SIG_DFL) action p—>p-sig & = -contsigmask; 同じように、もし停止シグナルが発生したら、ペンディング中の継続シグナルはすべてクリアする。 このように、すでに継続シグナルがあっても、ストップシグナルが来たら、それが優先する。どちらの 場合も、プロセスは停止されるか、あるいは続行されるかである。両方のシグナルが混在することはな い。 tty ドライバによって起こされる特別なストップシグナル ( SA ー TTYSTOP ) の場合、デフォルトのア クションを持ち、孤児のプロセスグループに向けられたものは禁止される。これらのプロセスが、シス テムのどこかに継承される前に ( 第 5 章、 exit ( ) 参照 ) 間違って停止されるのを防ぐためである。 [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 5 8 ] p->p-sig ト bit ; シグナルを、プロセスのシグナルペンディングマスクに加える。これで、このシグナルは、 シグナル 実装の残りの部分で処理される。 [ Fi1e : /usr/src/kernel/kern/sig. c, line : 514 ] SIG_HOLD & & ( (prop & SACONT) if (action return; のⅡ p->p-stat ! = SSTOP) ) シグナルがプロックされたら ( そして停止されたプロセスに対する継続でなければ ) 、そのシグナルの処 理はこれで終わりであり、シグナルはマスクが外されるまで、このシグナルの中で待ち続ける (unmask 呼び出しのあとの syscall ( ) によって発見される。第 3 章、 issig()/psig() 参照 ) 。 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 518 ] splhigh() ; switch (p->p-stat) { case SSLEEP : / * プロセスは割り込み禁止、ユーザーに戻るまで待っ * / 328 goto run ; if (p->p-f1ag&STRC) / * プロセスはトレース中、シグナルに気付くように、実行 * / goto out ; if ( (p->p-flag & SSINTR)
第 6 章シグナルを介したプロセス例外 (kern/sig. c) 昇格される。 [ Fi1e : /usr/src/kernel/kern/sig. c , line : 544 ] case SSTOP : / * KILL シグナルは常にプロセスを実行状態にする * / if (sig SIGKILL) goto runfast ; / * トレース中のプロセスへのシグナル : 動作は不要 * / if (p->p-f1ag&STRC) goto out ; 停止中のプロセスがシグナルを受ける場合、捕捉できないシグナルである SIGKILL かどうかチェック する。もし SIGKILL なら、停止できるように、そのプロセスを即座に実行させるための再スケジュー リングを行う * 24 。もし停止させるプロセスがトレース中であれば、そのプロセスはペンディング中の動 作を認識するように停止状態からウェイクアップされただけなので、これ以上の処理は必要ない。残り の実装は、 issig() と psig() の間で、プロセス自身によって行われる。 [ Fi1e : /usr/src/kernel/kern/sig ・ c , line : 554 ] if (prop & SA-CONT) { そうでなければスリープ状態に戻す * / goto run ; (p->p-wchan = = の / * 実行していたあとならば、再び実行させる * / goto runfast ; = SIG_CATCH) (act ion それ以外はプロセスのコンテクスト内で処理 * / p->p-sig & = &bit ; (action = SIG_DFL) デフォルトまたは無視ならペンディングをキャンセル * / if if if 330 コンテクストの中にある必要がある。 * 24 UNIX のプロセスは自殺によって終了する。終了させられるということを認識するためには、プロセスの ング中のシグナルは削除され、プロセスは前の状態に戻される。それ以外の場合、もしシグナルが捕捉 継続シグナルのアクションがデフォルトなら、コンテクスト内への配送は要求されないので、ペンディ ない、プロセスは停止された前と同じ状態に戻される。 復活する。プロセスはスリープ中にも実行中にも停止されるので、両方のケースに対処しなければなら シグナルのプロバティが停止中のプロセスを継続させるものであれば、停止されていた機能の動作が goto out ; p->p-stat = SSLEEP;
6.3 kern / sig. c の関数とファイル構成 次に、シグナルの結果としてプロセスステートを変更する。競合状態が起こらないように、プロセス の proc 工ントリへのアクセスを排他的に行う。プロセスが割り込みを禁じてスリープしている場合、 処理はこれで終了し、プロセスがスリープを終了してユーザーモードに戻るまでシグナルは発見されな い。もし、この条件が満たされなければ ( たとえばフロッピーディスクのようなプロックデバイスがオー プンされてもメディアがレディにならないときなど ) かなり長い ( 永久的な場合もある ) 時間が経過しな いとシグナルを受け取らない場合もある。ただしスリープが割り込み可能な場合、トレースされている ナルのビットをクリアし、プロセスはウェイクアップ待ちの状態で残される。スリープ中のプロセスに このシグナルのプロバティが、単にデフォルト動作として継続するだけであれば、ペンディングシグ = SIG_DFL) { goto out ; p->p-sig & = Nbit ; if ( (prop & SA—CONT) & & action [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 531 ] プロセスだけは、シグナルを発見できるように実行させることが許されている。 対する継続は、ウェイクアップを意味しない。 [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 537 ] if ( (prop & SA-STOP) ! = 効 & & action p->p-sig & = &bit ; StOP(), の , sig); goto out ; goto runfast ; 次は、スリープ中のプロセスを停止状態に変える場合である。 } else = SIG_DFL) { このシグナルのプロバティカゞデフォル トのストップであれば、ペンディングビットを削除し、補助関数 stop ( ) によりプロセスを強制的に停 止状態にして、継続の前に停止シグナルを提示する。こで注意すべきことは、スリープ中のプロセス が停止状態に変わったからといってスリープ状態の性質が変化したわけではないので、 SIGCONT シグ ナルによって継続させられれば、元のスリープ状態に戻ることがある。また、 psig Ⅱ al ( ) はプロセス のコンテクストにないときや、コンテクスト切替を安全に行えないときにも呼び出されることがあるの で、プロセスの再スケジューリング ( stop ( ) 関数のオプションのひとつである ) は行われない。 それ以外の場合、もしシグナルがプロセスのコンテクストに配送されてデフォルト以外の動作で扱わ れるのであれば ( たとえば捕獲されるかプロセスを終了させる ) 、シグナルが可能な限り迅速に配送され るように ( たとえば、上記の 2 つの場合のどちらも、プロセスは明示的ないし暗黙のうちに終了させら れる場合があり、資源がシステムに返却される ) 、そのプロセスは ( もし優先順位が下げられていたら ) 329
第 6 章シグナルを介したプロセス例外 (kern/sig. c) ◆ 6.3.2.1 sigaction() の実装 この実装では固定数のシグナルを使っていて、 int のビット数 ( 32 個 ) を制限としている ( ただし、 POSIX の記述では任意の数を使ってもいいことになっている ) 。 [ FiIe : /usr/src/kernel/kern/sig ・ c , line : 1 の 5 ] if (sig く = 効Ⅱ sig > = NSIG) return (EINVAL) ; sigmask(sig) ; bit ンデックスは実装済みのシグナルの集合の範囲 ( 1 から 32 まで ) になければならず、それ以外は即座に 最初にシグナル番号 ( sig) を見て、有効なシグナルのインデックスであることをチェックする。イ if (uap—>osa) { [ Fi1e : /usr/src/kernel/kern/sig. c , line : 11 の ] EINVAL 工ラーが返される。 sa—>sa-handler = ps—>ps-sigact [sig] ; sa—>sa—mask = ps—>ps—catchmask [sig] , フラグの組み立て * / sa—>sa-flags ( (ps->ps-sigonstack & bit) ! = の ) sa—>sa—flags ー = SA—ONSTAGK ; ( (ps->ps-sigintr & bit) sa—>sa—flags ト SA-RESTART; (p->p-flag & SNOCLDSTOP) sa—>sa—flags ト SA-NOCLDSTOP; (copyout (), (caddr-t) sa, (caddr-t)uap->osa, return (EFAULT) ; if if if if sizeof (vec))) 次に、・・古い " シグナル動作へのポインタ ( osa) がヌルではないことを確認する。ヌルでなければ、古 いシグナルの情報を ( 新しいシグナル情報へのポインタが与えられている場合は、それによって上書きさ れる前に ) 呼び出し側に渡す。ューザープログラムに見せるためのシグナル動作ステートを組み立てる ために、プロセスの構造体のあちこちに散らばっている情報の断片を組み合わせて一時的な POSIX シ グナル動作構造体バッファ ( sa によって参照される vec ) にまとめる。これはシグナルの実装を高速化 するために配置を変えているからである * 10 。この一時的パッフアを、 c 。 pyout * 11 によって、呼び出し 側プロセスに渡すが、それに失敗した場合はシステムコールを中絶して EFAULT 工ラーを返す。 * 10 シグナル動作のオプションフラグは、プロセス内のワード型ビットべクタとして実装されている。 * 11 386 の場合、 copyout の処理は、 i386 / locore. s が行う。 308
第 6 章シグナルを介したプロセス例外 ( kern / sig. c ) ポイントシグナルは無視されてユーザープロセスに戻ってしまい、そこで再びプレークポイントが生成 このようなシグナルループが永遠に続くことはな psignal(), sig) ; [ Fi1e : /usr/src/kernel/kern/sig. c , line : 941 ] く、プロセスの終了という結果になる。 されるというループに入ってしまう。 386BSD では、 [ FiIe : /usr/src/kernel/kern/sig. c , line : 951 ] 値を設定する。この構造体は、関数呼び出しの前に 0 で初期化されるものとわかっている。 を参照して、プロセスのシグナルビットマスクに、プロセスでシグナルを評価するときのデフォルトの ないときは ) デフォルトステートしてこれを継承する。 siginit() は、すべてのシグナルのプロバティ るために使われる、カーネル内部関数である。また、その子孫のプロセスも、 ( そうしないと初期化され siginit() は、最初のプロセスのシグナルステートを既知のステートに設定して実行可能な状態にす 6.3.17 siginit() とは何か スになれば、そのシグナルの配送ないしデフォルトの効果が発生する。 ルをプロセスのペンディングシグナル集合に追加する。これによって、そのプロセスがカレントプロセ もしシグナルがカレントプロセス以外のプロセスに送られた場合、 psignal() を使って、そのシグナ ◆ 6.3.17.1 siginit() の実装 siginit() の引数は 1 つ、ポインタへのプロセスである。戻り値はない。 siginit (struct proc *p) void 348 に注意してほしい。 ナルを、プロセスの p ー sigign 。 re マスクに追加する。続行シグナルは決して無視してはならないこと 配列に実装されているすべてのシグナルのプロバティを走査し、 ( SIGCONT 以外の ) 無視すべきシグ p->p-sigignore ト sigmask(i) ; if (sigprop [i] & SA- 工 GNORE & & i ! = SIGCONT) ; i く NSIG; i + + ) for (i [ FiIe : /usr/src/kernel/kern/sig. c , line : 956 ]
6.3 kern / sig. c の関数とファイル構成 . 312 ] [ Fi1e : /usr/src/kernel/kern/sig. c , int kill(cp, uap, retval) struct proc *cp; struct args { int pid; int signo ; } *uap ; VOid *retval ; この関数は、 POSIX システムコールの内部ハンドラの規約に従って、呼び出しを行うプロセス / ス レッドへのポインタ、実装される POSIX 関数へのカプセル化された引数群 ( 2 個 ) へのポインタ、そして POSIX 関数の戻り値へのポインタを引数として呼び出される。ハンドラがシステムコールを処理中に 発生したエラーは、この関数の戻り値として返される。 POSIX の kill ( ) 呼び出しで渡される引数は、 シグナルの送り先であるプロセス ID と、そのプロセス ID に向けて渡すべきシグナルである。これらの 情報に矛盾があったり範囲を超えていると、システムコールの処理中にエラーが発生する場合がある。 return (EINVAL) ; if ( (unsigned) uap->signo > = NSIG) [ Fi1e : /usr/src/kernel/kern/sig ・ c , line : 324 ] ◆ 6.3.7.1 k Ⅲ ( ) の実装 319 if (uap->signo) / * 送出すべきシグナルがあるか ? * / return (EPERM) ; if ( !cansignal(cp, p, uap->signo)) / * このシグナルを送る権利があるか ? * / return (ESRCH) ; = の p = pfind(uap->pid) ; / * プロセスを見つける * / if (uap → pid > の { [ Fi1e : /usr/src/kernel/kern/sig. c , line : 328 ] これには何の効果もない。このように、 " ヌル " シグナルは黙って関数を通るが、何の動作も行われない。 にあるかどうかを判定する。ここで存在しないシグナル番号である 0 も、有効な値として通っているが、 指定されたシグナル番号 ( signo ) を調べて、このシステムで実装されている有効なシグナルの範囲内