🐧 1. signal信号
- signal信号是Linux编程中非常重要的部分,接下来将详细介绍信号的基本概念、实现和使用,和与信号的几个系统调用(库函数)。
- signal信号是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断,从它的命名可以看出,它的实质和使用很象中断。
🐧 2. 信号的基本概念
- 软中断信号(signal,又简称为信号)用来通知进程发生了事件。进程之间可以通过调用kill库函数发送软中断信号。Linux内核也可能给进程发送信号,通知进程发生了某个事件(例如内存越界)。
- 注意,信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据,进程对信号的处理方法有三种:
- 1)第一种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
- 2)第二种是设置中断的处理函数,收到信号后,由该函数来处理。
- 3)第三种方法是,对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。
🐧 3. 信号的类型
发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:
信号名 | 信号值 | 默认处 理动作 | 发出信号的原因 |
SIGHUP | 1 | A | 终端挂起或者控制进程终止 |
SIGINT | 2 | A | 键盘中断Ctrl+c |
SIGQUIT | 3 | C | 键盘的退出键被按下 |
SIGILL | 4 | C | 非法指令 |
SIGABRT | 6 | C | 由abort(3)发出的退出指令 |
SIGFPE | 8 | C | 浮点异常 |
SIGKILL | 9 | AEF | 采用kill -9 进程编号 强制杀死程序。 |
SIGSEGV | 11 | C | 无效的内存引用 |
SIGPIPE | 13 | A | 管道破裂:写一个没有读端口的管道 |
SIGALRM | 14 | A | 由alarm(2)发出的信号 |
SIGTERM | 15 | A | 采用“kill 进程编号”或“killall 程序名”通知程序。 |
SIGUSR1 | 30,10,16 | A | 用户自定义信号1 |
SIGUSR2 | 31,12,17 | A | 用户自定义信号2 |
SIGCHLD | 20,17,18 | B | 子进程结束信号 |
SIGCONT | 19,18,25 | 进程继续(曾被停止的进程) | |
SIGSTOP | 17,19,23 | DEF | 终止进程 |
SIGTSTP | 18,20,24 | D | 控制终端(tty)上按下停止键 |
SIGTTIN | 21,21,26 | D | 后台进程企图从控制终端读 |
SIGTTOU | 22,22,27 | D | 后台进程企图从控制终端写 |
别看有这么多信号,其实常用的也就那几个红色的信号
处理动作一项中的字母含义如下
A 缺省的动作是终止进程。
B 缺省的动作是忽略此信号,将该信号丢弃,不做处理。
C 缺省的动作是终止进程并进行内核映像转储(core dump),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员 提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去。
E 信号不能被捕获。
F 信号不能被忽略。
🐧 4. signal 函数
signal库函数可以设置程序对信号的处理方式。
函数声明:
sighandler_t signal(int signum, sighandler_t handler);
参数signum表示信号的编号。
参数handler表示信号的处理方式,有三种情况:
1)SIG_IGN:忽略参数signum所指的信号。
2)一个自定义的处理信号的函数,信号的编号为这个自定义函数的参数。
3)SIG_DFL:恢复参数signum所指信号的处理方法为默认值。
一般不关心signal的返回值。
🐧 5. 信号有什么用
- 服务程序运行在后台,如果想让中止它,强行杀掉不是个好办法,因为程序被杀的时候,程序突然死亡,没有释放资源,会影响系统的稳定,用Ctrl+c中止与杀程序是相同的效果。
- 如果能向后台程序发送一个信号,后台程序收到这个信号后,调用一个函数,在函数中编写释放资源的代码,程序就可以有计划的退出,安全而体面。
- 信号还可以用于网络服务程序抓包等。
🐧 6. 可靠信号与不可靠信号
信号值 1 ~ 32 为不可靠信号 信号会丢失
信号值 34 ~ 64 为可靠信号 信号不会丢失
🐧 7. 应用实例
在实际开发中,在main函数开始的位置,程序员会先屏蔽掉全部的信号。
//屏蔽掉全部的信号
for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN);
- 这么做的目的是不希望程序被干扰。然后,再设置程序员关心的信号的处理函数。
- 程序员关心的信号有三个:SIGINT、SIGTERM和SIGKILL。
- 程序在运行的进程中,如果按Ctrl+c,将向程序发出SIGINT信号,信号编号是2。
- 采用“kill 进程编号”或“killall 程序名”向程序发出的是SIGTERM信号,编号是15。
- 采用“kill -9 进程编号”向程序发出的是SIGKILL信号,编号是9,此信号不能被忽略,也无法捕获,程序将突然死亡。
- 所以,程序员只要设置SIGINT和SIGTERM两个信号的处理函数就可以了,这两个信号可以使用同一个处理函数,函数的代码是释放资源。
示例 signal.cpp
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void EXIT(int sig)
{
printf("收到了信号%d,程序退出。\n",sig);
// 在这里添加释放资源的代码
exit(0); // 程序退出。
}
int main()
{
// 屏蔽全部的信号
for (int ii=0;ii<100;ii++)
signal(ii,SIG_IGN);
// 设置SIGINT和SIGTERM的处理函数
// 第一个参数也可以用对应的信号值表示 例如signal(2,EXIT);
signal(SIGINT,EXIT);
signal(SIGTERM,EXIT);
while (1) // 一个死循环
{
sleep(10);
}
}
//编译
g++ signal.cpp -o signal
//运行
./signal
运行效果
首先用 Ctrl + c 终止程序,收到了信号2,接着用 killall signal 收到了信号15。不管是用Ctrl+c还是kill,程序都能体面的退出。
🐧 8. 信号处理函数被中断
- 当一个信号到达后,调用处理函数,如果这时候有其他的信号发生,会中断之前的处理函数,等新的信号处理函数执行完毕后在继续执行之前的处理函数
- 如果是同一个信号的话会排队阻塞
示例 signal.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// 子进程退出时调用的函数
void hdfunc(int sig)
{
printf("sig=%d\n",sig);
for (int jj=1;jj<6;jj++) {
printf("jj(%d)=%d\n",sig,jj);
sleep(1);
}
}
int main()
{
signal(2,hdfunc);
signal(15,hdfunc);
for (int ii=1;ii<100;ii++) {
printf("ii=%d\n",ii);
sleep(1);
}
return 0;
}
运行效果
同时给程序发送两个信号,最后强行杀死
你请注意看运行效果,收到信号15之后执行该函数里面的代码,但是突然又收到了信号2,所以信号15被中断了,须等待2信号执行完毕之后再执行15信号, 是同一个信号的话会排队阻塞。如果不希望在接到信号时中断当前的处理函数,也不希望忽略该信号,而是延时一段时间再处理这个信号,这个时候要用到信号的阻塞。
🐧 9. 信号的阻塞
- 信号的阻塞和忽略信号是不同的,被阻塞的信号也不会影响进程的行为,信号只是暂时被阻止传递
- 进程忽略一个信号时,信号会被传递出去,但进程会将信号丢失
示例 signal.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// 子进程退出时调用的函数
void hdfunc(int sig)
{
sigset_t set;//信号集
sigemptyset(&set);
sigaddset(&set,15);
sigprocmask(SIG_BLOCK,&set,NULL);//阻塞信号
printf("sig=%d\n",sig);
for (int jj=1;jj<6;jj++) {
printf("jj(%d)=%d\n",sig,jj);
sleep(1);
}
//sigprocmask(SIG_UNBLOCK,&set,NULL);//解除阻塞
}
int main()
{
signal(2,hdfunc);
signal(15,hdfunc);
for (int ii=1;ii<100;ii++) {
printf("ii=%d\n",ii);
sleep(1);
}
return 0;
}
运行效果
和之前一样,同时给程序发送两个信号,最后强行杀死
你请注意看运行效果,收到信号2之后执行该函数里面的代码,突然又收到了15的信号,但这次不会中断当前的处理函数,而是把15的信号阻塞了,等2信号执行完毕之后再执行15信号。
🐧 10. 功能更强大的 sigaction
sigaction 也可以实现信号阻塞的功能
示例 sigaction.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void hdfunc(int sig)
{
printf("sig=%d\n",sig);
for (int jj=0;jj<5;jj++) {
printf("jj(%d)=%d\n",sig,jj);
sleep(1);
}
}
int main()
{
// signal (2,hdfunc);
// signal (15,hdfunc);
struct sigaction stact;
memset(&stact,0,sizeof(stact)); //初始化
stact.sa_handler = hdfunc; //指定信号处理函数
sigaddset (&stact.sa_mask,2); //指定需要阻塞的信号
sigaddset (&stact.sa_mask,15); //指定需要阻塞的信号
stact.sa_flags=SA_RESTART; //如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
sigaction (2,&stact,NULL); //设置信号2的处理行为
sigaction (15,&stact,NULL); //设置信号15的处理行为
char str[5];
memset (str,0,sizeof(str));
scanf("%s",str);
printf("str=%s\n",str);
/*
for (int ii=1;ii<100;ii++) {
printf("ii=%d\n",ii);
sleep(1);
}
*/
return 0;
}
运行效果
程序开始在等待用户的输入,这个时候同时给程序发送了两个信号,最后输入字符串,程序结束。
可以看运行效果,没有出现信号中断的情况。
本篇文章的内容大多数来源于:C语言技术网(www.freecplus.net),我只是把我从里面学习到知识分享给大家,一是为了巩固知识,二是为了自己以后忘了回来看看自己的博客哈哈,希望可以帮到有需要的人。