一、信号概述
1.发送信号
【1】
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
该函数将信号sig发送给目标进程;目标进程由pid指定,可能取值如下:
pid参数 | 含义 |
---|---|
pid>0 | 信号发送给PID为pid 的进程。 |
pid=0 | 信号发送给本进程组内的其他进程 |
pid = -1 | 信号发送给除init进程外的所有进程,但发送者需要拥有对目标进程发送信号的权限 |
pid < -1 | 信号发送给组ID为-pid的进程组中的所有成员 |
【2】
linux定义的信号值均大于0,如果sig取值为0则kill函数不发送任何信号。将sig值设为0时,可用来检测目标进程或进程组是否存在,因为检查在信号发送之前执行。但是这种检测方式是不可靠的,因为一方面由于进程PID的回绕,可能导致被检测到的pid不是我们所期望的pid,另一个方面,这种检测方法不是原子操作。
【3】函数成功时返回0,失败时返回-1并设置errno:
errno | 含义 |
---|---|
EINVAL | 无效的信号 |
EPERM | 该进程没有权限发送信号给任何一个目标进程 |
ESRCH | 目标进程或进程组不存在 |
2.信号处理方式
【1】目标进程收到信号时,需定义接收函数来处理
#include <signal.h>
typedef void (*__sighandler_t) (int);
信号处理函数只带一个整型参数用来只是信号类型。
【2】除了用户自定义信号处理函数之外,bits/signum.h头文件中还定义了信号的两种其他处理函数。
#include <bits/signum.h>
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)
SIG_IGN 表示忽略目标信号,SIG_DFL表示使用默认处理方式。
默认处理方式有:
结束进程
忽略信号
结束进程并生成核心转储文件
暂停进程
继续进程
3.linux信号
linux可用的信号都定义在bits/signum.h头文件中。
信号 | 默认行为 | 含义 |
---|---|---|
SIGHUP | Term | 终止控制终端或进程 |
SIGINT | Term | 键盘产生的中断(Ctrl-C) |
SIGQUIT | Core | 键盘产生的退出 |
SIGILL | Core | 非法指令 |
SIGTRAP | Core | 断点陷阱,用于调试 |
SIGABRT/SIGIOT | Core | 进程调用abort函数时生成该信号 |
SIGBUS | Core | 总线异常/错误内存访问 |
SIGFPE | Core | 浮点异常 |
SIGKILL | Term | 终止一个进程,该信号不可被捕获或者忽略 |
SIGUSR1 | Term | 用户自定义信号 |
SIGSEGV | Core | 非法内存地址引用 |
SIGUSR2 | Term | 用户自定义信号 |
SIGPIPE | Term | 向读端被关闭的管道或者socket连接张写数据 |
SIGALRM | Term | 时钟中断(闹钟) |
SIGTERM | Term | 进程终止 |
SIGSTKFLT | Term | 协处理器栈错误 |
SIGCHLD | Ign | 子进程退出或中断 |
SIGCONT | Cont | 启动被暂停的进程 |
SIGSTOP | Stop | 暂停进程 |
SIGSTP | Stop | 挂起进程 |
SIGTTIN | Stop | 后台进程试图从终端读取输入 |
SIGTTOU | Stop | 后台进程试图往终端输出内容 |
SIGURG | Ign | socket接收到紧急数据 |
SIGXCPU | Core | 进程的CPU使用时间超过其软限制 |
SIGXFSZ | Core | 文件大小超过其软限制 |
SIGVTALRM | Term | 只统计本进程用户空间代码的运行时间 |
SIGPROF | Term | 统计用户代码和内核的运行时间 |
SIGWINCH/SIGPOLL | Ign | 窗口尺寸调整 |
SIGIO | Term | I/O可用 |
SIGPWR | Term | 电源异常 |
SIGSYS | Core | 非法系统调用 |
SIGUNUSED | Core | 保留,通常和SIGSYS效果相同 |
4.中断系统调用
程序在执行处于阻塞状态的系统调用时接收到信号,并且为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。
二、信号函数
1.signal系统调用
为一个信号设置处理函数
#include<signal.h>
_sighandler_t signal (int sig, _sighandler_t _handler)
- sig参数指出要捕获的信号类型。_handler指定信号sig的处理函数。
- 函数成功时返回一个函数指针,该函数指针也是_sighandler_t类型。返回值是前一次调用signal函数时传入的函数指针,或是信号sig对应的默认处理函数指针SIG_DEF
- signal系统调用出错时返回SIG_ERR,并设置errno
2.sigaction系统调用
#include<signal.h>
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
- sig指出要捕获的信号类型,act指定新的信号处理方式,oact输出信号先前的处理方式。act和oact都是sigaction结构体类型的指针,其定义如下:
struct sigaction
{
#ifdef __USE_POSIX199309
union
{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int, struct siginfo *, void *);
}
_sigaction_handler;
#define sa_handler _sigaction_handler.sa_handler
#define sa_sigaction _sigaction_handler.sa_sigaction
#else
_sighandler_t sa_handler;
#endif
_sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
};
sa_hander 成员指定信号处理函数 sa_mask 成员设置进程的信号掩码 sa_flags成员设置程序收到信号时的行为。
三、信号集
1.信号集函数
【1】Linux使用数据结构sigset_t来表示一组信号,其定义如下:
#include <bits/sigset.h>
#define _SIGSET_NWORDS(1024 / (8 * sizeof(unsigned long int)))
typedef struct
{
unsignedlong int __val[_SIGSET_NWORDS];
}__sigset_t;
sigset_t实际上是一个长整型数组,数组的每个元素的每个位表示一个信号。这种定义方式和文件描述符集fd_set类似。
【2】Linux提供了如下一组函数来设置、修改、删除和查询信号集:
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集
int sigfillset(sigset_t *set); //在信号集中设置所有信号
int sigaddset(sigset_t *set, int signum); //将信号signum添加到set信号集中
int sigdelset(sigset_t *set, int signum); //删除信号
int sigismember(const sigset_t *set, intsignum); //测试信号是否在信号集中
2.进程信号掩码
#include<signal.h>
intsigprocmask(int _how, _const sigset_t *_set, sigset_t *_oset);
_set参数指定新的信号掩码,_oset参数则输出原来的信号掩码(如果不为NULL的话)。如果_set参数不为NULL,则_how参数指定设置进程信号掩码的方式,其可选值如下表所示:
how | 参数含义 |
---|---|
SIG_BLOCK | 新的进程掩码是当前值和set指定信号集的并集 |
SIG_UNBLOCK | 当前值和~set信号集的交际,因此set指定的信号将不被屏蔽 |
SIG_SETMASK | 直接设置为set |
如果set为NULL,则进程信号掩码不变,此时可以利用_oset参数获得进程当前的信号掩码。
sigprocmask成功返回0,失败返回-1并设置errno。
3.被挂起的信号
设置进程掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果取消对被挂起信号的屏蔽,则它能立即被进程接收到。下面这个函数可以获得当前被挂起的信号集:
#include<signal.h>
intsigpending(sigset_t *set);
set参数保存被挂起的信号集。进程即使多次接收到同一个被挂起的信号,sigpending函数也只能反映一次。并且,当使用sigprocmask使被挂起的信号不被屏蔽时,该信号的处理函数也只能被触发一次。
4.统一事件源
信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。信号处理函数需要尽可能地执行完毕,以确保该信号不被屏蔽(为了一些竞态条件,信号在处理期间,系统不会再触发它)太久。一种典型的解决方案是:把信号的主要逻辑放到程序的主循环中,当信号处理函数被触发时,它只是简单的通知主循环程序接收信号,并把信号值传递给主循环,主循环再根据被接收到的信号值执行目标信号的逻辑代码。信号处理函数通常用使用管道来将信号“传递“给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的独断读出该信号值,这里主循环可以用I/O复用系统监听管道的读端文件描述符上的刻度事件。如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源。