11.シグナル

シグナルはソフトウェアの割り込みとして働き、アプリケーションに対して、端末での割り込みキーの押下、プロセス間のパイプの破壊、ジョブコントロール機能などのイベントを非同期的に通知する。 シグナルを取り扱うために、プログラムはシグナルのタイプごとにシグナルハンドラを関連付けることができる。 たとえば、割り込みキーシグナルに対してシグナルハンドラを設定することで、プロセスを即座に終了する代わりにユーザに終了していいかどうかを問い合わせられる。 ほとんどの場合、アプリケーションはエラーシグナルに対して、きれいに終了したり、タスクのリトライをしたりしたいと思うだろう。
シグナルが発生すると、割り込まれたメインのコンテキストとは別のシグナルコンテキスト上で、関連付けられたシグナルハンドラの実行が始まる。 シグナルハンドラの実行が終了すると、メインコンテキストの続きが、あたかも割り込みなど起きなかったかのように実行を継続される。
WindowsはANSI標準に互換している最低限のシグナルをサポートしている。 その中でもOSによって提起されるシグナルはさらに少ない。 そのため、Windowsプログラマにとってはシグナルの利便性は限られてしまう。
7章において、ACEリアクタフレームワークによっていくつかのイベントタイプのシグナルイベントを取り扱う方法を説明した。 ここでは、リアクタフレームワークとは無関係に ACE のシグナルハンドリング機能の使い方について説明していく。 これは次のような場合に役に立つ。
この章では、ACE がどれほど簡単に一つ以上のシグナルタイプに対してハンドラを設定できるのかを示す。 まずはじめに、シグナルが起きた時にユーザ定義のハンドラ関数を呼び出す ACE_Sig_Action ラッパーについて見て行く。 次に、特定のシグナルが発生した時に ACE_Event_Handler::handle_signal() コールバックを呼び出す高レベルな ACE_Sig_Handler クラスについて見る。 最後に、特定のコードブロックをシグナルによる割り込みから防御する ACE_Sig_Guard クラスについて見る。

11.1 ラッパーの利用

POSIX では sigaction() 関数の呼び出しによって、特定のシグナルにアクション―コールバック関数の実行―を関連付ける。 ACE は、この sigaction() のラッパーを提供しており、それを使うと登録時の型安全性が保証される。 また、それと同時に ACE は引数に使われる sigset_t の型安全なラッパーも提供する。 この型はシグナルのコレクションを表しており、詳細については 7.3.2 節で説明した。
p.236-238の例はシグナルに対するコールバックの登録をラッパーを用いて行う例だ。
プログラムが実行されると、まずは ACE_Sig_Action クラスを用いてアクションを登録する。 その後、ACE_OS::kill() メソッドによって、明示的にシグナルを発生させる。 このケースでは、まずカレントプロセスに SIGUSR2 を送り、それに続いて SIGUSR1 を送っている。 これらのシグナルはどちらもユーザ定義用のものである。
シグナルハンドラは p.237 のようになっている。 ハンドラ関数のシグネチャに注意してほしい。 この関数は発生したシグナルの種類を示す整数を引数に取り、返り値は void である。 このモデルにおいては、特定のシグナルについて一つだけしかハンドラを関連付けられない。 つまり、一つのシグナル発生に関して複数のコールバック関数を起動することはできない。 一方、例でも示しているが、一つのハンドラ関数を複数種類のシグナルに関連付けることは問題無い。
例のハンドラ関数では、シグナルを受信して処理を行った後に10秒間のスリープに入っている。 実際のプログラムではこのようなことは絶対にしてはいけない。 一般的にシグナルハンドラは軽い処理のみを行うべきで、処理後はすぐに返るのが望ましい。 そうでなければ、実際のプログラムの実行をブロックしてしまう。 また、シグナルコンテキストで行うことができるアクションには制限(詳しくはOSのリファレンスを参照)がある。 シグナルハンドラ関数は、通常シグナルの発生をアプリケーションに通知するのみを仕事とする。
シグナルの発生による実際の処理は、シグナルハンドラから返ってから行うようにします。 このための方法の一つは、ACE_Reactor の通知メカニズム(7.4節参照)を利用して通常の実行コンテキストで処理を行うことです。
p.238にあるのが、シグナルハンドラを登録するためのコードです。
登録には、まずコールバックに利用したい関数のアドレスを渡して ACE_Sig_Action オブジェクトを生成します。 このコンストラクタでは、まだ登録処理は行われません。 実際の登録は、後ろにある SIGUSR1 と SIGUSR2 への register_action() の呼び出しで行われます。
登録の前に ACE_Sig_Set コンテナを作成し、SIGUSR1 を設定してあります。 そして、このオブジェクトをマスクとして ACE_Sig_Action オブジェクトへ登録しています。 これによって、アクションを登録する際に、設定したシグナルについてシグナルハンドラの実行中には応答しなくなるような設定が自動的に行われます。 これはシグナルハンドラ中に明示的に SIGUSR1 シグナルを発生させているため、ハンドラ実行中にそれによる再割り込みが実行されることを防ぐためです。
このマスクの指定は、OS にアクションを登録する前に行うように注意すること。 そうでないと、OS は指定に気付かない場合があり得る。
なお、ACE_Sig_Action オブジェクトは、特定のシグナルに登録を行ったら解放してしまってもよい。 以前のアクション登録を変更したいと思った場合には、新たな ACE_Sig_Action オブジェクトを生成すればよい。
今回の例の main 関数中で、ACE_OS::sleep() による while ループ内の errno のチェックに疑問を持ったかもしれない。 read()、write()、ioctl() などのようなブロックするシステムコールは、シグナルの発生によって解除されるため、このようにする必要があるのだ。 一度これらのブロックが解除されるとシステムコール自身は、シグナルによって割り込まれるか、もしくは errno を EINTR に設定して返る。
プログラムにおけるシグナルの利用は面倒である。 そのため、sigaction() 関数の多くの実装は SA_RESTART フラグを設定することで、シグナルが発生しても実行を継続するように指定することができる。 注意してほしいのは、UNIXやUNIXライクなOSにおいて、この呼び出しが再始動を促すかどうかが異なるということだ。 この点については OS のドキュメントを参照してから利用するようにしてほしい。
一般的にはほとんどの SVR4 UNIX システムは SA_RESTART フラグの利用を提供している。 ACE から利用する場合には ACE_Sig_Action::flags() メソッドに SA_RESTART を指定するか、もしくは ACE_Sig_Action オブジェクトの生成時にコンストラクタにこのフラグを設定すればよい。

11.2 イベントハンドラ

sigaction および sigset_t の型安全な C++ ラッパーを利用することで、オブジェクト指向なハンドラベースのシグナル取り扱い機構を利用できる。 クライアントプログラマは ACE_Sig_Handler クラスを利用することで、ACE_Event_Handler ベースのオブジェクトをシグナルのコールバック用に登録することができる。 ACE_Event_Handler 型はリアクタフレームワークの中心であるが、ACE_Sig_Handler の場合はリアクタへの登録を行わずに利用することが可能である。
クライアントプログラマはまず、ACE_Event_Handler のサブクラスを導出し、handle_signal() メソッドを実装する。
p.240 のソースにおいて、最初は ACE_Event_Handler クラスをパブリックに継承したクラスを定義する。 このイベントハンドラでは signalベースのイベントのみを取り扱うので、実装するのは handle_signal() メソッドのみでよい。 シグナルが発生すると、シグナル番号の他に siginfo_t と ucontext_t がメソッドに渡される。 これらは sigaction() の新しいシグネチャによって利用できる。 siginfo_t および ucontext_t 型については 11.2.1 節と 11.2.2 節で説明する。
プログラムが開始されると、二つのシグナルを処理するためのハンドラオブジェクトをスタック上に作成し、登録する。 登録は ACE_Sig_Handler::register_handler() メソッドによって行われる。 このメソッドは、シグナル番号と、シグナル発生時にコールバックされるべき ACE_Event_Handler オブジェクトのポインタを受け取る。 ここでは ACE_Sig_Action クラスにマスクやフラグを設定したものを渡すこともできる。 また、このメソッドは古いイベントハンドラや ACE_Sig_Action を取得するためにも使える。
コールバックに二つのシグナルハンドラを登録したら、これらに対応する二つのシグナルを発生させる。 シグナルがハンドラによって処理されるまでスリープした後でプログラムは終了する。

11.2.1 sig_info_t 入門

さきほどは handle_signal() メソッドにおいて、siginfo_t パラメータを無視した。 このパラメータには発生されたシグナルについての情報や属性値が含まれている。 siginfo_t が提供する機能を示し、いくつかの例に触れることで、siginfo_t 構造体に含まれるデータを利用するために sigaction のマニュアルを読みたくなるようにしてみせよう。
siginfo_t に含まれる情報は、そのシグナルによって異なる意味になる。 次の例ではシグナルによって処理を分岐し、どの情報が適切なのかを判断している。
例のシグナルハンドラメソッドはまず、%S フォーマット指定子を利用して受け取ったシグナルが何であるかを表示する。 そして、siginfo_t 構造体が提供されていなければ終了する。
どのシグナルについても、si_signo、si_errno、si_code は参照可能である。 既にシグナル番号は表示しているため、errno を表示することにする。 また、シグナルが送られたプロセスのプロセスIDとユーザIDも表示する。 これら(PIDとUID)は POSIX.1b の場合のみ有効な値であるが、この例では有効かどうかは区別しないこととする。
si_code メンバはシグナルがどこから発生したかの情報を提供してくれる。 いくつかの値はシグナル固有であり、その他は全てのシグナルに共通である。 ここでは後者のうちいくつかを調べ、理由に即した説明を表示している。
それに続く例では、どの siginfo_t のパーツが有効かどうかを決めるためにシグナルの値を確認している。 例えば、si_address 属性値は SIGILL、SIGFPE、SIGSEGV、SIGBUS でのみ有効である。 SIGFPE (floating point exception:浮動小数点例外)の場合、シグナルが起きた理由とその発生アドレスを示すために si_code と si_address を利用して説明を表示している。
また、子プロセスの終了を示す SIGCHLD を受けると下の例のようなフィールドを参照できる。
main() 関数では、次のように全てのシグナルを捕捉する設定を signalSet に行い、ハンドラを登録する。 以下のコード断片は SIGCHLD シグナルの処理をテストするために、短命な子プロセスを生成している。
ここではまだ siginfo_t の全ての属性値を紹介してはいない。 その他の詳細については、OS に付属する sigaction のドキュメントを参照してほしい。

11.2.2 ucontext_t 入門

handle_signal() メソッドの最後のパラメータは ucontext_t である。 この構造体は、シグナルが発生する直前におけるCPU状態やFPUレジスタのような実行時コンテキストを含んでいる。 これについての説明はこの章の範囲を越えてしまうため割愛する。 詳細についてはOSやベンダドキュメントを参照し、 ucontext_t 構造体の利用法について学んでほしい。

11.2.3 シグナルハンドラのスタッキング

特定のケースについては、シグナルへ関連付けるハンドラを複数にしたい場合もある。 前の節において、POSIX モデルでは一つのシグナルには一つのハンドラしか関連付けできないと学んだ。 しかし、ACE_Sig_Hanlers クラスを利用することでこの振舞いを変えることができる。 このクラスは特定のシグナルについてハンドラオブジェクトをスタックできるようにする。 前の例を変更してみると p.245 のようになる。
この例で異なっているのは、ACE_Sig_Handler が ACE_Sig_Handlers に変更されている部分だけだ。 これにより、一つのシグナルに対して複数のハンドラを登録できるようになる。 シグナルが発生すると、自動的に全てのハンドラがコールバックされる。

11.3 クリティカルセクションの保護

シグナルは、非同期ソフトウェア割り込みによく似ている。 これらの割り込みは普通は(前の例のように自分で生成しなければ)アプリケーション外から来る。 一度シグナルが起きると、シングルスレッドアプリケーションならば制御スレッドが、マルチスレッドならばそのうちの一つがシグナル処理ルーチンへジャンプし、実行した後、前に行っていた処理に戻る。 このようなシナリオの場合、実行中にどんなシグナルにも割り込まれたくないクリティカルセクションとなるコード範囲があるだろう。
ACE は特定のシグナル(SIGSTOP や SIGKILL など)を除いて、他のシグナルをマスクしたり無効にしたりしてクリティカルセクションを保護するためのスコープベースのクラスを提供している。
p.246-247 の例において、ACE_Sig_Set ss に設定されたシグナル群からコードのクリティカルセクションを保護しているのが ACE_Sig_Guard オブジェクトである。 この例では SIGUSR1 のみ指定されている。 これは、このスコープ内のコードが、スコープが閉じるまでの間 SIGUSR1 から保護されていることを示す。
これを試すのにはプログラムを走らせ、UNIX の kill コマンドで SIGUSR1(シグナル番号 16)をプロセスへ送る。
(訳注:ここの実行結果の3、5、6行目の「$」以降の部分はプログラムからの出力であり、コンソールに入力した文字列ではないことに注意)
プログラムが保護されたブロックから出る前に SIGUSR1 を送ると、その受信と処理はクリティカルセクションを出てから行われている。 これは、クリティカルセクションが SIGUSR1 から保護されているため、発生したシグナルがプロセス上で保留されているからである。 クリティカルセクションから出ると保護が解除され、保留されていたシグナルがプロセスへと伝達される。

11.4 リアクターによるシグナル管理

この章の始めの方でも見たが、リアクタは汎用のイベントディスパッチャであり、ACE_Sig_Handler と同様の方法でシグナルイベントにハンドラを関連付けることができる。 実際の所、多くのリアクタ実装が ACE_Sig_Handler をシグナルディスパッチングに利用している。 覚えておくべき重要な事項は、リアクタフレームワークの他のイベントとは異なり、シグナルハンドラはシグナルコンテキストで実行されるということだ。 しかしながら、リアクタの通知メカニズムを利用することで、通常のコンテキストに処理を戻すこともできる。 これらについて詳しくは 7.4 節を参照してほしい。

Index
Top