このページの目次

シグナル処理のユーザーランド

概要: sigaction → sigframe (ユーザースタック) → SA_RESTART → シグナルスタック (sigaltstack) → signalfd → timerfd → リアルタイムシグナル 対象: glibc + Linux、ユーザーランド視点 (カーネル部分はカーネルドキュメント参照)

概要

シグナルは Unix で最も古くから存在し、現在も活発に使用されている IPC/通知メカニズムです。ユーザーランドプログラミングにおいてシグナルの重要な課題は、「どのように送信するか」(kill システムコール)ではなく、「シグナルがいつ配信され、ハンドラ実行時のスタック状態はどうなっており、ハンドラ返却後にプログラムがどこから再開するか」です。本稿では、ユーザーランドのシグナルハンドラの実行モデルに焦点を当てます。

sigaction: シグナルハンドラのインストール

#include <signal.h>

struct sigaction sa = {
    .sa_sigaction = handler,   // void handler(int sig, siginfo_t *info, void *ucontext)
    .sa_flags     = SA_SIGINFO | SA_RESTART,
    .sa_mask      = 0,          // ハンドラ実行中にブロックするシグナル
};
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);

// 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 {
    ret = read(fd, buf, size);
} while (ret == -1 && errno == EINTR);  // 手動で再試行

// または: SA_RESTART → カーネルが自動的に再試行する (ただし、すべてのシステムコールが再起可能ではない)
//   再起可能: read/write/wait/pause/select/poll/...
//   再起不能: sleep (残り時間を返す), connect (エラーを返す), accept

// pselect / ppoll / epoll_pwait:
//   原子操作: シグナルのブロック解除 → 待機 → シグナルのブロック
//   競合状態の回避: ブロック解除と待機の間のシグナル

sigaltstack: 独立したシグナルスタック

// 問題: スタックオーバーフロー → SIGSEGV → シグナルハンドラはスタック上で実行する必要があるが、スタックが満杯!
// 解決策: シグナルハンドラ用に独立したスタック (sigaltstack) を割り当てる

stack_t ss = {
    .ss_sp    = sigstack_mem,          // 独立したメモリ領域
    .ss_size  = SIGSTKSZ,              // 通常 8KB 以上
    .ss_flags = 0,
};
sigaltstack(&ss, NULL);

struct sigaction sa = {
    .sa_sigaction = handler,
    .sa_flags     = SA_ONSTACK | SA_SIGINFO,  // ← SA_ONSTACK!
};
sigaction(SIGSEGV, &sa, NULL);

// 用途:
//   - SIGSEGV ハンドラ (クラッシュ情報の安全な記録)
//   - Go runtime: goroutine プリエンプション (SIGURG + 独立シグナルスタック)
//   - プロファイリング: SIGPROF ハンドラ

signalfd: シグナルをファイルディスクリプタに変換

// 従来のシグナルの問題: 非同期 → ハンドラは async-signal-safe な関数のみ呼び出せる
// signalfd: シグナルを fd として読み取る (同期) → poll/epoll 可能

#include <sys/signalfd.h>

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);    // 先にブロックする (さもないとデフォルト動作)

int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);

// イベントループ:
struct signalfd_siginfo fdsi;
read(sfd, &fdsi, sizeof(fdsi));
printf("got signal %d\n", fdsi.ssi_signo);

// epoll と組み合わせて使用 → すべてのイベントを統一して処理

timerfd: タイマーをファイルディスクリプタに変換

#include <sys/timerfd.h>

int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);

struct itimerspec its = {
    .it_interval = { .tv_sec = 1, .tv_nsec = 0 },  // 周期 1秒
    .it_value    = { .tv_sec = 1, .tv_nsec = 0 },  // 初回 1秒
};
timerfd_settime(tfd, 0, &its, NULL);

// 期限到来ごとに → fd が読み取り可能になる → read は 1 (期限到来回数) を返す
uint64_t expirations;
read(tfd, &expirations, sizeof(expirations));

リアルタイムシグナル (SIGRTMIN 〜 SIGRTMAX)

POSIX リアルタイムシグナル vs 従来のシグナル:
  従来のシグナル (SIGINT, SIGTERM, ...): 複数回送信 → 1回の配信にマージされる可能性がある (キュー=1)
  リアルタイムシグナル (SIGRTMIN+):       複数回送信 → 順序どおりキューイング → 消失しない

リアルタイムシグナルは追加データを持てる:
  union sigval value;
  value.sival_int = 42;
  sigqueue(target_pid, SIGRTMIN, value);

デバッグ

# プロセスのシグナル状態
cat /proc/<pid>/status | grep Sig
# SigBlk: ブロック済み, SigIgn: 無視, SigCgt: キャッチ済み (ハンドラあり)
# SigPnd: スレッドごとの保留, ShdPnd: プロセスごとの保留

# シグナルの送信
kill -s SIGUSR1 <pid>
kill -s SIGRTMIN+1 <pid>   # リアルタイムシグナル

# strace でシグナルを確認
strace -e trace=signal ./prog

参考

  • 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