Unix/Linux编程:通过文件描述符来获取信号------signalfd()
从内核2.6.22开始,Linux提供了(非标准的)signalfd()系统调用:利用该调用可以创建一个特殊文件描述符,发往调用者的信号都可从该描述符中读取。signalfd机制为同步接受信号提供了sigwaitinfo()之外的另一种选择。
NAME
signalfd - create a file descriptor for accepting signals
SYNOPSIS
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);
DESCRIPTION
signalfd() 创建一个文件描述符,可用于接受以调用者为目标的信号。这提供了使用信号处理程序
或sigwaitinfo(2)的替代方法,并且具有文件描述符可由select(2)、poll(2)和epoll(7)监视
的优点。
mask 指定了有意通过 signalfd 文件描述符来读取的信号
此参数是一个信号集,其内容可以使用sigsetops(3)中描述的宏进行初始化。
如同sigwaitinfo()一样,通常也应该使用 sigprocmask()阻塞 mask 中的所有信号,以确
保在有机会读取这些信号之前,不会按照默认处置对它们进行处理
不可能通过signalfd文件描述符接收SIGKILL或SIGSTOP信号;如果在mask中指定,这些信号将被忽略。
如果指定 fd 为−1,那么 signalfd()会创建一个新的文件描述符,用于读取 mask 中的信号;
如果fd不是-1,那么将修改与 fd 相关的 mask 值,且该 fd 一定是由之前对 signalfd()的一次调用创建
而成
在版本2.6.26之前的Linux中,flags参数未使用,必须指定为零。
从Linux 2.6.27开始,以下值可以在标志中按位“或”来更改signalfd()的行为:
SFD_NONBLOCK 为底层的打开文件描述设置 O_NONBLOCK 标志,以确保不会阻塞未来的读操作。
既省去了一个额外的 fcntl()调用,又获得了相同的结果。
SFD_CLOEXEC 请参阅open(2)中的O_CLOEXEC 标志的描述,以了解这可能有用的原因。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
创建文件描述符之后,可以使用 read()调用从中读取信号。提供给 read()的缓冲区必须足够大,至少应能够容纳一个 signalfd_siginfo 结构。<sys/signalfd.h>文件定义了该结构,如下所示:
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) */
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
该结构中字段所返回的信息与传统 siginfo_t 结构中类似命名的字段信息相同
read()每次调用都将返回与等待信号数目相等的signalfd_siginfo 结构,并填充到已提供的缓冲区中。如果调用时并无信号正在等待,那么read()将会阻塞,直到有信号到达。也可以使用fcntl()的F_SETFL 操作来为文件描述符设置 O_NONBLOCK 标志,使得读操作不再阻塞,且若无信号等待,则调用失败,errno 为 EAGAIN。
当从 signalfd 文件描述符中读取到一信号时,该信号获得接纳,且不再为该进程而等待。
select()、poll()和 epoll可以将 signalfd 描述符和其他描述符混合起来进行监控。
当不再需要 signalfd 文件描述符时,应当关闭 signalfd 以释放相关内核资源
看个例子:下面是接收函数
// signalfd_sigbval
#include <cstring>
#include <signal.h>
#include <stdio.h>
#include <cstdlib>
#include <zconf.h>
#include <sys/signalfd.h>
#include <signal.h>
int main(int argc, char *argv[])
{
if (argc < 2 || strcmp(argv[1], "--help") == 0){
printf("%s sig-num...\n", argv[0]);
exit(1);
}
printf("%s: PID = %ld\n", argv[0], (long) getpid());
// 为在命令行参数中指定的信号创建掩码
sigset_t mask;
sigemptyset(&mask);
for (int j = 1; j < argc; j++)
sigaddset(&mask, atoi(argv[j]));
// 阻塞这些信号
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1){
perror("sigprocmask");
exit(1);
}
// 创建用来读取这些信号的 signalfd 文件描述符
int sfd;
sfd = signalfd(-1, &mask, 0);
if (sfd == -1){
perror("signalfd");
exit(1);
}
ssize_t s;
struct signalfd_siginfo fdsi;
for (;;) {
// 循环从文件描述符sfd中读取信号,并显示返回的 signalfd_siginfo 结构中的部分信息
s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo)); //read()将会阻塞,直到有信号到达
if (s != sizeof(struct signalfd_siginfo)){
perror("read");
exit(1);
}
printf("%s: got signal %d", argv[0], fdsi.ssi_signo);
if (fdsi.ssi_code == SI_QUEUE) {
printf("; ssi_pid = %d; ", fdsi.ssi_pid