前言
信号是在软件层面上对中断的模拟,也叫软中断信号,用于通知应用程序发生了异步事件。信号不进行数据传递,仅向进程传递异步事件。
当硬件检测到异常时,将异常信息发给内核,内核再把异常信号通知到进程中;shell终端也可以生成信号,并通知给前台进程;程序中也可以通过系统调用,生成的信号可以发给自己,也可以发给其它进程。
1. 信号分类
在linux系统shell终端中输入命令“kill -l”查看内核中维护的信号,由信号编号(数字部分)和信号名组成。向进程发送信号的命令格式为“kill -编号 pid”,我们常用的“kill -9 pid”命令就是使用编号为9的信号来终止进程。
可以使用命令“man 7 signal”查看详细内容。
![](https://img-blog.csdnimg.cn/a317a14eaf444bf0b06ac91274afe55e.png)
1.1 不可靠信号
早期的unix系统中,信号的处理机制存在很多问题,例如信号捕获后,下次该信号触发时又恢复到系统默认处理了,需要在自定义的信号处理函数中重新配置处理函数;还有就是在信号处理过程中再次触发了新信号,此时这个信号可能就会丢失掉。
编号小于SIGRTMIN(编号34)的信号被叫做不可靠信号或非实时信号,linux系统继承了unix的这种不可靠信号,也对它做了改进,信号捕获处理函数配置了一次就不会跳回到系统默认处理了,但还是存在丢失的情况。
1.2 可靠信号
由于不可靠信号被应用在了很多方面,为了兼容性,对之前的不可靠信号不改动,新增加一些优化后的信号,SIGRTMIN(编号34)到SIGRTMAX(编号64)的信号被称为可靠信号或实时信号。可靠信号修复了unix信号机制的那两个问题,信号不丢失。
2. 信号处理
当进程接收到信号后,通常有三种处理方式:①系统默认处理方式,②忽略信号,③捕获信号自定义处理。系统调用提供了信号处理API。
2.1 signal()函数
signal()和sigaction()都可以用来设置信号处理函数,但sigaction函数更复杂一点。
头文件:
#include <signal.h>
typedef void (*sighandler_t)(int);
函数原型:
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum:指定信号;例如SIGINT;
handler:
SIG_IGN:忽略该信号;
SIG_DFL:系统默认处理方式;
void (*sighandler_t)(int):捕获信号自定义处理函数;
返回值:
成功:返回最后一次注册信号调用signal()时的handler 值;
失败:返回SIG_ERR。//#define SIG_ERR ((sig_t) -1)
#include <stdio.h>
#include <signal.h>
void sig_handler(int sig)
{
printf("signal handler...\n");
}
int main()
{
signal(SIGINT,sig_handler);
while(1);
return 0;
}
3. 进程中发送信号
信号也可以在进程中通过系统调用API来发送,接收方可以是本身进程或者其它进程。
3.1 kill函数
向指定的进程中发送指定的信号。
头文件:
#include <sys/types.h>
#include <signal.h>
函数原型:
int kill(pid_t pid, int sig)
参数:
pid:
大于0,向指定pid 的进程发送信号;
等于0,向同一个进程组的进程发送信号;
等于-1,除发送进程自身外,向所有进程ID 大于1 的进程发送信号;
小于-1,向进程组ID 等于该pid 绝对值的进程组内所有进程发送信号;sig:指定要发送的信号;设为 0 表示空信号;
返回值:
成功:返回 0 ;
失败:返回 -1 。
测试例程:
打开两个shell终端,在sig.c中设置信号捕获处理函数,kill.c中向指定进程中传入信号。
sig.c代码:
#include <stdio.h>
#include <signal.h>
void sig_handler(int sig)
{
printf("signal handler...\n");
}
int main()
{
signal(SIGINT,sig_handler);
while(1);
return 0;
}
kill.c代码:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc < 2){
printf("please input argv[1].\n");
exit(0);
}
kill(atoi(argv[1]),SIGINT);
return 0;
}
3.2 raise函数
相比于kill函数,raise函数只能对自身进程发送信号。
头文件:
#include <signal.h>
函数原型:
int raise(int sig)
参数:
sig:指定要发送的信号;
返回值:
成功:返回 0;
失败:返回非零值。
4. 信号其它API
4.1 alarm函数
alarm函数用于设置一个定时器,时间到达时,内核会向调用进程中发送SIGALRM信号。
头文件:
#include <unistd.h>
函数原型:
unsigned int alarm(unsigned int seconds)
参数:
seconds:以秒为单位设置时间;0 表示取消之前设置的定时器;
返回值:
成功:调用alarm()前已经设置过定时器了,且还没有超时,则返回剩余未超时的时间值,同时时间也被更新;如果之前未设置,则返回 0。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main()
{
int ret = -1;
int time = 10;
alarm(10);
sleep(5);
ret = alarm(10);
printf("ret = %d\n",ret);
while(1)
{
printf("sleep:%d\n",time--);
sleep(1);
}
return 0;
}
4.2 pause函数
系统调用函数pause()使进程停止,进入休眠状态,当捕获到信号,且从信号处理函数返回时,pause才退出返回,没有捕获信号之前pause一直返回 -1 。
头文件:
#include <unistd.h>
函数原型:
int pause(void)