系列文章目录
C++技能系列
 Linux通信架构系列
 C++高性能优化编程系列
 深入理解软件架构设计系列
 高级C++并发线程编程
期待你的关注哦!!!
 
现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
  Now everything is for the future of dream weaving wings, let the dream fly in reality.
Linux信号概念、认识、处理动作
- 系列文章目录
- 一、信号的基本概念
- 1、信号一般是怎么产生的?
- 1.1、某个进程发送给另一个进程或者发送给自己
- 1.2、由内核发送给某个进程
 
- 2、信号在系统中的定义
 
- 二、通过kill命令认识一些信号
- 三、进程的状态
- 四、常用的Signal信号列表
- 五、信号处理的相关动作
- 六、小结
一、信号的基本概念
信号,在很多大型应用程序中都经常出现。信号就是一个通知(事件通知),用来通知某一个进程发生的发生了某一件事。当然,这些事情或者说这些信号一般都是突然事件,或者说都是突然到来的,进程本身并不知道这些信号在什么时候发生。换句话说,信号是异步发生的,又称软件中断。
在Nginx中,可以把信号想象成是一个mastrt进程和work进程之间的一个很有效的通信手段,所以把信号看成一种非常简单的短消息。
1、信号一般是怎么产生的?
1.1、某个进程发送给另一个进程或者发送给自己
注意:进程能把信号发送给自己。某个进程把自己执行起来后,再执行一个自己并向原来的自己发送信号是可以的。
 如Nginx在运行过程中(1个master进程,多个worker进程),要做热升级。热升级是要启动新的master进程的,那么这个新启动的master进程启动时,通过增加一些命令行参数的手段就可以向旧的master进程发送一些信号,从而控制旧的master进程做一些动作。
1.2、由内核发送给某个进程
(1)通过在键盘上输入一些命令动作,如按Ctrl + C组合键(中断信号)、使用kill命令等。
 (2)内存访问异常。除数为0等,硬件会检测到并通知内核等。
2、信号在系统中的定义
信号是有名字的,这些名字都是以SIG开头,不同的系统支持的信号数量各不相同,少则支持十几个,多则支持50~60个。
信号虽然有名字,其实也都是一些数字。准确的说,信号是一些正整数,定义在系统的头文件里,一般在编程的时候包含signal.h(usr/include/)头文件即可。
输入命令可以查询:
  sudo find / -name "signal.h" | xargs grep - in "SIGHUP"
这个命令的含义是从根目录开始,寻找一个名字叫做signal.h的文件, -name就是根据名字来查找。在某些文件中查找某行是否包含某个字符串时,可以用上面的命令。(在一批文件里搜索一个字符串)。
 grep是文本搜索工具,-i参数表示查找忽略大小写,-n参数表示显示查找到的行号,要查找文本字符串是”SIGHUP“。
 xargs参数表示向其他命令传递参数,用了xargs之后,find命令找到的文件内容就能传递到grep中,所以grep实际是在找到的文件内容中进行搜索。

随便找一个signal.h文件,利用vim编辑器查看内容:
 
在vim编辑器查看signal.h的结果, 看起来大概有32个信号 ,这些SIG开头的信号其实就是一些宏定义。
二、通过kill命令认识一些信号
相信很多人对kill的用法最熟悉的莫过于输入”kill 进程ID“来杀死一个进程。其实kill命令所做的工作也是想进程发送一个信号,如果没有特别的要求,操作系统就会根据该信号,对该进程执行默认的动作,也就是终止该进程。
以  kill -数字 进程ID 方式发送多种信号,不带 -数字 ,那个数字 默认是15 ,既 -15 。
| kill的参数 | 该参数发出的信号 | 操作系统默认的动作 | 
|---|---|---|
| -1 | SIGHUP(链接断开) | 终止掉进程(进程没了) | 
| -2 | SIGINT(终端中断符,比如Ctrl + C) | 终止掉进程(进程没了) | 
| -3 | SIGQUIT(终端退出符,比如Ctrl + \) | 终止掉进程(进程没了) | 
| -9 | SIGKILL(终止) | 终止掉进程(进程没了) | 
| -18 | SIGCONT(使暂停的进程继续) | 忽略(进程依旧还在运行不受影响) | 
| -19 | SIGSTOP(停止),可用SIGCONT继续,但任务被放到了后台 | 停止进程(不是终止,进程还在) | 
| -20 | SIGTSTP (终端停止符,同Ctrl + Z),但任务被放到了后台,可用SIGCONT继续 | 停止进程(不是终止,进程还在) | 
值得一提的是-19对应的SIGSTOP信号,该信号用于停止进程,但进程还在,和以往的信号(比如SIGKILL)不一样(SKILL是终止进程,进程不在了)。
接下来,我们测试一下,写一个无限循环、不停输出信息的代码:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *const *argv)
{
        printf("hello nginx\n");
        for(;;)
        {
                sleep(1);
                printf("进程休息1s\n");
        }
        printf("程序退出!再见!\n");
        return 0;
}
编译、链接并运行,然后我们kill 5382杀死进程,如图:


如果输入kill 进程ID就是向该进程发送SIGTERM终止信号,同时进程被终止了,显示显示killed by SIGTERM,说明被SIGTERM信号杀死了。
后续几个信号就不列举了,可以自己去尝试一下。
三、进程的状态
如何查看进程状态呢?
查看进程状态,使用ps命令,在显示的列中增加一个stat 列即可。
 ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E ‘bash|PID|nginx’
还有一种方法,ps命令配合aux也可以显示进程状态(aux代表一种BSD风格的显示格式)
 ps -aux | grep -E ‘bash|PID|nginx’
如图:

STAT列显示进程状态字母是什么意思呢?可以看下表:
| 状态 | 含义 | 
|---|---|
| D | 不可中断的休眠状态(通常是I/O的进程),可以处理信号,有延迟 | 
| R | 可执行状态 & 运行状态(在运行队列里的状态) | 
| S | 可中断的休眠状态之中(等待某事件完成),可以处理信号 | 
| T | 停止或被追踪(被作业控制信号所停止) | 
| Z | 僵尸进程 | 
| X | 死掉的进程 | 
| < | 高优先级的进程 | 
| N | 低优先级的进程 | 
| L | 有些页被锁紧内存 | 
| s | session leader(会话首进程),其下有子进程 | 
| t | 追踪期间被调试器停止 | 
| + | 位于前台的进程组 | 
可以看到nginx进程的状态是S+状态。S意为sleepling(睡眠),+意为在前台运行。
如果现在用kill -19命令发送SIGSTOP信号来停止nginx进程(不是终止)则进程还在。
看下图的实例演示:
 
四、常用的Signal信号列表
常用的Signal信号如表所示:
| 信号名称 | 信号含义 | 
|---|---|
| SIGHUP(1)(链接断开) | 终端断开信号。如果终端接口检测到一个链接断开,发送信号到该终端所在的会话首进程,默认动作会导致所有相关的进程退出(Xshell断开就会发送此信号)。kill -1 进程号 也能发送此信号给进程。 | 
| SIGALM(定时器超时) | 一般调用系统函数alarm创建定时器后,定时器超时就会产生这个信号 | 
| SIGINT(2)(中断) | 输入Ctrl + C(如进程正在循环中做一件事),按 Ctrl + C组合键(中断键)就能打断进程正在做的事,按Ctrl + C组合键(中断键)就能打开进程正在做的事,终止进程。但shell会将后台进程对该信号的处理设置忽略(也就是说该进程若在后台运行则不会收到该信号) | 
| SIGSEGV(无效内存) | 内存访问异常,除数为0等,硬件会检测到并通知内核。其实这个SEGV代表断违例(segmentation violation),有时候运行一个自己写的C程序,如果程序内存访有问题,执行时也会出现这个信号 | 
| SIGIO(异步I/O) | 通知异步I/O信号,如果通信接口套接字接口上有数据到达,或发生一些异步错误,内核就会向进程通知该信号。 | 
| SIGCHLD(子进程改变) | 一个进程终止或停止时,这个信号会被发送给父进程(想象一下官方的Nginx: worker进程终止时,master进程应该会收到内核发出的针对该信号的通知。) | 
| SIGUSR1、SIGUSR2(都是用户定义的信号) | 用户定义的信号,可用于应用程序。 | 
| SIGTERM(15)(终止) | 一般 通过在命令行输入”kill 进程ID“ 命令杀死一个进程时就会触发这个信号,程序收到这个信号后,可以编写代码做退出前的处理工作,实现”优雅退出“ | 
| SIGKILL(-9)(终止) | 该信号不能被忽略,不能被进程本身捕捉,是杀死任意进程的可靠方法 | 
| SIGSTOP(19)(停止) | 该信号不能被忽略,不能被进程本身捕捉,使进程停止执行。可以发送SIGCONT信号让该进程继续执行,但继续执行后该进程会被放入后台 | 
| SIGQUIT(3)(终端退出符) | 按 Ctrl + \组合键会触发该信号。但shell会将后台进程对该信号的处理设置为忽略(也就是说在后台运行则不会收到该信号) | 
| SIGCONT(18)(使暂停的进程继续) | 使  暂停的进程继续运行 | 
| SIGTSTP(20)(终端停止符) | 按 Ctrl + Z组合键触发该信号。 进程被停止,并被放入后台,可以用SIGCONT继续运行 | 
五、信号处理的相关动作
 回顾刚才讲的kill命令时,用不同数字向nginx进程发送信号,结果是不同的,如果用-1、-2、-3、-9等发送信号时,结果是不同的,发现nginx进程被杀掉了(用ps看不到了);但如果用-18发信号时(让进程继续运行的信号)时,发现nginx进程没什么反应;用-19、-20发信号时,发现nginx进程停止了,但没有终止(用ps还能看到nginx进程的存在),可以用-18发送信号让停止的nginx进程继续运行。
当某个信号出现时,可以按以下三种方式之一进行处理。这些方法称为信号的处理或与信号相关的动作。
 (1)执行系统的默认动作
如果在代码中没有特别针对信号的处理代码,当应该进程收到信号时,操作系统(内核)会对该进程执行一个默认动作。不同的信号对应不同的默认动作,但是对于绝大多数信号,内核对进程执行的默认动作是把该进程杀死(终止该进程)。
(2)忽略此信号
如果不希望当收到某个信号的时候,内核执行该信号所对应的默认动作(如把进程杀死),可以通过写一些代码使操作系统忽略此信号,操作系统发现代码中实现了对该信号的忽略,就不会针对该信号对进程采取任何动作。
可能会有一个疑问:如果某个进程忽略了所有的信号,那内核岂不是完全杀不死该进程了?
当然不是这样的,因为只有两个信号不能被忽略(特权信号),就是SIGKILL和SIGSTOP。如果遇到常规杀不死的进程,可以使用kill -9的命令,正常情况下都能把进程杀死。
(3)捕捉该信号
所谓捕捉该信号,就是在代码中写一个信号处理函数,当系统收到该信号时,会自动调用该处理函数来处理。
当然,就算写了自己的信号处理函数,SIGKILL和SIGSTOP这两个信号仍然是特权信号,因此不要写代码去捕捉这两个信号,因为这样的代码是无效的。
六、小结
希望对信号有一个较好的理解,因为在很多大型软件开中,信号的使用都会体现在很多方面。



















