このページの目次
シグナル処理のユーザーランド
概要: sigaction → sigframe (ユーザースタック) → SA_RESTART → シグナルスタック (sigaltstack) → signalfd → timerfd → リアルタイムシグナル 対象: glibc + Linux、ユーザーランド視点 (カーネル部分はカーネルドキュメント参照)
概要
シグナルは Unix で最も古くから存在し、現在も活発に使用されている IPC/通知メカニズムです。ユーザーランドプログラミングにおいてシグナルの重要な課題は、「どのように送信するか」(kill システムコール)ではなく、「シグナルがいつ配信され、ハンドラ実行時のスタック状態はどうなっており、ハンドラ返却後にプログラムがどこから再開するか」です。本稿では、ユーザーランドのシグナルハンドラの実行モデルに焦点を当てます。
sigaction: シグナルハンドラのインストール
struct sigaction sa = ;
;
;
// sa_flags の重要なビット:
// SA_SIGINFO: 3引数ハンドラを使用する (siginfo と ucontext を取得可能)
// SA_RESTART: 中断されたシステムコールを自動的に再試行する
// SA_NODEFER: ハンドラ実行中に自身を自動的にブロックしない
// SA_RESETHAND: 1回限りのハンドラ (実行後に SIG_DFL に復元)
// SA_ONSTACK: sigaltstack (独立したシグナルスタック) を使用する
Signal Frame: シグナルハンドラの「幽霊スタック」
// シグナル配信時、カーネルはユーザースタック上に sigframe を作成する:
//
// 通常のスタック (下方成長):
// [user data] ← 元々の RSP
// [sigframe] ← カーネルが構築した「スナップショット」
// ├─ 保存されたレジスタ: RIP, RSP, RFLAGS, 汎用レジスタ
// ├─ siginfo_t (si_signo, si_code, si_addr, ...)
// └─ FPU状態 (XSAVE)
// ← 新しい RSP (シグナルハンドラはここから開始)
// シグナルハンドラの返却:
// sigreturn() → sigframe からすべてのレジスタを復元 → プログラム継続
重要: シグナルハンドラはユーザースタック上で動作します(カーネルスタックではありません)。それは通常のユーザーランド関数です。ただし、呼び出す前にカーネルがレジスタコンテキスト全体を保存し、ハンドラが返却してから復元します。
シグナルとシステムコール
// システムコールは「低速」操作 (パイプ/ソケットからの read) でシグナルによって中断される:
ssize_t ret;
do while ; // 手動で再試行
// または: SA_RESTART → カーネルが自動的に再試行する (ただし、すべてのシステムコールが再起可能ではない)
// 再起可能: read/write/wait/pause/select/poll/...
// 再起不能: sleep (残り時間を返す), connect (エラーを返す), accept
// pselect / ppoll / epoll_pwait:
// 原子操作: シグナルのブロック解除 → 待機 → シグナルのブロック
// 競合状態の回避: ブロック解除と待機の間のシグナル
sigaltstack: 独立したシグナルスタック
// 問題: スタックオーバーフロー → SIGSEGV → シグナルハンドラはスタック上で実行する必要があるが、スタックが満杯!
// 解決策: シグナルハンドラ用に独立したスタック (sigaltstack) を割り当てる
stack_t ss = ;
;
struct sigaction sa = ;
;
// 用途:
// - SIGSEGV ハンドラ (クラッシュ情報の安全な記録)
// - Go runtime: goroutine プリエンプション (SIGURG + 独立シグナルスタック)
// - プロファイリング: SIGPROF ハンドラ
signalfd: シグナルをファイルディスクリプタに変換
// 従来のシグナルの問題: 非同期 → ハンドラは async-signal-safe な関数のみ呼び出せる
// signalfd: シグナルを fd として読み取る (同期) → poll/epoll 可能
sigset_t mask;
;
;
;
; // 先にブロックする (さもないとデフォルト動作)
int sfd = ;
// イベントループ:
struct signalfd_siginfo fdsi;
;
;
// epoll と組み合わせて使用 → すべてのイベントを統一して処理
timerfd: タイマーをファイルディスクリプタに変換
int tfd = ;
struct itimerspec its = ;
;
// 期限到来ごとに → fd が読み取り可能になる → read は 1 (期限到来回数) を返す
uint64_t expirations;
;
リアルタイムシグナル (SIGRTMIN 〜 SIGRTMAX)
POSIX リアルタイムシグナル vs 従来のシグナル:
従来のシグナル (SIGINT, SIGTERM, ...): 複数回送信 → 1回の配信にマージされる可能性がある (キュー=1)
リアルタイムシグナル (SIGRTMIN+): 複数回送信 → 順序どおりキューイング → 消失しない
リアルタイムシグナルは追加データを持てる:
union sigval value;
value.sival_int = 42;
sigqueue(target_pid, SIGRTMIN, value);
デバッグ
# プロセスのシグナル状態
|
# SigBlk: ブロック済み, SigIgn: 無視, SigCgt: キャッチ済み (ハンドラあり)
# SigPnd: スレッドごとの保留, ShdPnd: プロセスごとの保留
# シグナルの送信
# strace でシグナルを確認
参考
- man: sigaction(2), sigaltstack(2), signalfd(2), timerfd_create(2)
- ソースコード: glibc
signal/sigaction.c,sysdeps/unix/sysv/linux/signalfd.c - LWN: "Signalfd and timerfd", "POSIX real-time signals"
キーワード: sigaction, sigframe, SA_RESTART, sigaltstack, SA_ONSTACK, signalfd, timerfd, real-time signals, SIGEV_THREAD