不定期补充、修正、更新;欢迎大家讨论和指正
目录
概览
kill -l 命令查看信号的宏名,前面的数字就是信号的编号
#多数信号系统默认处理方式为终止 下面只列出产生core文件和忽略处理的
1) SIGHUP #终端线路挂断
2) SIGINT #终止进程(Ctrl+C)
3) SIGQUIT #终止进程(Ctrl+\) 产生core文件
4) SIGILL #非法指令 产生core文件
5) SIGTRAP #跟踪自陷
6) SIGABRT #调用abort函数自杀终止程序 终止并产生core文件
7) SIGBUS #总线故障 终止并产生core文件
8) SIGFPE #算术运算异常,例如除以0,浮点溢出等
9) SIGKILL #无条件终止指定进程,不可被捕获和忽略
10) SIGUSR1 #用户自定义信号1,可用于应用程序
11) SIGSEGV #访问内存出错,段错误
12) SIGUSR2 #用户自定义信号2,可用于应用程序
13) SIGPIPE #写没有读权限的管道文件
14) SIGALRM #Linux三种间隔定时器:real,按实际经过的时间计时,无论进程在什么状态下运行;调用alarm函数
15) SIGTERM #终止进程 kill PID 默认就是发送这个信号
16) SIGSTKFLT
17) SIGCHLD #子进程状态改变 忽略,可以用wait函数来处理这个信号
18) SIGCONT #继续执行 忽略
19) SIGSTOP #非终端发送的停止信号
20) SIGTSTP #终端发送的停止信号 Ctrl+Z
21) SIGTTIN #后端进程读终端
22) SIGTTOU #后端进程写终端
23) SIGURG #I/O紧急信号 忽略
24) SIGXCPU #CPU时限超时
25) SIGXFSZ #文件长度过长
26) SIGVTALRM #Linux三种间隔定时器:virtual,仅在进程在用户态下执行才计时,定时到达发送信号
27) SIGPROF #Linux三种间隔定时器:profile,进程在用户态或内核态执行才计时;统计分布图用计时器到时
28) SIGWINCH #窗口大小发生变化 忽略
29) SIGIO #异步通知信号
30) SIGPWR
31) SIGSYS
34) SIGRTMIN
#总共62个信号,32和33没有
#35~64为Linux后期增设的信号,所以不需要关心。
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
接受信号
当进程接受其他进程或OS发送的信号,可以在进程内部对接受的信号作处理,分为默认,忽略和捕获。
signal(2)
NAME
signal - ANSI C signal handling //处理信号
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
//它定义了一个类型sighandler_t,表示指向返回值为void型(参数为int型)的函数(的)指针。
//它可以用来声明一个或多个函数指针。
//sighandler_t sig1, sig2; 这个声明等价于下面这种晦涩难懂的写法:
//void (*sig1)(int), (*sig2)(int);
sighandler_t signal(int signum, sighandler_t handler);
//signum信号编号,也可以用宏名
// sighandler_t handler 即 void (*handler)(int)
//如果为SIG_IGN,则信号处理为忽略 宏名为((void (*) (int)) 0)
//如果为SIG_DFL,则信号处理为默认 宏名为((void (*) (int)) 1)
//如果是一个函数,则信号处理为捕获,执行函数内的语句
//SIGKILL 和 SIGSTOP信号不能被捕获和忽略
//返回值为上一次信号处理的方式,错误时返回SIG_ERR 宏名为((void (*) (int)) -1)
将SIGINT(即在终端Ctrl+C终止进程)的处理方式改为忽略,按下Ctrl+C之后没有效果,按下Ctrl+\结束进程
记得写上typedef void (*sighandler_t)(int); 不然使用sighandler_t会报错
当handler为函数地址时,SIGINT的处理方式会执行函数内的语句
同时观察返回值,成功则为返回上一次设置的处理方式
将第一次改为捕获,第二次修改为默认,第二个pre_sig的确是函数的地址
SIGKILL 和 SIGSTOP信号不能被捕获和忽略
将所有信号设置为忽略处理
无论是发送SIGINT(Ctrl+C)还是SIGQUIT(Ctrl+\)都无法结束进程
在另一个终端kill进程 即默认发送SIGTEM命令,也无法终止
发送SIGKILL命令才能终止进程
SIGKILL和SIGSTOP信号不能被捕获和忽略就是为了防止所有信号被设置成忽略或者捕获,导致没有信号能够终止进程
子进程继承信号控制设定
在fork()之前对信号进行处理,子进程会继承父进程对信号控制的设定
对父子进程分别发送SIGTERM命令,父进程对SIGTERM命令做了捕获处理,当kill PID时会打印其进程号
可以看到kill子进程(PID=20180),子进程的信号处理也是捕获
对父子进程分别发送SIGINT终止进程
exec对信号控制设定的影响
子进程调用exec函数族加载新程序时,若信号的处理方式为捕获,则被改为默认动作
但是信号处理方式为默认和忽略时,子进程依然继承。
因为exec运行新的程序后会覆盖从父进程继承来的存储映像,那么信号捕捉函数在新程序中已无意义
所以exec会将原先设置为要捕捉的信号都更改为默认动作。
子进程执行while程序,while程序是自己编写的死循环程序
对父进程发送SIGTERM信号,处理方式为捕获
对子进程发送SIGTERM信号,子进程终止,因为此时父进程还未终止,所以子进程为僵尸进程,状态为Z
发送信号
进程也有向自己或其他进程发送信号的需求,但大多数还是接受信号的情况比较多。
alarm(2)
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//当设定的时间倒数结束,alarm()会像当前进程发送SIGALRM信号。
//函数会返回在新调用alarm时前一次调用alarm剩余的秒数,如果之前没有调用alarm则返回0
//比如 alarm(5) ;
sleep(2);
alarm(6);
alarm(4);
//第一个alarm返回值为0,因为之前没有调用过alarm,
//第二个alarm返回值为3,因为第一个倒数两秒后重新设置了闹钟,剩余三秒
//第三个alarm返回值为6,因为还没等第二个开始倒计时就被设置了闹钟,还剩余六秒
pause(2)
NAME
pause - wait for signal
SYNOPSIS
#include <unistd.h>
int pause(void);
//只在捕获信号并返回信号捕获函数时返回-1,并且error被设置为EINTR
kill(2)
NAME
kill - send signal to a process
SYNOPSIS
int kill(pid_t pid, int sig);
//向pid进程发送sig信号
//成功返回0,失败返回-1
raise(3)
NAME
raise - send a signal to the caller //调用kill(2)实现的,向当前进程发送信号
SYNOPSIS
#include <signal.h>
int raise(int sig);
//等价于kill(getpid(),sig);
//成功返回0,失败返回非零数
abort(3)
NAME
abort - cause abnormal process termination
SYNOPSIS
#include <stdlib.h>
void abort(void);
//向当前进程发送SIGABRT信号,默认处理方式是终止
信号集
信号有几个状态,产生,递达(信号到达进程),未决(到达进程,但是进程还不能处理该信号)
为了处理递达和未决时内核对信号的处理方式,每个进程的PCB都有信号屏蔽字和未决信号集两张信号集表。
信号屏蔽字,0表示信号是打开的,可以立即处理,1表示信号被屏蔽,阻塞等待。
未处理信号集,结构与信号屏蔽字相同,专门用来记录未处理的信号。
如图,信号递达,通过该进程的信号屏蔽字查询该信号是否可以处理,如果可以处理则将该信号在信号屏蔽字的位置设为1
当处理完后设为零
如果此时信号在处理中,然后又发送了同样的信号,这时信号屏蔽字为1,内核还处理不了后来的信号,所以在未决信号集设1
当之前的信号处理完毕,信号屏蔽字设置为0,内核会查看未决信号集是否有状态为1的信号,如果有就处理,并置0。
设置SIGINT为捕获后,在捕获函数还未结束,不管发送多少次SIGINT信号都不会有反应
修改信号屏蔽字
如果需要发送信号立即处理或其他操作,可以手动修改信号屏蔽字将指定位置设为0或1
以上面的情景,如果想使得只要SIGINT发送就执行捕获函数,就要每次发送一个SIGINT信号就手动将信号屏蔽字置0,使后面的发送的信号直接处理
先创建一个变量,将信号所在信号集的位置置为0,其他位置置1,然后将此变量和信号屏蔽字作与运算,若想置1,就其他位置置0,然后作或运算
利用系统和库的API实现
NAME
sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX sig‐
nal set operations
SYNOPSIS
#include <signal.h>
int sigemptyset(sigset_t *set);
//将变量set的64位全部设为0
int sigfillset(sigset_t *set);
//全部设为1
int sigaddset(sigset_t *set, int signum);
//将signum信号所在的那一位设置为1,其余不变
int sigdelset(sigset_t *set, int signum);
//将signum信号所在的那一位设置为0,其余不变
//成功返回0,失败返回01
sigprocmask(2)
NAME
sigprocmask, rt_sigprocmask - examine and change blocked signals
//将设置好的set变量修改信号屏蔽字
SYNOPSIS
#include <signal.h>
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//how修改方式,有三种
// SIG_BLOCK:屏蔽某个信号 即屏蔽字=屏蔽字|set
// SIG_UNBLOCK:打
[video(video-33XzawTr-1591968177877)(type-edu_course)(url-https://edu.csdn.net/course/blogPlay?goods_id=14277&blog_creator=weixin_44568462&marketing_id=84)(image-https://img-bss.csdnimg.cn/2020422112656862_91070.png?imageMogr2/auto-orient/thumbnail/400x269!/format/png)(title-150讲轻松搞定Python网络爬虫)]
[video(video-fQHJVgqC-1591968196507)(type-edu_course)(url-https://edu.csdn.net/course/blogPlay?goods_id=14277&blog_creator=weixin_44568462&marketing_id=84)(image-https://img-bss.csdnimg.cn/2020422112656862_91070.png?imageMogr2/auto-orient/thumbnail/400x269!/format/png)(title-150讲轻松搞定Python网络爬虫)]
开某个信号 即屏蔽字=屏蔽字|(~set)
// SIG_SETMASK:直接使用set的值替换掉屏蔽字
//oldset保存之前屏蔽字的值,NULL为不保存
在捕获函数中修改信号屏蔽字