进程中的信号
作者:下家山
一:进程中的信号
1.1信号的定义:
信号是unix和linux系统响应某种条件而产生的一个事件。
接收到该信号的进程会相应的采取一些行动。
1.1.1 实例解析
当我们在ubuntu上ping www.baidu.com的时候,ping程序一直在运行,直到我们按下ctrl+C,ping程序才会停止。
首先我们linux系统中有个程序在运行【进程,这个进程能够捕获到ctrl+c按键】,当我们按下ctrl+c时,相当于发出了一个信号,该进程接收到这个信号后,采取一些行动。【这个行动,就是终止前台进程】
1.1.2 信号的一些属性
我们用术语raise(生成)表示一个信号的产生。
使用术语catch(捕获)表示接收到一个信号。
信号,是由某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。他们由shell和终端处理器生成来引起中断,他们还可以作为在进程间传递消息或修改行为的一种方式,明确的由一个进程发给另一个进程。
信号可以被生成,捕获,响应或忽略。
1.1.3 系统中有哪些信号
Linux系统中的信号命名 | |
信号名称 | 说明 |
SIGABORT | 进程异常终止 |
SIGALRM | 超时警告 |
SIGFPE | 浮点运算异常 |
SIGHUP | 链接挂断 |
SIGILL | 非法指令 |
SIGINT | 终端中断 |
SIGKILL | 终止进程(此信号不能被捕获或忽略) |
SIGPIPE | 如果你试图向一个管道或套接口写入,当读取方关闭连接,你将得到一个SIGPIPE的信号,它会使进程终止除非指定处理方法。 |
SIGQUIT | 终端退出 |
SIGSEGV | 无效内存段访问 |
SIGTRM | 终止 |
SIGUSR1 | 用户定义信号1 |
SIGUSR2 | 用户定义信号2 |
|
|
|
|
如果进程接收到这些信号中的一个,但事先没有安排捕获它,进程将会立刻终止。【对这句话的理解,看下面的例子就明白了,如果你在shell提示符下运行了一个前台进程,而且你的代码里面没有捕获ctrl+c的代码,那么,当用户按下ctrl+c时,你所运行的这个进程将会退出】
通常,系统将生成核心转储文件core,并将其放在当前目录下。该文件是进程在内存中的映像,它对程序的调试很有用处。
其他信号如下:
Linux系统中的信号命名 | |
信号名称 | 说明 |
SIGCHLD | 子进程已经停止或退出 |
SIGCONT | 继续执行暂停进程 |
SIGSTOP | 停止执行(此信号不能被捕获或忽略) |
SIGTSTP | 终端挂起 |
SIGTTIN | 后台进程尝试读操作 |
SIGTTOU | 后台进程尝试写操作 |
SIGCHLD信号对于管理子进程很有用。默认情况下,它是被忽略的。其他信号会使接收它的进程停止运行,但SIGCONT是一个例外,它的作用是让进程恢复并继续执行。Shell脚本通过它来控制作业,但用户程序很少会用到它。
1.1.4 杀死前台进程
如果我们对shell和终端驱动程序是按照通常情况配置的话,当我们在键盘按下终端中断字符(Ctrl+C的组合键)就会向前台进程(即当前运行的程序)发生SIGINT信号,这将引起该程序终止,除非它事先安排了捕获这个信号。
1.1.5 杀死后台进程
如果想发生一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill命令。该命令需要有一个可选的信号代码或信号名称和一个接收信号的目标进程的PID(这个PID一般需要用ps命令查出来)。
【实例解析】
当我们在后台运行ctrl_c进程$./ctrl_c &,实际上系统会报出它的进程ID是6303,不需要我们通过ps命令查看。(前台运行的进程,才需要ps查看ID)
然后,当我们输入kill -HUP 6003回车,进程ctrl_c进程终止,但是shell提示符并没有出来,需要我们在运行ctrl_c进程的终端上敲回车键才可以显示shell提示符。
按了回车键后,终端跳出:
Kill当然也可以杀死前台进程
当我们在另一个终端,通过ps查到ctrl_c的进程ID是6002,然后通过kill杀掉它的时候,ctrl_c进程会被终止,跳出Terminated字符。
1.1.6 killall通杀令
Kill命令有一个有用的变体叫killall,它可以给运行着某一命令的所有进程发送信号。并不是所有的unix系统都支持它,但是linux系统一般都有该命令。如果不知道某个进程的ID,或者想给执行相同命令的许多不同的进程发送信号,这条命令就很有用了。
1.2 如何用函数来处理信号
Ctrl+c,kill,killall这类命令需要在终端下操作,程序设计很不方便,灵活,如何使用函数来实现呢!
1.2.1 信号处理函数signal
#include <signal.h>
Void (*signal (int sig, void (*func)(int))) (int);
这个相当复杂的函数定义说明:
1,signal是一个带有sig和func两个参数的函数。
2,sig参数表示要捕获或忽略的信号;
3,func是一个函数指针,接收到指定的信号后将要调用的函数由参数func给出;
**************下面讲解了实例后回头看比较好**********************
4,信号处理函数必须有一个int类型的参数(即接收到的信号代码)
并且返回类型为void。
Signal函数本身也返回一个同类型的函数,即先前用来处理这个信号的函数,
,或者也可以用下面的两个特殊值之一来代替信号处理函数。
信号处理函数中的特殊值 | |
名称 | 说明 |
SIG_IGN | 忽略信号 |
SIG_DFL | 恢复默认行为 |
1.2.2 signal实例
我们通过一个实例可以很清楚的理解信号的处理方法。
我们将写一个程序ctrl_c_signal.c,它将响应用户敲入的Ctrl+C组合键,在屏幕上打印一条信息而不是终止程序的运行。当用户第二次按下Ctrl+C时,程序真正的终止退出。
1.2.3 signal实例解析
Main函数的作用是,截获按下Ctrl+C组合键时产生的SIGINT信号。没有信号出现时,它会在一个无限循环中每隔一秒打印一条信息。
第一次按下Ctrl+C会让程序作出响应,然后程序继续执行。再次按下Ctrl+C时,程序将结束运行,因为SIGINT的处理方式已经恢复默认行为——终止程序的执行。
在此例中,我们可以看到,信号处理函数
void catch(int sig)
使用了一个单独的整数参数sig,它就是引起该函数被调用的信号代码。如果需要在同一个函数中处理多个信号,这个参数就很有用。
1.2.4 signal函数的弊端
如果想保留信号处理函数,让它继续响应用户的Ctrl+C组合键,我们就需要再次调用signal函数来重新建立它。这会使信号在一段时间内无法得到处理,这段时间从调用中断函数开始,到信号处理函数的重建为止。如果在这段时间内程序接收到第二个信号,它就会违背我们的意思。
1.3 kill传递信号
1.3.1 kill实例
1.3.2 kill实例解析
Kill.c第一个函数ding是模拟一个闹钟。
在main函数中,我们告诉子进程在等待5秒后发送一个SIGALRM信号给它的父进程。
当fork函数返回值为0时,说明是子进程代码在运行;
当fork函数返回值大于0时,说明是父进程代码在运行;
这里为什么要sleep(5)呢,是因为子进程要发送一个SIGALRM信号父进程,所以一定要等到父进程捕获函数【(void )signal(SIGALRM,ding)】成功运行起来,所以这个5秒是估算的。
子进程通过kill函数向父进程发送SIGALRM信号;
父进程通过(void )signal(SIGALRM,ding)捕获SIGALRM信号,如果捕获到了,启动信号处理函数ding。
父进程启动信号处理函数后,调用了一个pause函数,该函数将等待有一个信号出现【这里就是SIGALRM】,并且这个信号处理完毕【也就是ding函数返回】
1.3.3 kill函数和kill命令
我们杀死一个进程通常是kill 进程号,实际上kill命令就是通过Kill函数来实现的,只不过当输入kill 进程号的时候,相当于执行了kill(进程号,SIGKILL)
1.4 sigaction函数
百度了解一下
1.5 利用sigchld信号避免僵尸进程
SIGCHLD信号的编号
该信号的功能是,当进程结束时,会向其父进程发生该信号。通过这个原理,我们可以避免僵尸进程。
运行结果:
解析:
问题:
如果子进程通过exit(0)正常退出呢?
执行结果
结论:没有区别!
如果父进程调用了wait函数呢?