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 文件描述符接收 SIGKILL 或 SIGSTOP 信号;如果在 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) 语义
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_ptr 和 ssi_int 字段不会被 sigqueue(3) 发送的信号所携带的数据填充。
示例
下面的程序通过 signalfd 文件描述符接收 SIGINT 和 SIGQUIT 信号。程序在接收到 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)