文章目录
1. 基本数据结构
信号: int sig;
信号集:sigset_t set;
- sigset_t类型有的是数组, 有的是unsigned long类型, 原理是每个信号在信号集里是一个位掩码。
// 数组
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
// unsigned long
typedef __sigset_t sigset_t;
typedef unsigned long __sigset_t;
2. 常见信号
SIGINT:
ctrl + c 绑定,结束前台进程
SIGQUIT:
ctrl + \ 绑定,结束进程,产生core
SIGTERM:
kill 或者killall命令,可以被阻塞和处理, kill不加参数默认产生的信号。
SIGKILL:
kill -9 pid 或者 kill -SIGKILL,不能被应用程序捕获,也不能被阻塞或忽略。
SIGHUP:
挂起信号,在关闭终端是产生,默认处理是终止收到给信号的进程。
SIGSEGV:
默认动作是异常终止。这个动作也许会结束进程,但是可能生成一个核心文件以帮助调试。
SIGPIPE:
当服务器 close 一个连接时,若 client 继续向服务器发数据,根据 TCP 协议的规定,客户端会收到一个 RST 响应,client再往这个服务器发送数据时,系统会发出一个 SIGPIPE 信号给客户端进程,导致客户端进程退出。
SIGALRM:
调用alarm函数触发,可阻塞
SIGUSR1:
调用kill -USR1 pid触发
3. 信号API
3.1 signal: 绑定信号处理函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void signalHandle(int sig)
{
if (sig == SIGQUIT) {
printf("sigHand SIGQUIT\n");
} else if (sig == SIGINT) {
printf("sigHand SIGINT\n");
} else if (sig == SIGTERM) {
printf("sigHand SIGTERM");
} else if (sig == SIGUSR1) {
printf("sigHandle SIGUSR1");
} else {
printf("other signal\n");
}
}
int main()
{
signal(SIGINT, signalHandle);
signal(SIGQUIT, signalHandle);
signal(SIGTERM, signalHandle);
signal(SIGUSR1, signalHandle);
for (int i = 0; ;i++) {
printf("%d\n", i);
sleep(3);
}
return 0;
}
输入:
kill -USR1 58912
kill -SIGINT 58912
kill -SIGTERm 58912
kill -sigkill 58912
SIG_DFL
:默认处理函数
SIG_IGN
:忽略捕获消息
3.2 kill
int kill(pid_t pid, int signo);
1)当pid>0
时,给指定进程发信号
2)当pid == 0
时,给自己和自己同组的进程组发信号
3)当pid == -1
时,给每个进程,除了0和1(init)进程发信号。
4)当pid < 0
且 pid != -1
时,给-pid进程组发信号
当signo
为0
时,表示给进程发空信号,测试给进程是否还存在。如果不存在,会返回ESRCH
错误
// 给指定的进程发信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
int pid;
while(1) {
scanf("%d", &pid);
printf("%d\n", pid);
kill(pid, SIGINT);
kill(pid, SIGQUIT);
kill(pid, SIGTERM);
kill(pid, SIGUSR1);
}
return 0;
}
// 给自身同组的进程组发信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void signalHandle(int sig)
{
if (sig == SIGINT) {
printf("sigHand SIGINT\n");
} else {
printf("other signal\n");
}
}
int main()
{
signal(SIGINT, signalHandle);
signal(SIGUSR1, signalHandle);
for (int i = 0; ;i++){
printf("%d\n", i);
if (i % 2) {
kill(0, SIGINT);
}
sleep(3);
}
return 0;
}
sigqueue
raise
3.3 raise:
int raise(int signo);
给自身发信号,相当于kill(getpid(), signo)
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void signalHandle(int sig)
{
if (sig == SIGINT) {
printf("sigHand SIGINT\n");
} else {
printf("other signal\n");
}
}
int main()
{
signal(SIGINT, signalHandle);
signal(SIGUSR1, signalHandle);
for (int i = 0; ;i++){
printf("%d\n", i);
if (i % 2) {
raise(SIGINT);
}
sleep(3);
}
return 0;
}
3.4 sigprocmask 信号掩码
内核为每个进程维护一个信号掩码,即一组信号,并阻塞其针对该进程的传递。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how取值:
SIG_BLOCK:将当前信号集合set取并集
SIG_UNBLOCK:将set从当前信号集中移除
SIG_SETMASK:将set置换当前信号集
如果set为空,则返回当前信号集,不管how的取值。
1)进程在执行某个信号的处理函数时,同一信号再次进来会造成函数的重入或者其他信号处理函数被执行,从而打断当前信号处理函数的执行。为了不被打断,通过sigprocmask
设置进程信号屏蔽集。
2)对于一个被屏蔽的信号,即便目标进程将它的处理方式设置为了SIG_IGN
,内核也不会忽略并丢弃这个信号。这是因为啊,在这个信号被屏蔽的这段时间里,进程是有可能将SIG_IGN
改成其他的处理函数的。进程可是以它解除对这个信号的屏蔽时,设置的处理函数为依据的,在信号阻塞期间的改动通通不算数。要是内核按照它之前的设定把信号忽略了,它反倒是要怪罪内核了,内核可不想背这个锅。
信号集操作
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(sigset_t *set);
// 打印被信号集
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int printSigset(sigset_t &sigSet)
{
int cnt = 0;
printf("printf:begin\n");
for (int i = 0; i < NSIG; i++) {
if (sigismember(&sigSet, i)) {
printf("%d-%s\n", i, strsignal(i));
cnt++;
}
}
printf("cnt = %d, printf end\n", cnt);
}
int main() {
sigset_t old;
sigset_t old2;
sigset_t blockSet;
sigaddset(&blockSet, SIGINT);
sigaddset(&blockSet, SIGQUIT);
sigaddset(&blockSet, SIGTERM);
sigprocmask(SIG_BLOCK, &blockSet, &old); // old为原始信号集
printSigset(old);
sigprocmask(SIG_BLOCK, NULL, &old2); // old2为add后的信号集
printSigset(old2);
sigprocmask(SIG_SETMASK, &blockSet, &old); // 将老的信号集置回去
return 0;
}
3.5 sigpending 处于等待的信号
int sigpending(sigset_t *set);
1)当内核传递给进程被阻塞的信号时,信号会添加到进程的等待队列中,当进程对阻塞信号释放时,再将等待信号传递给进程。
2)标准信号1-31进入等待信号集时,如果同一标准信号有多次进入,最终只会向进程传递一次该标准信号。同一实时信号如果多次进入,则会排成一个队列,待进程阻塞释放时,再将该实时信号的队列逐一传递给进程。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
int printSigset(sigset_t &sigSet)
{
int cnt = 0;
printf("printf:begin\n");
for (int i = 0; i < NSIG; i++) {
if (sigismember(&sigSet, i)) {
printf("%d-%s\n", i, strsignal(i));
cnt++;
}
}
printf("cnt = %d, printf end\n", cnt);
}
int main()
{
sigset_t oldSet, newSet, pendSet;
sigaddset(&newSet, SIGINT);
sigaddset(&newSet, SIGQUIT);
sigprocmask(SIG_BLOCK, &newSet, &oldSet);
printSigset(oldSet);
printf("=====1\n");
sigpending(&pendSet);
printSigset(pendSet);
printf("=====2\n");
//raise(SIGQUIT);
//raise(SIGINT);
kill(getpid(), SIGINT);
kill(getpid(), SIGQUIT);
sleep(5);
sigpending(&pendSet);
printSigset(pendSet);
printf("=====3\n");
if (sigprocmask(SIG_SETMASK, &oldSet, NULL) == 0) { // 这里为啥coredump???
printf("sigprocmask error2 \n");
printSigset(newSet);
} else {
printf("sigprocmask error \n");
}
printf("sigprocmask \n");
return 0;
}
3.6 sigaction 同signal,改变信号的处理函数
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
简化的结构示意
struct sigaction {
void (*sa_handler) (int); // 信号处理函数,也可以是SIG_IGN,SIG_DFL
// void (*_sa_sigaction)(int,struct siginfo *, void *); 第二个参数siginfo接收sigqueue发送的额外信息
sigset_t sa_mask; // 一组阻塞信号,在调用sa_handler是阻塞sa_mask信号集
int sa_flags; // 控制信号处理过程的各种选项,可取值SA_NOCLDSTOP,SA_NOCLDWAIT,SA_RESETHAND,SA_RESTART, SA_SIGINFO等
void (*sa_restorer)(void);
}
1)相比signal更灵活,可移植性更好。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
int printSigset(sigset_t &sigSet)
{
int cnt = 0;
printf("printf:begin\n");
for (int i = 0; i < NSIG; i++) {
if (sigismember(&sigSet, i)) {
printf("%d-%s\n", i, strsignal(i));
cnt++;
}
}
printf("cnt = %d, printf end\n", cnt);
}
void signalHandle(int sig)
{
if (sig == SIGINT) {
printf("sigHand SIGINT\n");
} else {
printf("other signal\n");
}
}
int main()
{
struct sigaction new_act, old_act;
new_act.sa_handler = signalHandle;
new_act.sa_flags = 0;
sigemptyset(&new_act.sa_mask);
sigaction(SIGINT, &new_act, &old_act);
raise(SIGINT);
return 0;
}
2)和signal的差别
1、signal在调用handler之前先把信号的handler指针恢复;sigaction调用之后不会恢复handler指针, 直到再次调用sigaction修改handler指针。这样,signal就会丢失信号,而且不能处理重复的信号, 而sigaction就可以。 因为signal在得到信号和调用handler之间有个时间把handler恢复了,这样再次接收到此信号就会执行默认的handler。(虽然有些调用,在handler的以开头再次置handler,这样只能保证丢信号的概率降低,但是不能保证所有的信号都能正确处理)
2、signal在调用过程不支持信号block;sigaction调用后在handler调用之前会把屏蔽信号(屏蔽信号中自动默认包含传送的该信号)加入信号中,handler调用后会自动恢复信号到原先的值。 signal处理过程中就不能提供阻塞某些信号的功能,sigaction就可以阻塞指定的信号和本身处理的信号,直到handler处理结束。这样就可以阻塞本身处理的信号,到handler结束就可以再次接受重复的信号。
3.7 sigqueue 同kill,raise,alarm,abort给进程传递一个信号,相比前面的几种更复杂
int sigqueue(pid_t pid, int sig, const union sigval val)
typedef union sigval {
int sival_int;
void *sival_ptr;
} sigval_t;
1)sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
2)在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void sighanlder(int signo, siginfo_t *info, void *ctx)
{
printf(" info->si_int = %d\n",info->si_int);
printf(" info->si_value.sival_int = %d\n",info->si_value.sival_int);
}
int main()
{
struct sigaction act;
act.sa_sigaction = sighanlder;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, NULL) == -1) {
printf("sigaction error \n");
return 0;
}
union sigval val;
val.sival_int = 22;
if (sigqueue(getpid(), SIGINT, val) == -1) {
printf("sigqueue error\n");
return 0;
}
printf("end\n");
return 0;
}
3.8 pause
int pause(void); 终止进程,直到信号到达
3.9 sigsuspend
当前sigprocmask阻塞了信号A,调用sigsuspend将阻塞一个新的信号B或者不阻塞任何信号,程序阻塞在sigsuspend调用点,直到有非B的信号或任何信号到达,这时sigsuspend函数返回,同时把程序的阻塞恢复到信号A。 主要应对的场景是,要监听某个信号,在信号到后做处理的同时把信号给阻塞住,避免两次信号到来的时候,只处理第一次,忽略第二次,sigsuspend在处理第一次的时候会阻塞信号导致第二次信号被阻塞,相当于放到待处理队列中而不是忽略掉。
int sigsuspend(const sigset_t *mask); // always return -1
3.10 sigwait
- 在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
- sigwait(&set, signo)监听信号集set中所包含的信号,并将其存在signo中。
- sigwait函数所监听的信号在之前必须被阻塞(pthread_sigmask),调用sigwait后线程阻塞(这个时候线程信号没有被阻塞,线程被sigwait阻塞,sigwait解阻塞的条件时被监听的信号集中某个信号到来),直到有一个被监听的信号到来时,这时sigwait返回,参数signo被置为到来的信号值,同时被监听的集合再次被阻塞(pthread_sigmask作用)。
int sigwait(const sigset_t *set, int *sig);
#include<stdio.h>
#include<pthread.h>
#include<signal.h>
static void sig_alrm(int signo);
static void sig_init(int signo);
int
main()
{
sigset_t set;
int sig;
sigemptyset(&set);
sigaddset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);//阻塞SIGALRM信号
signal(SIGALRM, sig_alrm);
signal(SIGINT, sig_init);
sigwait(&set, &sig);//sigwait只是从未决队列中删除该信号,并不改变信号掩码。也就是,当sigwait函数返回,它监听的信号依旧被阻塞。
switch(sig){
case 14:
printf("sigwait, receive signal SIGALRM\n");
/*do the job when catch the sigwait*/
break;
default:
break;
}
sigdelset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);
for(;;)
{}
return 0;
}
static void
sig_alrm(int signo)
{
printf("after sigwait, catch SIGALRM\n");
fflush(stdout);
return ;
}
static void
sig_init(int signo)
{
printf("catch SIGINT\n");
return ;
}
除了返回信息方面,sigwaitinfo的行为基本上与sigwait类似。sigwait在sig中返回触发的信号值;而sigwaitinfo的返回值就是触发的信号值,并且如果info不为NULL,则sigwaitinfo返回时,还会在siginfo_t *info中返回更多该信号的信息,sigtimedwait的行为又与sigwaitinfo的行为类似,只是它多了一个超时参数timeout,也就是可以设置该函数的最大阻塞时间。siginfo_t的结构体定义如下:
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);
siginfo_t
{
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /*Least significant bit of address (since Linux 2.6.32)*/
}
向某个线程发送信号
int tkill(int tid, int sig);
int tgkill(int tgid, int tid, int sig);
linux api查询:
https://www.die.net/search/?q=kill&sa=Search&ie=ISO-8859-1&cx=partner-pub-5823754184406795%3A54htp1rtx5u&cof=FORID%3A9
参考:
https://www.cnblogs.com/mickole/p/3191804.html
https://www.cnblogs.com/chllovegeyuting/archive/2012/09/10/2679178.html
https://idc.wanyunshuju.com/cym/799.html
多进程多线程下的sigwiat
sigwait
sigwaitinfo & sigtimedwait
sigsuspend