signalfd(2) - Linux 手册页

名称

signalfd - 创建一个用于接收信号的文件描述符

概要

#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) 阻塞要通过该文件描述符接收的信号集,以防止这些信号按照其默认行为进行处理。无法通过 signalfd 文件描述符接收 SIGKILLSIGSTOP 信号;如果在 mask 中指定了这些信号,它们将被静默忽略。

如果 fd 参数为 -1,则该调用会创建一个新的文件描述符,并将 mask 中指定的信号集与该描述符关联。如果 fd 不为 -1,则它必须指定一个现有的有效 signalfd 文件描述符,此时 mask 将用于替换与该描述符关联的信号集。

从 Linux 2.6.27 开始,可以在 flags 中按位或以下值来改变 signalfd() 的行为:

SFD_NONBLOCK

在新的打开文件描述上设置 O_NONBLOCK 文件状态标志。使用此标志可以省去额外的 fcntl(2) 调用以达到同样的效果。

SFD_CLOEXEC

在新的文件描述符上设置执行时关闭(FD_CLOEXEC)标志。有关此标志为何有用的原因,请参阅 open(2) 中关于 O_CLOEXEC 标志的说明。

在 Linux 2.6.26 及之前版本中,flags 参数未被使用,且必须指定为零。

signalfd() 返回一个支持以下操作的文件描述符:

read(2)
如果进程有 mask 中指定的一个或多个挂起信号,则提供给 read(2) 的缓冲区将被用于返回一个或多个描述这些信号的 signalfd_siginfo 结构体(见下文)。read(2) 会返回尽可能多且能放入缓冲区中的挂起信号信息。缓冲区大小必须至少为 sizeof(struct signalfd_siginfo) 字节。read(2) 的返回值是读取的总字节数。
作为 read(2) 的结果,这些信号会被消耗,因此它们不再处于进程的挂起状态(即不会被信号处理程序捕获,也不能使用 sigwaitinfo(2) 接收)。

如果进程没有 mask 中的任何挂起信号,则 read(2) 将阻塞,直到有一个 mask 中的信号发送给进程,或者如果文件描述符已被设置为非阻塞,则会失败并返回错误 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 结构体

通过 read(2) 从 signalfd 文件描述符返回的 signalfd_siginfo 结构体的格式如下:
struct signalfd_siginfo {
    uint32_t ssi_signo;   /* Signal number */
    int32_t  ssi_errno;   /* Error number (unused) */
    int32_t  ssi_code;    /* Signal code */
    uint32_t ssi_pid;     /* PID of sender */
    uint32_t ssi_uid;     /* Real UID of sender */
    int32_t  ssi_fd;      /* File descriptor (SIGIO) */
    uint32_t ssi_tid;     /* Kernel timer ID (POSIX timers)
    uint32_t ssi_band;    /* Band event (SIGIO) */
    uint32_t ssi_overrun; /* POSIX timer overrun count */
    uint32_t ssi_trapno;  /* Trap number that caused signal */
    int32_t  ssi_status;  /* Exit status or signal (SIGCHLD) */
    int32_t  ssi_int;     /* Integer sent by sigqueue(3) */
    uint64_t ssi_ptr;     /* Pointer sent by sigqueue(3) */
    uint64_t ssi_utime;   /* User CPU time consumed (SIGCHLD) */
    uint64_t ssi_stime;   /* System CPU time consumed (SIGCHLD) */
    uint64_t ssi_addr;    /* Address that generated signal
                             (for hardware-generated signals) */
    uint8_t  pad[X];      /* Pad size to 128 bytes (allow for
                              additional fields in the future) */
};
该结构体中的每个字段都类似于 siginfo_t 结构体中同名的字段。siginfo_t 结构体在 sigaction(2) 中有描述。并非返回的 signalfd_siginfo 结构体中的所有字段对特定信号都有效;有效字段集可以通过 ssi_code 字段中返回的值来确定。此字段类似于 siginfo_t si_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 本身(如果 fd 是一个有效的 signalfd 文件描述符)。出错时,返回 -1 并设置 errno 以指示错误。

错误

EBADF

fd 文件描述符不是一个有效的文件描述符。

EINVAL

fd 不是一个有效的 signalfd 文件描述符。

EINVAL

flags 无效;或者在 Linux 2.6.26 或更早版本中,flags 为非零值。

EMFILE

已达到每个进程打开文件描述符的限制。

ENFILE

已达到系统范围打开文件总数的限制。

ENODEV

无法挂载(内部)匿名 inode 设备。

ENOMEM

没有足够的内存来创建新的 signalfd 文件描述符。

版本

signalfd() 从 Linux 2.6.22 内核版本开始提供。glibc 从 2.8 版本开始提供完整支持。signalfd4() 系统调用(见注释)从 Linux 2.6.27 内核版本开始提供。

符合

signalfd() 和 signalfd4() 是 Linux 特有的。

说明

底层的 Linux 系统调用需要一个额外的参数 size_t sizemask,用于指定 mask 参数的大小。glibc 的 signalfd() 包装函数不包含此参数,因为它为底层系统调用提供了所需的值。

进程可以创建多个 signalfd 文件描述符。这使得在不同的文件描述符上接收不同的信号成为可能。(如果使用 select(2)、poll(2) 或 epoll(7) 监控这些文件描述符,这可能很有用:不同信号的到达会使不同的描述符就绪。)如果一个信号出现在多个文件描述符的 mask 中,那么该信号的发生(仅一次)可以从其中任何一个描述符中读取到。

底层 Linux 系统调用

底层有两个 Linux 系统调用:signalfd() 和较新的 signalfd4()。前者不实现 flags 参数。后者实现了上述的 flags 值。从 glibc 2.9 开始,signalfd() 包装函数在可用时会使用 signalfd4()。

错误

在 2.6.25 之前的内核中,ssi_ptrssi_int 字段不会被 sigqueue(3) 发送的信号所携带的数据填充。

示例

下面的程序通过 signalfd 文件描述符接收 SIGINTSIGQUIT 信号。程序在接收到 SIGQUIT 信号后终止。以下 shell 会话演示了该程序的使用。

$ ./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");
        }
    }
}

参见

eventfd(2), poll(2), read(2), select(2), sigaction(2), sigprocmask(2), sigwaitinfo(2), timerfd_create(2), sigsetops(3), sigwait(3), epoll(7), signal(7)

引用自

explain(1), explain(3), explain_signalfd(3), explain_signalfd_or_die(3), signal(2), sigtimedwait(2), timer_getoverrun(2)