当我们想强制结束一个程序的时候,我们通常会给它发送一个信号然后该进程捕捉到信号,再然后该进程执行一定操作最终被终止。信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。
一个信号的产生叫生成,接收到一个信号叫捕获,生成信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort(),最常用的是kill;捕获信号的主要函数有:signal(),sigprocmask() ,sigpending(),sigsuspend(), sigemptyset(),最常用的是signal。下面重点讲一下kill和signal的使用。
1.信号的生成kill
在Linux系统下想结束某个进程时,用的最多的指令就是kill或者killall,这就是常用的信号生成方式,在程序运行时ctrl+c中断进程,会产生信号SIGINT。在Linux系统对应kill命令也有kill函数,c语言引入头文件sys/types.h和signal.h之后就可以使用。kill 送出一个特定的信号 (signal) 给行程 id 为 pid 的行程根据该信号而做特定的动作,若没有指定,预设是送出终止 (SIGTERM) 的信号,例如kill -9 pid就是强制杀死进程号为pid的进程。在这里有人就会问为什么要用-9呢,-9只是kill发送信号的一个编号,可以输入kill –l把kill所有可用的信号名称列出来。
kill -9 意思是把SIGKILL信号发送给进程号为pid的进程。
其中前面31个信号为不可靠信号(非实时的,可能会出现信号的丢失),后面的信号为可靠信号(实时的real_time,对信号排队,不会丢失)。
1) SIGHUP (挂起) 当运行进程的用户注销时通知该进程,使进程终止
2) SIGINT (中断) 当用户按下时,通知前台进程组终止进程
3) SIGQUIT (退出) 用户按下或时通知进程,使进程终止
4) SIGILL (非法指令) 执行了非法指令,如可执行文件本身出现错误、试图执行数据段、堆栈溢出
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用
6) SIGABRT (异常中止) 调用abort函数生成的信号
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.
8) SIGFPE (算术异常) 发生致命算术运算错误,包括浮点运算错误、溢出及除数为0.
9) SIGKILL (确认杀死) 当用户通过kill -9命令向进程发送信号时,可靠的终止进程
10) SIGUSR1 用户使用
11) SIGSEGV (段越界) 当进程尝试访问不属于自己的内存空间导致内存错误时,终止进程
12) SIGUSR2 用户使用
13) SIGPIPE 写至无读进程的管道, 或者Socket通信SOCT_STREAM的读进程已经终止,而再写入。
14) SIGALRM (超时) alarm函数使用该信号,时钟定时器超时响应
15) SIGTERM (软中断) 使用不带参数的kill命令时终止进程
17) SIGCHLD (子进程结束) 当子进程终止时通知父进程
18) SIGCONT (暂停进程继续) 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞.
19) SIGSTOP (停止) 作业控制信号,暂停停止(stopped)进程的执行. 本信号不能被阻塞, 处理或忽略.
20) SIGTSTP (暂停/停止) 交互式停止信号, Ctrl-Z 发出这个信号
21) SIGTTIN 当后台作业要从用户终端读数据时, 终端驱动程序产生SIGTTIN信号
22) SIGTTOU 当后台作业要往用户终端写数据时, 终端驱动程序产生SIGTTOU信号
23) SIGURG 有"紧急"数据或网络上带外数据到达socket时产生.
24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25) SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。
26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF (梗概时间超时) setitimer(2)函数设置的梗概统计间隔计时器(profiling interval timer)
28) SIGWINCH 窗口大小改变时发出.
29) SIGIO(异步I/O) 文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR 电源失效/重启动
31) SIGSYS 非法的系统调用。
程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP。
不能恢复至默认动作的信号有:SIGILL,SIGTRAP。
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM。
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU。
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH。
2.信号的捕获signal
signal的函数原型是:
typedef void (*sig_t)( int );
sig_t signal(int signum,sig_t handler);
第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
第二个参数handler描述了与信号关联的动作,它可以取以下三种值:
(1)一个无返回值的函数地址
此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为signum的信号时,就执行handler 所指定的函数。这个函数应有如下形式的定义:
void func(int sig);
(2)SIG_IGN
这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
(3)SIG_DFL
这个符号表示恢复系统对信号的默认处理。
最常用的捕获signal场景是当signal到来时,程序运行某函数,函数由你自己指定,此时函数一般是做一些资源的释放,比如socket关闭,内存释放,文件关闭,有人会说了我不释放那些资源也可以退出程序,你说的没错,你不做任何处理一样可以退出程序,但是有时会影响其他共享或者交互进程,比如作为服务端异常退出时不主动关闭socket,可能会导致客户端不知道断开连接,导致某些数据丢失或者逻辑异常,所以在程序正常和异常退出时最好都做到优雅,这里除了SIGKILL,SIGSTOP除外,因为这些信号无法捕获想优雅退出不给机会。
代码示例:
#include <stdio.h>
#include <signal.h>
void server_uninit()
{
//释放所有资源
printf("close all socket and file\n");
}
void func1()
{
printf("SIGINT \n");
server_uninit();
printf("可以优雅的退出程序了 \n");
exit(0);
}
void func2()
{
printf("SIGTERM \n");
server_uninit();
printf("可以优雅的退出程序了 \n");
exit(0);
}
void func3()
{
printf("SIGSEGV \n");
server_uninit();
printf("可以优雅的退出程序了 \n");
exit(0);
}
void func0()
{
printf("SIGKILL \n");
server_uninit();
printf("可以优雅的退出程序了 \n");
exit(0);
}
int main()
{
signal (SIGINT, func1);
signal(SIGKILL, func0);
signal(SIGSEGV, func3);
signal(SIGTERM, func2);
int i = 0;
while(1)
{
printf("i %d\n",i++);
sleep(2);
}
return 0;
}