目录
0.引入
比较详细的信号介绍-这个朋友的文章也可以好好看看
网络编程的三个重要信号(SIGHUP ,SIGPIPE,SIGURG)
1.总览
2.著名的11号信号
著名的段错误是11号信号:
SIGSEGV: SIG 是信号名的通用前缀, SEGV 是segmentation violation,也就是存储器区段错误。
3.SIGHUP信号
SIGHUP信号的作用以及守护进程为什么要忽略SIGHUP信号
SIGHUP信号的作用:
比如修改了 nginx 配置文件,希望不重启nginx就让配置生效,可以往nginx进程发一个SIGHUP信号。
守护进程为什么要忽略SIGHUP信号?
首先创建一个会话leader进程A,接着用A创建子进程B,然后退出A,之后所有进程从B创建,保证所有
以后新创建进程都不是会话 leader进程(这是为了防止终端取得进程控制权而采取的保证措施),但
是,由于有一个exit进程A的操作,当A退出时,会对本会话的所有进程发送 SIGHUP信号,默认操作是
全部退出,而此时是在创建守护进程过程中,必须防止B收到SIGHUP而退出,否则下面的操作就无法进
行,所以这里必须对SIGHUP进行忽略!
4.信号9和信号15
kill和kill -9,两个命令在linux中都有杀死进程的效果,然而两命令的执行过程却大有不同,
在程序中如果用错了,可能会造成莫名其妙的现象。
kill -15 pid(默认)
执行完该指令后,操作系统会发送一个 SIGTERM 信号给对应的程序。当程序接收到该信号后,
可能会发生以下几种情况的一种:
当前程序立刻停止;
程序释放相应资源,然后再停止;
程序可能仍然继续运行。
大部分程序会先释放自己的资源,然后再停止。但是也有程序可以在接受到信号量后,继续做其
他一些事情,并且这些事情是可以配置的。如果程序正在等待IO,可能就不会立马做出响应。也
就是说,信号15(SIGTERM)是可能被阻塞、被忽略的。
kill -9 pid
如果信号15(SIGTERM)不进行响应怎么办?那信号9(SIGKILL)就是必杀信号,多半ROOT会直接
使用这个命令,但并不推荐这么做。
小结:在使用 kill -9前,应该先使用kill -15,给目标进程一个清理善后工作的机会。
如果没有,可能会留下一些不完整的文件或状态,从而影响服务的再次启动。
5.信号小结
信号 | Signal | Description |
1 | SIGHUP | 信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联 |
2 | SIGINT | 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出 |
3 | SIGQUIT | 和 SIGINT类似, 但由QUIT字符(通常是Ctrl+)来控制. 进程在因收到 SIGQUIT 退出时会产生core文件, 在这个意义上类似于一个程序错误信号 |
4 | SIGILL | 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号 |
5 | SIGTRAP | 由断点指令或其它trap指令产生. 由debugger使用 |
6 | SIGABRT | 程序自己发现错误并调用abort时产生 |
7 | SIGBUS | 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数 |
8 | SIGFPE | 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误 |
9 | SIGKILL | 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略 |
10 | SIGUSR1 | 留给用户使用 |
11 | SIGSEGV | 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据 |
12 | SIGUSR2 | 留给用户使用 |
13 | SIGPIPE | Broken pipe |
14 | SIGALRM | 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号 |
15 | SIGTERM | 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号 |
17 | SIGCHLD | 子进程结束时, 父进程会收到这个信号 |
16 | SIGSTKFLT | |
18 | SIGCONT | 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符 |
19 | SIGSTOP | 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略 |
20 | SIGTSTP | 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl+Z)发出这个信号 |
21 | SIGTTIN | 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行 |
22 | SIGTTOU | 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到 |
23 | SIGURG | 有"紧急"数据或out-of-band数据到达socket时产生 |
24 | SIGXCPU | 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变 |
25 | SIGXFSZ | 超过文件大小资源限制 |
26 | SIGVTALRM | 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间 |
27 | SIGPROF | 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间 |
28 | SIGWINCH | 窗口大小改变时发出 |
29 | SIGIO | 文件描述符准备就绪, 可以开始进行输入/输出操作 |
30 | SIGPWR | Power failure |
5.1 硬件异常相关的信号
信号 信号值 说明
SIGBUS | 7 | 总线错误,表示发生了内存访问错误
SIGFPE | 8 | 表示发生了算术错误
SIGILL | 9 | 进程尝试执行非法的机器语言指令
SIGSEGV | 11 | 段错误,表示应用程序访问了无效地址
这四种硬件异常,一般是由程序自身引发的,不是由其他进程发送的信号引发的,
并且这些异常都比较致命,以至于进程无法继续下去,所以这些信号产生之后,会
立即递送给进程。默认情况下,这四种信号都会使进程终止,并且产生core dump
文件以供调试。对于这些信号,进程既不能忽略,也不能阻塞。
5.2 终端相关的信号
终端定义了如下几种信号生成字符:
Ctrl+C : 产生SIGINT信号
Ctrl+\ : 产生SIGQUIT信号
Ctrl+Z : 产生SIGTSTP信号
键入这些信号生成字符,相当于向前台进程组发送了对应的信号。
另一个和终端关系比较密切的信号是SIGHUP信号。很多程序员都遇到过这种问题:使用ssh登陆
到远程的Linux服务器,执行比较耗时的操作(如编译项目代码),却因为网络不稳定,或者需要关机
回家,ssh连接被断开,最终导致操作中途被放弃而失败。
之所以会这样,是因为一个控制进程在失去其终端之后,内核会负责向其发送一个SIGHUP信号。
在登录会话中,shell通常是终端的控制进程,控制进程收到SIGHUP信号后,会引发如下的连锁反应:
shell收到SIGHUP后会终止,但是在终止之前,会向由shell创建的前台进程组和后台进程组发送
SIGHUP信号,为了防止处于停止状态的任务接收不到SIGHUP信号,通常会在SIGHUP信号之后,发送
SIGCONT信号,唤醒处于停止状态的任务。前台进程组和后台进程组的进程收到SIGHUP信号,默认的
行为是终止进程,这也是前面提到的耗时任务中途失败的原因。注意,单纯地将命令放入后台执行(通
过&符号),并不能摆脱被SIGHUP信号追杀的命运。
5.3 软件事件相关的信号
软件事件触发信号产生的情况比较多:
1. 子进程退出,内核可能会向父进程发送SIGCHLD信号;
2. 父进程退出,内核可能会给子进程发送信号;
3. 定时器到期,给进程发送信号.
6.信号的处理
从上面可以看到,信号产生的源头有很多,那么内核将信号递送给进程后,进程会执行什么操作呢?
很多信号尤其是传统的信号,都会有默认的信号处理方式,如果我们不改变信号的处理函数,那么收
到信号之后,就会执行默认的操作。信号的默认操作有一下几种:
1.ignore
显示地忽略信号,内核将会丢弃该信号,信号不会对目标进程产生任何影响。
2.terminate
终止进程,很多信号的默认处理是终止进程。
3.core
生成核心转储文件并终止进程,进程会被杀死并且产生核心转储文件。
核心转储文件记录了进程死亡现场的信息,用户可以使用核心转储文件
来调试,分析进程死亡的原因。
4.stop
停止进程不同于终止进程,终止进程是进程已经死亡,但是停止进程仅
是使进程暂停,将进程的状态设置成TASK_STOPPED,一旦收到恢复执行
的信号,进程还可以继续执行。
5.continue
恢复进程的执行,和停止进程相对应,某些信号可以使进程恢复执行.
根据信号的默认处理,可以将传统信号进行如下分类:
1.ignore类别
-----------------------------------------------------------
| 信号 | 值 | 说明 |
-----------------------------------------------------------
| SIGCHLD | 17 | 子进程终止、停止或恢复执行 |
-----------------------------------------------------------
| SIGURG | 23 | 套接字上的紧急数据 |
-----------------------------------------------------------
| SIGWINCH | 28 | 终端窗口大小发生变化 |
-----------------------------------------------------------
2.terminate类别
-----------------------------------------------------------
| 信号 | 值 | 说明 |
-----------------------------------------------------------
| SIGHUP | 1 | 挂起(hangup),多用于终端断开
-----------------------------------------------------------
| SIGINT | 2 | 终端中断
-----------------------------------------------------------
| SIGKILL | 9 | 杀死进程,该信号不能被忽略、屏蔽,该信号处
| | | 理函数也不能被用户自定义函数该别
-----------------------------------------------------------
| SIGUSR1 | 10 | 用户自定义信号1
-----------------------------------------------------------
| SIGUSR2 | 12 | 用户自定义信号2
-----------------------------------------------------------
| SIGPIPE | 13 | 管道断开,多见于socket通信
-----------------------------------------------------------
| SIGALAM | 14 | 定时器到期,该信号多用于实现定时器
-----------------------------------------------------------
| SIGTERM | 15 | 终止进程,因为SIGKILL过于残暴,进程终止时
| | |可能需要先执行一些操作来保存现场信息,所以
| | |合理的杀死进程的方法是先发送SIGTERM信号,稍
| | |等片刻在发送SIGKILL信号
-----------------------------------------------------------
| SIGSTKFLT | 16 | 协处理器栈错误,Linux并未使用该信号
-----------------------------------------------------------
| SIGVTALRM | 26 | 虚拟定时器过期,setitimer函数的ITIMER_VIRTUAL模式
-----------------------------------------------------------
| SIGPROF | 27 | 性能分析定时器过期,setitimer函数的ITIMER_PROF模式
-----------------------------------------------------------
| SIGIO | 29 | I/O时可能发生
-----------------------------------------------------------
| SIGPWR | 30 | 电量将要耗尽
-----------------------------------------------------------
3.core类别
-----------------------------------------------------------
| 信号 | 值 | 说明 |
-----------------------------------------------------------
| SIGQUIT | 3 | 终端Ctrl+\可产生该信号
-----------------------------------------------------------
| SIGILL | 4 | 非法的指令
-----------------------------------------------------------
| SIGTRAP | 5 | 跟踪/断点,gdb/strace一类工具会使用该信号,
| | | 这类工具会拦截或修改SIGTRAP信号处理函数
-----------------------------------------------------------
| SIGABRT | 6 | 进程终止,进程调用abort函数会向自身发送
| | | SIGABRT信号,此外如果使用了assert,assert
| | | 失败时也会产生SIGABRT信号
-----------------------------------------------------------
| SIGBUS | 7 | 总线错误
-----------------------------------------------------------
| SIGFPE | 8 | 算术异常
-----------------------------------------------------------
| SIGSEGV | 11 | 段错误,访问了非法的地址
-----------------------------------------------------------
| SIGXCPU | 14 | 突破了对CPU时间的限制
-----------------------------------------------------------
| SIGXFSZ | 25 | 突破了对文件大小的限制
-----------------------------------------------------------
| SIGSYS | 31 | 无效的系统调用
-----------------------------------------------------------
4.stop类别
-----------------------------------------------------------
| 信号 | 值 | 说明 |
-----------------------------------------------------------
| SIGSTOP | 19 | 确保进程会停止,该信号不能被忽略,不能将信号
| | |信号处理函数改写成用户指定的函数
-----------------------------------------------------------
| SIGTSTP | 20 | 终端停止信号,和SIGSTOP功能类似,但是可以被
| | |进程忽略,可以被捕捉执行用户指定的信号处理函数
-----------------------------------------------------------
| SIGTTIN | 21 | 用于作业控制,如果后台进程组尝试对终端执行
| | | read操作,终端驱动程序就会向该进程组发送
| | |SIGTTIN信号
-----------------------------------------------------------
| SIGTTOU | 22 | 如果终端启动了TOSTOP(如通过stty tostop命令)
| | | 即不允许后台进程向终端写入,而某一后台进程
| | | 尝试写入终端时,终端驱动程序就会向进程组发送
| | | SIGTTOU信号
-----------------------------------------------------------
5.continue类别
-----------------------------------------------------------
| 信号 | 值 | 说明 |
-----------------------------------------------------------
| SIGCONT | 18 | 如果目标进程处于停止状态,则恢复执行
-----------------------------------------------------------
7.信号的生命周期
当向目标进程发送一枚信号SIGXXX时,Linux内核收到了产生的信号,然后再目标进程的进程描
述符里记录了一笔:收到了信号SIGXXX,但是还没有递送给目标进程的这一段时间里,信号处于
挂起状态,被称为(pending)信号,也称为未决信号。内核将信号递送给进程,进程就会暂停当
前的控制流,转而去执行信号处理函数,这就是一个信号的完整生命周期。
典型信号可以会按照上面所述流程来处理,但是实际情况要复杂的多,还有许多场景需要考虑:
*目标进程正在执行关键代码,不能被信号中断,需要阻塞某些信号,那么在这期间,信号就不允
许被递送到进程,直到目标进程接触阻塞。
*内核发现同一个信号已经存在,那么它该如何处理这种重复的信号,排队还是丢弃?
*内核递送信号的时候,发现已有多个不同的信号被挂起,那它应该优先递送哪个信号?
*对于多线程的进程,如果向该进程发送信号,应该由哪一个线程来负责响应?
上述场景都是使用信号前需要考虑的问题。
8.信号的可靠性
信号可以分为两类:
可靠信号 :信号值在[1,31]之间的所有信号,被称为不可靠信号;
不可靠信号 :在[ SIGRTMIN, SIGRTMAX]之间的信号被称为可靠信号;
不可靠信号是指发送的信号,内核不一定能递送给目标进程,信号可能会丢失。
因为不可靠信号出现较早且应用广泛,处于兼容性考虑,不能改变这些信号的行为模式,
所以只能新增信号。新增的信号就是[SIGRTMIN, SIGRTMAX]范围内的信号,它们被称
为可靠信号。
可靠信号和不可靠信号的根本差异在于收到信号后,内核有不同的处理方式。
对于不可靠信号,内核用位图来记录该信号是否处于挂起状态。如果收到某不可靠信号,
内核发现已经存在该信号处于未决状态,就会简单地丢弃该信号。因此发送不可靠信号,
信号可能会丢失,即内核递送给目标进程的次数,可能小于信号发送的次数。
对于可靠信号,内核内部有队列来维护,如果收到可靠信号,内核会将信号挂到相应的队
列中,因此不会丢弃。严格手来,内核也设有上限,挂起信号的个数也不能无限制地增大,
不然耗费内核资源,因此只能说,在一定范围之内,可靠信号不会被丢弃。
可以通过如下命令获取内核对挂起信号的限制值,不同的系统会有不一样的值.
9.信号6出现可能的情况
10.不可以被捕捉的信号
11.各种终止信号