linux新API--signalfd的使用方法
名字
signalfd - 创建一个用于所受信号的文件描述符概要
#include <sys/signalfd.h>int signalfd(int fd, const sigset_t*mask, intflags);
描述
signalfd() 创建一个可以用于接受以调用者为目标的信号的文件描述符。这提供了一个使用信号处理器或sigwaitinfo(2)的改良方式,并且这种方式存在一个优点就是可以使用select(2)、poll(2) 和epoll(7) 来监视。mask 参数指出想要通过这个文件描述符接受的信号集。这个参数是信号集,它可以使用在 sigsetops(3)说明的宏来初始化。通常情况下,通过这个文件描述符接受的信号应该使用sigprocmask(2)阻塞,这样可以避免触发这些信号的默认处理方式。不可能通过文件描述符接受到信号SIGKILL 或SIGSTOP;如果这两个信号在mask 里指定则会被默默地忽略。
如果 fd 参数是 -1,那么调用创建一个新的文件描述符并且把 mask 指定的信号集与其关联。如果fd 不是 -1,它必须是一个有效的并已经存在的文件描述符,而mask用于替换之前与这个文件描述符关联的信号集。
从 Linux 2.6.27 开始,下面值可以经过位或运算放置在 flags 里以来改变signalfd() 的行为:
- SFD_NONBLOCK
- 在新打开的文件描述符设置 O_NONBLOCK 标记。在 fcntl(2) 中保存这个标记可以得到相同的效果。
- SFD_CLOEXEC
- 在新打开的文件描述符里设置 close-on-exec (FD_CLOEXEC) 标记。参看在open(2) 里关于O_CLOEXEC标记的描述来了解这为什么有用。
在包括 2.6.26 之前的版本里,flags 参数没有使用,并且必须指定为零。
signalfd() 返回的文件描述符支持如下操作:
- read(2)
- 如果有一个或多个在 mask 指定的信号对进程未决,那么 read(2) 提供的缓存区将用于返回一个或多个用于描述信号的signalfd_siginfo 结构(见下面说明)。read(2)尽可能地把未决的信号都返回并且把它们填充到提供的缓存区里。这个缓存区至少要有sizeof(structsignalfd_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; /* 信号编号 */ 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; /* 波动事件 (SIGIO) */ uint32_t ssi_overrun; /* POSIX 定时器超过次数 */ uint32_t ssi_trapno; /* 导致信号的陷阱号 */ int32_t ssi_status; /* 退出状态或信号 (SIGCHLD) */ int32_t ssi_int; /* sigqueue(2) 发送的整数 */ uint64_t ssi_ptr; /* sigqueue(2) 发送的指针 */ uint64_t ssi_utime; /* 用户CPU时消耗 (SIGCHLD) */ uint64_t ssi_stime; /* 系统CPU时消耗 (SIGCHLD) */ uint64_t ssi_addr; /* 产生信号的地址(对硬件信号) */ uint8_t pad[X]; /*填充到 128 字节 (允许将来添加字段) */ };这个结构中的字段与 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) 之后仍然保持打开,除非它被标记为“执行时关闭”(见fcntl(2))。任何在调用execve(2)之前可读的信号在调用之后的新程序里保持可读。(这与传统信号语义相同,当一个阻塞的信号处于未决状态时穿过execve(2) 后仍然未决。线程语义
在多线程程序里的 signalfd 文件描述符语义对应信号标准语义。换句话说,当一个线程读一个 signalfd文件描述符时,它将读取直接发送给其自身的信号或直接发送进程的信号(也就是整个线程组)。(一个线程不能读发送给进程里其它线程的信号。)返回值
成功时,signalfd() 返回一个 signalfd 文件描述符;这是一个全新的文件描述符(如果 fd是 -1),或者fd(如果fd 是一个有效的 signalfd 文件描述符)。错误时,-1被返回并且设置 errno 来指明错误。错误
- EBADF
- fd 文件描述符不是一个有效的文件描述符。
- EINVAL
- fd 不是一个有效的 signalfd 文件描述符。
- EINVAL
- flags 无效; 或者,在 2.6.26 及其前,flags 非零。
- EMFILE
- 达到单个进程打开的文件描述上限。
- ENFILE
- 达到可打开文件个数的系统全局上限。
- ENODEV
- 不能挂载(内部)匿名结点设备。
- ENOMEM
- 没有足够的内存来创建新的 signalfd 文件描述符。
版本
signalfd() 在 Linux 内核 2.6.22 开始有效。glibc 开始支持在版本2.8。signalfd4() 系统调用(看下面注意)在 Linux 内核 2.6.27 开始有效。遵循于
signalfd() 和 signalfd4() 是 Linux 特有的。注意
底层的 Linux 系统调用需要一个额外的参数,size_t sizemask,这个参数指定 mask参数的尺寸。自从可以自己提供这个值给底层系统调用之后,glibcsignalfd() 包装函数不需要这个参数。一个进程可以创建多个 signalfd 文件描述符。这使得可以在不同的文件描述符上接受不同的信号。(这可能是有用的,如果使用select(2)、poll(2) 或epoll(7)来监视这个文件描述符;当不现的信号抵达时会有不同的文件描述符准备好了。)如果一个信号出现多个文件描述符的mask里,那么这个信号的出现会导致这些文件描述符中的某一个且只有一个可读。
底层的 Linux 系统调用
这里有两个底层的 Linux 系统调用:signalfd() 和 最近才有的signalfd4()。之前的系统调用没有实现flags 参数。之后的系统调用实现了上面描述的flags 参数。从 glibc 2.9 开始,signalfd() 包装函数在signalfd4() 有效时使用它。错误
在内核 2.6.25 之前,通过 sigqueue(2)发送信号时 ssi_ptr 和 ssi_int 域没有填充值。示例
下面程序通过 signalfd 文件描述符接受 SIGINT 和 SIGQUIT 信号。这个程序在接受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); /* 阻塞信号以使得它们不被默认的处理试方式处理 */ 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"); } } } /*这个例子只是很简单的说明了使用signalfd的方法,并没有真正发挥它的作用,有了这个API,就可以将信号处理作为IO看待, 每一个信号集合(或者某一个对应的信号)就会有对应的文件描述符,这样将信号处理的流程大大简化,将应用程序中的业务作为文件来操作,也体现了linux下的一切皆文件 的说法,非常好,假如有很多种信号等待着处理,每一个信号描述符对待一种信号的处理,那么就可以将信号文件描述符设置为非阻塞,同时结合epoll使用,对信号的 处理转化为IO复用,和这个有相似之处的API还有timerfd*/