signalfd(2) シグナル受け付け用のファイルディスクリプタを生成する

書式

#include <sys/signalfd.h>

int signalfd(int fd, const sigset_t *mask, int flags);

説明

signalfd() は、呼び出し元宛てのシグナルを受け付けるために使用されるファイル ディスクリプタを生成する。 この方法はシグナルハンドラや sigwaitinfo(2) を用いる方法の代わりとなるものであり、このファイルディスクリプタを select(2), poll(2), epoll(7) で監視できるという利点がある。

mask 引き数には、呼び出し元がこのファイルディスクリプタ経由で受け付けたい シグナル集合を指定する。この引き数で指定するシグナル集合の内容は、 sigsetops(3) で説明されているマクロを使って初期化することができる。 通常、ファイルディスクリプタ経由で受信するシグナル集合は、 そのシグナルがデフォルトの配送方法に基いて処理されるのを防ぐために、 sigprocmask(2) を使ってブロックしておくべきである。 シグナル SIGKILLSIGSTOP を signalfd ファイルディスクリプタ経由で受信することはできない。 これらのシグナルが mask で指定された場合には黙って無視される。

fd 引き数が -1 の場合、 signalfd() は新しいファイルディスクリプタを生成し、 mask で指定されたシグナル集合をそのファイルディスクリプタに関連付ける。 fd 引き数が -1 以外の場合、 fd には有効な既存の signalfd ファイルディスクリプタを指定しなければならず、 そのディスクリプタに関連付けられているシグナル集合は mask を使って置き換えられる。

Linux 2.6.27 以降では、 以下の値のいくつかをビット単位の論理和 (OR) で指定することで、 signalfd() の振舞いを変更することができる。

SFD_NONBLOCK
新しく生成されるオープンファイル記述 (open file description) の O_NONBLOCK ファイルステータスフラグをセットする。 このフラグを使うことで、 O_NONBLOCK をセットするために fcntl(2) を追加で呼び出す必要がなくなる。
SFD_CLOEXEC
新しいファイルディスクリプタに対して close-on-exec (FD_CLOEXEC) フラグをセットする。 このフラグが役に立つ理由については、 open(2) の O_CLOEXEC フラグの説明を参照のこと。

バージョン 2.6.26 以前の Linux では、 flags 引き数は未使用であり、0 を指定しなければならない。

signalfd() が返すファイルディスクリプタは以下の操作をサポートしている。

read(2)
mask に指定されているシグナルのうち一つ以上がそのプロセスに対して 処理待ち (pending) であれば、それらのシグナルの情報が read(2) に渡されたバッファを使って、 signalfd_siginfo 構造体に格納されて返される。 read(2) は、バッファに格納可能な範囲でできるだけ多くの処理待ちのシグナルに ついての情報を返す。 バッファは最低でも sizeof(struct signalfd_siginfo) バイトの大きさがなければならない。 read(2) の返り値は読み出されたトータルのバイト数である。
read(2) が行われた結果、シグナルは消費され、 これらのシグナルはそのプロセスに対しては処理待ちではなくなる (つまり、シグナルハンドラで捕捉されることもなく、 sigwaitinfo(2) を使って受け取ることもできなくなる)。
mask に指定されているシグナルがそのプロセスに対して一つも処理待ちでなければ、 read(2) は、 mask で指定されたシグナルのうちいずれか一つがそのプロセスに対して発生するまで 停止 (block) する、もしくはファイルディスクリプタが非停止 (nonblocking) に設定されている場合はエラー EAGAIN で失敗する。
poll(2), select(2) (と同様の操作)
mask に指定されたシグナルのうち一つ以上がそのプロセスに対して処理待ちであれば、 ファイルディスクリプタは読み出し可能となる (select(2) の readfds 引き数や poll(2) の POLLIN フラグ)。
signalfd ファイルディスクリプタは、これ以外のファイルディスクリプタ 多重 API である pselect(2), ppoll(2), epoll(7) もサポートしている。
close(2)
ファイルディスクリプタがそれ以降は必要なくなった際には、クローズすべきである。 同じ signalfd オブジェクトに関連付けられたファイルディスクリプタが全て クローズされると、そのオブジェクト用の資源がカーネルにより解放される。

signalfd_siginfo 構造体

signalfd ファイルディスクリプタからの read(2) で返される signalfd_siginfo 構造体のフォーマットは以下の通りである。
struct signalfd_siginfo {
struct signalfd_siginfo {
    uint32_t ssi_signo;   /* シグナル番号 */
    int32_t  ssi_errno;   /* エラー番号 (未使用) */
    int32_t  ssi_code;    /* シグナルコード */
    uint32_t ssi_pid;     /* 送信元の PID */
    uint32_t ssi_uid;     /* 送信元の実 UID */
    int32_t  ssi_fd;      /* ファイルディスクリプタ (SIGIO) */
    uint32_t ssi_tid;     /* カーネルタイマ ID (POSIX タイマ)
    uint32_t ssi_band;    /* Band イベント (SIGIO) */
    uint32_t ssi_overrun; /* POSIX タイマのオーバーラン回数 */
    uint32_t ssi_trapno;  /* シグナルの原因となったトラップ番号 */
    int32_t  ssi_status;  /* 終了ステータスかシグナル (SIGCHLD) */
    int32_t  ssi_int;     /* sigqueue(3) から送られた整数 */
    uint64_t ssi_ptr;     /* sigqueue(3) から送られたポインタ */
    uint64_t ssi_utime;   /* 消費したユーザ CPU 時間 (SIGCHLD) */
    uint64_t ssi_stime;   /* 消費したシステム CPU 時間 (SIGCHLD) */
    uint64_t ssi_addr;    /* シグナルを生成したアドレス
                             (ハードウェアが生成したシグナルの場合) */
    uint8_t  pad[X];      /* pad の大きさは 128 バイト
                             (将来のフィールド追加用の場所の確保) */
};
signalfd_siginfo 構造体の各フィールドは、 siginfo_t 構造体の同じような名前のフィールドと同様である。 siginfo_t 構造体については sigaction(2) に説明がある。 返された signalfd_siginfo 構造体の全てのフィールドがあるシグナルに対して有効なわけではない。 どのフィールドが有効かは、 ssi_code フィールドで返される値から判定することができる。 このフィールドは siginfo_tsi_code フィールドと同様である。詳細は sigaction(2) を参照。

fork(2) での扱い

fork(2) が行われると、子プロセスは signalfd ファイルディスクリプタのコピーを 継承する。 子プロセスでこのファイルディスクリプタから read(2) を行うと、子プロセスに対するキューに入っているシグナルに関する 情報が返される。

execve(2) での扱い

他のファイルディスクリプタと全く同様に、 signalfd ファイルディスクリプタも execve(2) の前後でオープンされたままとなる。但し、そのファイルディスクリプタに close-on-exec のマーク (fcntl(2) 参照) が付いている場合はクローズされる。 execve(2) の前に読み出し可能となっていた全てのシグナルは新しく起動されたプログラム でも引き続き読み出し可能である (これは伝統的なシグナルの扱いと同じであり、 処理待ちのブロックされたシグナルは execve(2) の前後で処理待ちのままとなる)。

スレッドでの扱い

マルチスレッドプログラムにおける signalfd ファイルディスクリプタの扱いは シグナルの標準的な扱いと全く同じである。 言い換えると、あるスレッドが signalfd ファイルディスクリプタから 読み出しを行うと、そのスレッド自身宛てのシグナルとプロセス (すなわち スレッドグループ全体) 宛てのシグナルが読み出される。 (スレッドは同じプロセスの他のスレッド宛てのシグナルを読み出すことはできない。)

返り値

成功すると、 signalfd() は signalfd ファイルディスクリプタを返す。 返されるファイルディスクリプタは、 fd が -1 の場合は新規のファイルディスクリプタであり、 fd が有効な signalfd ファイルディスクリプタだった場合は fd 自身である。 エラーの場合、-1 を返し、 errno にエラーを示す値を設定する。

エラー

EBADF
ファイルディスクリプタ fd が有効なファイルディスクリプタでない。
EINVAL
fd が有効な signalfd ファイルディスクリプタではない。
EINVAL
flags が無効である。もしくは、Linux 2.6.26 以前の場合には flags が 0 以外である。
EMFILE
オープン済みのファイルディスクリプタの数がプロセスあたりの上限に 達していた。
ENFILE
オープン済みのファイル総数がシステム全体の上限に達していた。
ENODEV
(カーネル内の) 無名 inode デバイスをマウントできなかった。
ENOMEM
新しい signalfd ファイルディスクリプタを生成するのに十分なメモリがなかった。

バージョン

signalfd() はカーネル 2.6.22 以降の Linux で利用可能である。 正しく動作する glibc 側のサポートはバージョン 2.8 以降で提供されている。 signalfd4() システムコール (「注意」参照) は カーネル 2.6.27 以降の Linux で利用可能である。

準拠

signalfd() と signalfd4() は Linux 固有である。

注意

実際の Linux のシステムコールでは size_t sizemask という引き数が追加で必要である。この引き数で mask のサイズを指定する。 glibc の signalfd() ラッパー関数にはこの引き数は含まれず、 ラッパー関数が必要な値を計算して内部で呼び出すシステムコールに提供する。

一つのプロセスは複数の signalfd ファイルディスクリプタを生成することができる。 これにより、異なるファイルディスクリプタで異なるシグナルを受け取ることが できる (この機能は select(2), poll(2), epoll(7) を使ってファイルディスクリプタを監視する場合に有用かもしれない。 異なるシグナルが到着すると、異なるファイルディスクリプタが利用可能に なるからだ)。 一つのシグナルが二つ以上のファイルディスクリプタの mask に含まれている場合、そのシグナルの発生はそのシグナルを mask に含むファイルディスクリプタのうちいずれか一つから読み出すことができる。

下層にある Linux のシステムコール

下層にある Linux システムコールは二種類あり、 signalfd() と、もっと新しい signalfd4() である。 signalfd() は flags 引き数を実装していない。 signalfd4() では上記の値の flags が実装されている。 glibc 2.9 以降では、 signalfd() のラッパー関数は、 signalfd4() が利用可能であれば、これを使用する。

バグ

カーネル 2.6.25 より前では、 sigqueue(3) により送信されたシグナルと一緒に渡されるデータでは、フィールド ssi_ptrssi_int は設定されない。

下記のプログラムは、シグナル SIGINTSIGQUIT を signalfd ファイルディスクリプタ経由で受信する。 シグナル SIGQUIT 受信後にプログラムは終了する。 以下に示すシェルセッションにこのプログラムの使い方を示す。
$ ./signalfd_demo
^C                   # Control-C generates SIGINT
Got SIGINT
^C
Got SIGINT
^\                    # Control-\ generates SIGQUIT
Got SIGQUIT
$

プログラムのソース

#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
    sigset_t mask;
    int sfd;
    struct signalfd_siginfo fdsi;
    ssize_t s;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    /* Block signals so that they aren't handled
       according to their default dispositions */
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        handle_error("sigprocmask");
    sfd = signalfd(-1, &mask, 0);
    if (sfd == -1)
        handle_error("signalfd");
    for (;;) {
        s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
        if (s != sizeof(struct signalfd_siginfo))
            handle_error("read");
        if (fdsi.ssi_signo == SIGINT) {
            printf("Got SIGINT\n");
        } else if (fdsi.ssi_signo == SIGQUIT) {
            printf("Got SIGQUIT\n");
            exit(EXIT_SUCCESS);
        } else {
            printf("Read unexpected signal\n");
        }
    }
}

この文書について

この man ページは Linux man-pages プロジェクトのリリース 3.65 の一部である。 プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。