シグナルの基礎概念
シグナルはカーネルがプロセスへ通知するソフトウェア割り込みである。man 7 signalで詳細を確認できる。
- 各プロセスは起動時にデフォルトのシグナルハンドラを持つ
- シグナルは即座に処理されるとは限らず、ペンディング状態で留まることもある
- プロセスは受信済みシグナルを記録できる
フォアグラウンド/バックグラウンドプロセス
| 種別 | 端末占有 | キーボード割り込み | 例 |
|---|---|---|---|
| フォアグラウンド | 占有 | 受信可 | ./app |
| バックグラウンド | 非占有 | 受信不可 | ./app & |
Ctrl+Cはフォアグラウンドプロセスにのみ SIGINT を送信する。
シグナルの処理方法
- デフォルト動作 (
SIG_DFL) - 無視 (
SIG_IGN) - カスタムハンドラ (
signal/sigaction)
カスタムハンドラ実装例
#include <iostream>
#include <signal.h>
#include <unistd.h>
int main() {
{
signal(SIGINT, [](int signum){
std::cout << "Caught SIGINT (" << signum << ")\n";
});
while (true) {
std::cout << "Running...\n";
sleep(1);
}
}
SIGKILL (9) と SIGSTOP (19) は捕捉・ブロックできない。
シグナルの発生源
1. 端末からのキー入力
- Ctrl+C →
SIGINT - Ctrl+\ →
SIGQUIT - Ctrl+Z →
SIGTSTP
2. kill コマンド / システムコール
kill -TERM 1234 # PID 1234へSIGTERMを送信
kill -9 1234 # SIGKILLを送信(強制終了)
#include <sys/types.h>
#include <signal.h>
kill(getpid(), SIGUSR1); // 自プロセスへSIGUSR1
raise(SIGUSR1); // 同上
abort(); // 自プロセスへSIGABRT(コアダンプ生成)
3. ハードウェア例外
ゼロ除算や不正メモリ参照が発生するとCPU例外が発生し、カーネルは対応するシグナルを送信する。
int x = 5 / 0; // SIGFPEが送出される
4. ソフトウェア条件
- パイプの読み取り側が閉じた →
SIGPIPE alarm()タイマー →SIGALRM
コアダンプとデバッグ
コアダンプを有効にするとクラッシュ時にメモリイメージが保存される。
ulimit -c unlimited # コアファイルサイズ制限を解除
生成された core.<pid> を gdb ./a.out core.<pid> で解析できる。
シグナルの内部構造
カーネルは各タスクに対して以下のビットマスクを保持する。
- pending:未処理シグナル集合
- blocked:ブロック中シグナル集合
- handler:シグナルごとのハンドラテーブル
シグナルマスク操作API
sigset_t mask, old;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, &old); // SIGINTをブロック
// ... クリティカルセクション ...
sigprocmask(SIG_SETMASK, &old, nullptr); // 元に戻す
シグナル捕捉フロー
プロセスがカーネル空間からユーザ空間へ復帰する際に、ペンディングシグナルをチェックしハンドラを実行する。
- ユーザ空間 → システムコール → カーネル空間
- カーネル空間でシグナルチェック
- ハンドラが設定されていればユーザ空間へ戻りハンドラ実行
- ハンドラ終了後、元の実行コンテキストへ復帰
子プロセス終了通知 SIGCHLD
子プロセスが終了すると親へ SIGCHLD が送信される。
#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
void on_child_exit(int) {
while (true) {
pid_t pid = waitpid(-1, nullptr, WNOHANG);
if (pid <= 0) break;
std::cout << "Child " << pid << " reaped\n";
}
}
int main() {
signal(SIGCHLD, on_child_exit);
for (int i = 0; i < 3; ++i) {
if (fork() == 0) {
sleep(2);
exit(0);
}
}
pause(); // 子プロセス全終了を待つ
}
また、signal(SIGCHLD, SIG_IGN) とすることでゾンビプロセスを発生させずに子プロセスを自動回収できる(Linux固有)。