Linux系统下的信号
一、信号概念
1. 什么是信号
信号是软件中断,信号提供了一种异步处理事件的方法,它允许进程和内核中断其他进程。一个信号就是一条消息,它通知进程系统发生了一个某种类型的事件,在Linux系统中支持31中不同类型的信号
在我这篇文章中给大家列出了Linux系统支持的31个信号(https://blog.csdn.net/qq_34934140/article/details/116550027)。
每种信号都对应不同的系统类型,底层硬件异常是由内核异常处理程序处理的,正常情况下对用户是不可见的,信号提供了一种机制,通知用户进程发生了这些异常。每个信号都有自己的名字,他们都是以SIG开头,这些信号都包含在<signal.h>中。
产生信号的事件相对于进程来说是不可预见的,所以进程不可以测试一个变量来判断信号的产生,而必须告诉内核当信号发生时进程该如何处理。在信号出现时有以下三种处理方式
1)忽略信号,大多数信号采用这种处理方式,但是有两种信号不可用忽略,SIGKILL 和 SIGSTOP,因为他们向内核和超级用户提供了可靠的进程终止或停止的方法。
2)捕捉信号,通知内核在信号发生时调用一个用户函数,在用户函数中执行用户希望对该事件的处理方式。同样SIGKILL 和 SIGSTOP不可以被捕捉。
3)执行默认动作,系统会给所有信号一个默认动作,在(https://blog.csdn.net/qq_34934140/article/details/116550027)里面我已经详细列出。
2.信号术语
1)发送信号
内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。发送信号的原因
a. 内核检测都一个系统事件,如硬件异常或子程序终止
b. 一个进程调用kill函数,显示地要求内核发送一个进程给目的进程,或者进程自己给自己发送信号。
2)接收信号
当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号也可以捕捉它做一个信号处理函数。
3)待处理信号
一个发出而没有被接收的信号,任一时刻,一种类型最多只会有一个待处理信号,如果一个进程此时有一个类型为k的处理信号,那么接下来发送到此进程的所有k类型信号都不会排队等待
,他们都会被丢弃,进程可以选择阻塞接收某种信号,当一个信号被阻塞时,它仍然可以被发送,但是不会被接收,所有产生了待处理信号,直到进程取消对该进程的阻塞。一个待处理信号最多被
接收一次,在内核中有一个pending位向量维护着待处理信号集合,在blocked位向量中维护着被阻塞的信号集合,只要传送一个k类型信号,内核就会设置pending的第k位,接收一个k信号就会清楚
pending的k位。
4)阻塞信号
Linux提供阻塞信号的隐式和显式机制
隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理信号类型的待处理信号
显式阻塞机制:应用程序可以使用sigprocmask函数和它的辅助函数,明确地阻塞和解除阻塞选定的信号
5)不可靠的信号
不可靠指的是信号可能会丢失,一个信号发生了,但你是进程却不知道。
6)可靠信号术语和语义
未决:信号产生和递送之间的时间间隔内,称信号是未决的,调用sigpending函数可以查看哪些信号被设置为阻塞并且是未决的。
信号屏蔽字:每个信号都有,它规定了当前阻塞递送到该进程的信号集,每种信号对应屏蔽字中的一位,使用sigprocmask函数来查看、设置、修改。
7)作业控制信号
SIGCHLD 子进程已停止或终止
SIGCONT 如果进程停止则继续运行
SIGSTOP 停止信号
SIGTSTP 交互式停止信号
SIGTTIN 后台进程组成员读控制终端
SIGTTOU 后台进程组成员写控制终端
除了SIGCHLD之外,大多数应用程序不处理这些信号,交互式shell通常会处理这些信号。
8)信号集
一个可以表示多个信号的数据类型
3.编写信号处理函数基本规则
处理程序和主程序并发执行,他们共享全局变量,因此可能与主程序和其他处理程序相互干扰,所以需要编写安全、正确、可移植的信号处理程序。
1)安全的信号处理
a.处理程序尽可能简单,
b.在处理程序中只调用异步信号安全的函数
c.保存和恢复,在处理程序时把errno保存在一个局部变量中,返回时恢复。
d.阻塞所有信号,保护对共享全局数据结构的访问。
e.用volatile声明全局变量,
f.用sig_atomic_t声明标志,保证读写不可中断
二、信号调用函数
1. signal函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:设置某一信号对应动作
参数:signum:信号名
handler:信号处理函数,当信号发生时调用此函数。
返回值:成功返回处理函数的指针,失败返回SIG_ERR。
2. 发送信号函数(kill函数和raise函数)
#include <sys/types.h>
#include <signal.h>
2.1 int kill(pid_t pid, int sig);
功能:将信号发送给进程或者进程组
参数:pid > 0:发送给进程的进程ID
pid = 0: 将信号发送给发送进程所属进程组的所有进程
pid < 0: 发送给pid绝对值的进程
pid = -1:发送给可发送的所有进程
sig:信号名
返回值:成功 0, 失败 -1.
2.2 int raise(int sig);
功能:允许进程 向自己发送信号
参数:sig:信号名
返回值:成功 0, 失败 -1.
3. 定时函数(alarm函数和pause函数)
#include <unistd.h>
3.1 unsigned int alarm(unsigned int seconds);
功能:设置一个定时器,当超时之后产生SIGALRM信号,如果忽略此信号,默认动作是终止此进程
参数:seconds:时间,单位 秒
返回值:0或者一起设置闹钟时间剩余的秒数
注释:每个进程只能设置一个闹钟,如果调用alarm函数时,进程已经调用过了此函数则返回剩余时间数,时间被新值替换。
3.2 int pause(void);
功能:进程挂起直到捕捉到一个信号
参数:无
返回值:-1, errno 设置为 EINTR.
4. 信号集函数
#include <signal.h>
4.1 int sigemptyset(sigset_t *set);
功能:初始化set指向的信号集,清除其中所有信号。所有应用程序在使用信号集前必须调用sigfillset或者sigemptyset
参数:set;信号集
返回值:成功 0, 失败 -1.
4.2 int sigfillset(sigset_t *set);
功能:初始化set指向的信号集,使其包括所有信号。所有应用程序在使用信号集前必须调用sigfillset或者sigemptyset
参数:set;信号集
返回值:成功 0, 失败 -1.
4.3 int sigaddset(sigset_t *set, int signum);
功能:添加一个信号
参数:set;信号集
signum:信号
返回值:成功 0, 失败 -1.
4.4 int sigdelset(sigset_t *set, int signum);
功能:删除一个信号
参数:set;信号集
signum:信号
返回值:成功 0, 失败 -1.
4.5 int sigismember(const sigset_t *set, int signum);
功能:测试一个指定位,因为信号没有编号为0,所有从信号编号中减去1得到要处理的位编号。
参数:set;信号集
signum:信号
返回值:成功 0, 失败 -1.
5.检测和修改信号屏蔽字
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:可以检测和修改进程的信号屏蔽字
参数:how: SIG_BLOCK:该进程新的信号屏蔽字是set指向的信号集和当前信号屏蔽字的并集,阻塞信号
SIG_UNBLOCK:该进程新的信号屏蔽字是set指向的信号集和当前信号屏蔽字的补集的交集,解除阻塞
SIG_SETMASK:该进程新的信号屏蔽字是set指向的值
set:非空指针,则how修改当前信号屏蔽字
空指针:则how无意义
6.返回信号集函数:sigpending
#include <signal.h>
int sigpending(sigset_t *set);
功能:返回一信号集
参数:set:信号集
返回值:成功 0,失败 -1.
7.检测或修改以及指定信号相关联的处理动作
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:检测或修改以及指定信号相关联的处理动作
参数:signum:要修改或检测的信号
act:非空指针:则要修改动作
为空: 则由oldact指针返回信号上一个动作。
oldact:指针
返回值:成功 0,失败 -1.
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
当更改信号动作时,sa_handler是信号处理函数,sa_sigaction是替代的信号处理程序,sa_mask是一个信号集,调用信号处理函数之前新将信号集加入到信号屏蔽字中,
这样当正在处理信号时候,再来这种信号就会被阻塞,sa_flags指对信号进程处理的各个选项,有以下选项。
SA_NOCLDSTOP:若signo是SIGCHLD,当子进程终止产生此信号。
SA_NOCLDWAIT:若signo是SIGCHLD,当子进程终止不创建僵尸进程,若调用wait则阻塞到所有子进程都终止
SA_NODEFER:当捕捉到信号时,在执行其信号处理函数时,系统不自动阻塞此信号
SA_ONSTACK:此信号递送给替换栈上的进程。
SA_RESETHAND:此信号捕捉函数入口处,将此信号处理方式重置为SIG_DFL,清除SA_SIGINFO标准
SA_RESTART:此信号中断的系统调用自动重启
SA_SIGINFO:提供了一个指向siginfo结构的指针和指向进程上下文的标识符指针。
8.非局部转移函数 sigsetjmp函数 和 siglongjmp函数
同setjmp函数longjmp函数功能一样,在 信号处理程序中调用siglongjmp跳回到主程序。不同之处是多了一个参数,如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字
。调用siglongjmp时,如果带非0 savemask的sigsetjmp调用已经保存env,则siglongjmp从其中恢复保存的信号屏蔽字。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);
功能:可以保存当前执行线索状态
参数: env:存放调用siglongjmp时可以恢复栈状态的所有信息,通常为全局变量
savemask:非0:env中保存当前进程信号屏蔽字
返回值:直接调用返回 0,若从siglongjmp调用返回0.
int siglongjmp(sigjmp_buf env, int val);
功能:跳转函数
参数:env:sigsetjmp调用的env
val:非0值
9.恢复信号屏蔽字的值
#include <signal.h>
int sigsuspend(const sigset_t *mask);
功能:如果捕捉到一个信号而且信号处理函数返回,则sigsuspend返回,并且该信号屏蔽字设置为调用sigsuspend之前的值
参数:由sigmask指向的值
返回值:此函数没有成功返回值,如果返回到调用者则总是-1.
10.程序异常终止函数
#include <stdlib.h>
void abort(void);
功能:此函数将SIGABRT信号发送给调用进程
11.信号名和信号编号
#include <signal.h>
11. void psignal(int sig, const char *s);
void psiginfo(const siginfo_t *pinfo, const char *s);
功能:打印与信号编号对应的字符串
参数:sig:信号名
s: 对应字符串
11.2 #include <string.h>
char *strsignal(int sig);
功能:给定信号编号,返回信号字符串