1. シグナルの基本概念
シグナルは、カーネルがユーザー空間のプロセスに送信する非同期通知機構であり、特定のイベント発生を知らせるために利用される。
1.1 シグナルの処理フロー
- 生成:カーネル、他のプロセス、またはハードウェア(例:MMUによる`SIGSEGV`)がシグナルを発生させる。
- 配信:カーネルが対象プロセスにシグナルを伝達し、処理を促す。
- 処理:プロセスがシグナルを受け取り、事前に定義された動作を実行する。
1.2 シグナルの発生源
- ハードウェア異常:
SIGSEGV:無効なメモリアクセス(セグメンテーション違反)SIGFPE:算術エラー(ゼロ除算など)SIGILL:無効な命令実行
- 端末操作:
SIGINT:Ctrl+Cによる中断SIGQUIT:Ctrl+\による終了とコアダンプ生成SIGTSTP:Ctrl+Zによる一時停止
- ソフトウェアイベント:
SIGCHLD:子プロセスの状態変化(終了・停止)SIGPIPE:読み込み側が閉じられたパイプへの書き込みSIGALRM:タイマー(alarm/setitimer)の期限切れSIGUSR1,SIGUSR2:ユーザー定義シグナル
- 他プロセスからの送信:
kill()システムコール経由で発行
1.3 シグナルの処理戦略
- デフォルト動作:多くのシグナルはプロセス終了を引き起こす。一部は無視(
SIGCHLD)やコアダンプ(SIGQUIT)。 - 無視:内核に処理不要と通知(
SIGKILL/SIGSTOPは無視不可)。 - キャッチ:シグナルハンドラ関数を登録。到着時に処理を中断してハンドラを実行。
1.4 主要なシステムコール
signal():簡易なハンドラ登録(非推奨、互換性劣る)sigaction():標準的かつ強力なハンドラ設定方法kill():指定プロセスへシグナル送信alarm():タイムアウト後にSIGALRMを発生pause():シグナル受信までプロセスを待機sigemptyset(),sigaddset(),sigprocmask():シグナルマスク管理
1.5 注意点
- 非同期性:任意のタイミングで到着可能
- 信頼性:標準シグナル(1-31)は不可靠(再発生時は無視)。リアルタイムシグナル(34-64)はキューイング可能
- 再入安全性:ハンドラ内では
write()や_exit()など、非同期信号安全な関数のみ使用可。malloc()やprintf()は避ける - volatile宣言:ハンドラとメインコード間の共有変数は
volatile修飾子を付与
2. シグナルの分類と用途
2.1 終了シグナル
| シグナル | 値 | デフォルト | 説明 | 捕獲/無視可否 | コアダンプ |
|---|---|---|---|---|---|
SIGHUP | 1 | 終了 | 端末切断時、親プロセスグループに送信。リロード通知にも利用 | 是 | 否 |
SIGINT | 2 | 終了 | Ctrl+Cによる中止。前向きな終了を促す | 是 | 否 |
SIGQUIT | 3 | コアダンプ | Ctrl+\で終了+ダンプ生成。デバッグ用 | 是 | 是 |
SIGTERM | 15 | 終了 | killのデフォルト。クリーンアップ可能な終了 | 是 | 否 |
SIGKILL | 9 | 終了 | 強制終了。無視・捕捉不可。最後の手段 | 否 | 否 |
2.2 異常シグナル
| シグナル | 値 | デフォルト | 説明 | 捕獲/無視可否 | コアダンプ |
|---|---|---|---|---|---|
SIGILL | 4 | コアダンプ | 無効な命令実行。実行ファイル破損など | 是 | 是 |
SIGTRAP | 5 | コアダンプ | ブレークポイントやトレーストラップ。デバッガ用 | 是 | 是 |
SIGABRT | 6 | コアダンプ | プログラム自身がabort()呼出。アサート失敗でも発生 | 是 | 是 |
SIGBUS | 7 | コアダンプ | 物理アドレス不正・アライメント違反 | 是 | 是 |
SIGFPE | 8 | コアダンプ | ゼロ除算・オーバーフロー | 是 | 是 |
SIGSEGV | 11 | コアダンプ | 最も一般的なメモリアクセス違反。ポインタ誤用の兆候 | 是 | 是 |
2.3 作業制御シグナル
| シグナル | 値 | デフォルト | 説明 | 捕獲/無視可否 |
|---|---|---|---|---|
SIGSTOP | 17,19,23 | 停止 | プロセス実行の一時停止。不可捕捉・不可無視 | 否 |
SIGTSTP | 18,20,24 | 停止 | Ctrl+Zによる停止。後で再開可能 | 是 |
SIGCONT | 19,18,25 | 継続 | 停止したプロセスの再開。無視しても有効 | 是 |
2.4 I/O関連シグナル
| シグナル | 用途 | 推奨処理 |
|---|---|---|
SIGPIPE | パイプ断接後の書き込みエラー | 無視(SIG_IGN)し、EPIPEエラーを検査 |
SIGIO | ファイルディスクリプタの準備完了通知 | 高負荷アプリではepoll優先 |
SIGURG | 帯外データ(OOB)処理 | 緊急コマンド受信用にハンドラ設置 |
SIGCHLD | 子プロセスリソース回収 | 子プロセス終了時にwaitpid()呼び出し必須 |
2.5 その他の重要シグナル
| シグナル | 値 | デフォルト | 説明 | 捕獲/無視可否 |
|---|---|---|---|---|
SIGCHLD | 17,20,18 | 無視 | 子プロセスの終了・停止時、親に通知。リソース回収に必須 | 是 |
SIGPIPE | 13 | 終了 | 読端が閉じられたパイプへの書き込み | 是 |
SIGALRM | 14 | 終了 | alarm()やsetitimer()によるタイマー切れ | 是 |
SIGUSR1 | 10,30,16 | 終了 | ユーザー定義1。用途はアプリケーションで定義 | 是 |
SIGUSR2 | 12,31,17 | 終了 | ユーザー定義2。ログ切り替え・状態報告などに利用 | 是 |
3. 実装例
3.1 SIGPIPEの回避
ネットワークサーバーでクライアント切断後に応答しようとした場合、send()がSIGPIPEを送出しプロセスが終了する。
// 初期化時に無視
#include <signal.h>
signal(SIGPIPE, SIG_IGN);
// 以降、I/O関数はエラー返却
ssize_t sent = send(sockfd, data, len, MSG_NOSIGNAL);
if (sent == -1 && errno == EPIPE) {
close(sockfd); // リソース解放
}
3.2 SIGIOによる非同期I/O
イベント駆動型の非同期処理を実現する。
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
void io_callback(int sig, siginfo_t *info, void *context) {
int fd = info->si_fd;
int event = info->si_band;
// ファイルディスクリプタの準備完了処理
}
int main() {
struct sigaction sa = {0};
sa.sa_sigaction = io_callback;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sigaction(SIGIO, &sa, NULL);
int fd = open("/dev/device", O_RDONLY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC | fcntl(fd, F_GETFL));
// 他の処理...
}
注:現代のネットワークアプリではepollの方が性能的に優れている。
3.3 SIGURGによる緊急コマンド受信
ネットワーク経由で「緊急停止」などの緊急指令を受信。
// 送信側
send(sockfd, "!", 1, MSG_OOB);
// 受信側
void urg_handler(int sig) {
char urgent_data;
recv(sockfd, &urgent_data, 1, MSG_OOB);
// 緊急処理
}
// シグナル登録
signal(SIGURG, urg_handler);
fcntl(sockfd, F_SETOWN, getpid());
3.4 SIGCHLDによる子プロセス管理
並列サーバーで子プロセスが終了した際にゾンビ化を防ぐ。
void child_reaper(int sig) {
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0) {
continue;
}
errno = saved_errno;
}
int main() {
struct sigaction sa = {0};
sa.sa_handler = child_reaper;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
while(1) {
pid_t pid = fork();
if (pid == 0) {
// 子プロセス処理
exit(0);
}
// 親プロセスはループ継続
}
}