前言:学习编程一定要敲,接着测试,然后查资料,最后总结!!!
一、为什么要掌握信号
linux中程序是运行在后台的,没有交互的界面。那么就需要使用信号来给这些程序传递命令。
服务程序运行在后台,如果想让中止它,强行杀掉不是个好办法,因为程序被杀的时候,程序突然死亡,没有释放资源,会影响系统的稳定,用Ctrl+c中止与杀程序是相同的效果。
如果能向后台程序发送一个信号,后台程序收到这个信号后,调用一个函数,在函数中编写释放资源的代码,程序就可以有计划的退出,安全而体面。
信号还可以用于网络服务程序抓包等,这是较复杂的应用场景,暂时不介绍。
二、什么是信号
signal信号是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断,从它的命名可以看出,它的实质和使用很像中断。
其实就是类似windows中的cmd命令,就是让linux中的bash命令让程序获取到并执行相关操作。
三、信号示例
1.linux自带信号处理示例
利用下面代码及bash命令实现linux系统自带的信号处理。
//simpledemo.cpp
#include <stdio.h>
#include <unistd.h>
void executeTask()
{
printf("execute task...\n");
}
int main()
{
printf("task begin---");
while (1)
{
sleep(2);
executeTask();
}
return 0;
}
编译后尝试下面的步骤:
#让一个程序在后台跑,就在后面加&符号,例:./simpledemo &
./程序名 &
#查看该进程 例:ps -df|grep simpledemo
ps -df|grep 程序名
#利用ID终止进程 例:kill 5555
kill 进程ID
#终止掉所有名称的进程,如果启动了多个进程就可以用这个命令 例:killall simpledemo
killall 程序名
上面这些bash命令其实就是给linux系统内核shell传递信号,然后shell执行相关的底层代码并返回结果,类比到我们编写的代码就有下面图片中的作用:
2.自定义代码信号处理示例:
//simpledemo.cpp
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void executeTask()
{
printf("execute task...\n");
}
void signalfunc(int sig)
{
printf("Code get signal:%d",sig);
}
int main()
{
signal(SIGINT,signalfunc);//2 SIGINT:ctrl+c
signal(SIGTERM,signalfunc);//15 SIGTERM:kill
printf("task begin---");
while (1)
{
sleep(2);
executeTask();
}
return 0;
}
用上面的代码,再接收到下面bash命令时就会和上面1.linux自带信号处理示例不同。
#让一个程序在后台跑,就在后面加&符号
./simpledemo.cpp &
#查看该进程
ps -df|grep simpledemo
#因为我们代码接收到信号会做代码规定的处理(代码接收一次信号会执行一次信号对应的函数signalfunc),而不会执行shell默认的处理,所以下面的kill中止命令会失效,这时候需要给kill命令加参数使shell接收到的命令还通过系统来终止。
#通过kill加参数的方法可以杀死进程,是因为加了参数后信号值就变了。代码中没定义处理函数就会走shell默认。
kill -9 simpledemo
#利用ID终止进程 例:kill 5555
kill 进程ID
#终止掉所有名称的进程,如果启动了多个进程就可以用这个命令,这个命令其实就是多个kill ID命令一起执行而已。
killall simpledemo
四、信号的基本概念
软中断信号(signal,又简称为信号)用来通知进程发生了事件。进程之间可以通过调用kill库函数发送软中断信号。Linux内核也可能给进程发送信号,通知进程发生了某个事件(例如内存越界)。
注意,信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据,进程对信号的处理方法有三种:
1)第一种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
2)第二种是设置中断的处理函数,收到信号后,由该函数来处理。
3)第三种方法是,对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。
五、信号的类型
下面加粗的是常用的,其他不怎么用到。
六、库函数signal
signal库函数可以设置程序对信号的处理方式。
- 函数声明:
sighandler_t signal(int signum, sighandler_t handler);
- 参数说明:
参数signum表示信号的编号。
参数handler表示信号的处理方式,有三种情况:
1)SIG_IGN:忽略参数signum所指的信号。
2)一个自定义的处理信号的函数,信号的编号为这个自定义函数的参数。
3)SIG_DFL:恢复参数signum所指信号的处理方法为默认值。
程序员不关心signal的返回值。
七、可靠信号和不可靠信号
不可靠信号合并多次信号示例:
下面的代码在接收到ctrl+c的信号后,只会执行一次。而可靠信号会执行触发信号的相应次数。并且可靠信号是按顺序执行,先得到信号将信号函数执行完后,再执行下一次。
//bukekaosignal.cpp
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void executeTask()
{
printf("execute task...\n");
}
void signalfunc(int sig)
{
printf("Code get signal:%d\n", sig);
for (int i = 0; i < 5; i++)
{
sleep(1);
printf("sig%d:%d\n",sig,i);
}
}
int main()
{
signal(SIGINT, signalfunc); //2 SIGINT:ctrl+c 不可靠信号
signal(34, signalfunc); // 34 可靠信号触发方式: killall -34 bukekaosignal
printf("task begin---\n");
while (1)
{
sleep(1);
executeTask();
}
return 0;
}
八、信号处理函数被中断
这个很好尝试,还是利用“七、可靠信号和不可靠信号”的bukekaosignal.cpp
,ctrl+c
后再killall 34 bukekaosignal
就可以看到效果。
九、信号阻塞(延迟处理)
1. 方法一:集合sigset_t
这里用到一个新集合,也就是信号集:sigset_t
它的使用效果步骤是:
1.先使用signal()让程序获取到信号。
2.将信号填充至集合
3.设置集合中信号状态。如block
4.在固定地方再次设置集合状态。如unblock
注:如果缺失第一步signal,那直接进行2、3、4步是无效的。
//大概用法如下:
//这个地方有个要注意的点就是必须是signal()函数设置过的信号并且在sigset_t结合中的信号,在sigprocmask的状态设置才有效。
sigset_t set;//创建信号集
sigaddset(&set, 15);//添加单个信号
sigfillset(&set);//将所有信号添加到集合集中
sigprocmask(SIG_BLOCK, &set, NULL);//block 设置信号集状态
sigprocmask(SIG_UNBLOCK, &set, NULL);//unblock 设置信号集状态
这个可以使不可靠信号也实现先处理完之前获取到的信号后,在处理下一个信号。下面代码示例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void executeTask()
{
printf("execute task...\n");
}
void signalfunc(int sig)
{
//设置某信号阻塞锁,然后在某一时刻再解锁
sigset_t set;//创建信号集
sigaddset(&set, 15);//添加信号
sigfillset(&set);//将所有信号添加到集合集中
sigprocmask(SIG_BLOCK, &set, NULL);//block 设置信号集状态
printf("Code get signal:%d\n", sig);
for (int i = 0; i < 5; i++)
{
sleep(1);
printf("sig%d:%d\n",sig,i);
}
sigprocmask(SIG_UNBLOCK, &set, NULL);//unblock 设置信号集状态
}
int main()
{
signal(SIGINT, signalfunc); //2 SIGINT:ctrl+c 不可靠信号
signal(15, signalfunc); // 15 killall -15 bukekaosignal
printf("task begin---\n");
int ii;
while (1)
{
ii++;
sleep(1);
executeTask();
}
return 0;
}
2. 方法二:结构体sigaction
下面简单介绍使用方法:
//下面是使用sigaction获取信号,以及相应处理函数的方式
//下面的方式和signal(SIGINT, signalfunc); signal(15, signalfunc);的效果是一样的
struct sigaction stact;
memset(&stact,0,sizeof(stact));//初始化
stact.sa_handler=handlerfunc;//指定信号处理函数
//①
sigaction(2,&stact,NULL);//指定信号2的处理行为
sigaction(15,&stact,NULL);//指定信号15的处理行为
sigaction实现阻塞相比较signal
配合sigset_t
使用要简单一些;sigaction指定某信号阻塞只需要添加一行代码,但要注意添加位置:
//这行代码一定要添加在指定信号处理行为的前面和指定信号处理函数的后面。也就是加到上面//①的位置。
sigaddset(&stact.sa_mask,15);//指定阻塞
sigaction结构体的sa_flags
参数:设置为SA_RESTART表示如果信号中断了进程的某个系统调用,则系统自动启动该系统调用,示例代码如下:
//可以使用此代码尝试设置sa_flags=SA_RESTART和不设置的区别。
//不设置:下面的scanf的系统调用会中断掉
//设置后:下面的scanf的系统调用会重启。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <cstring>
void handlerfunc(int sig)
{
printf("Code get signal:%d\n", sig);
for (int i = 0; i < 5; i++)
{
sleep(1);
printf("sig%d:%d\n",sig,i);
}
}
int main()
{
struct sigaction stact;
memset(&stact,0,sizeof(stact));//初始化
stact.sa_handler=handlerfunc;//指定信号处理函数
stact.sa_flags=SA_RESTART;//如果信号中断了进程的某个系统调用,则系统自动启动该系统调用。
sigaddset(&stact.sa_mask,15);//指定阻塞
sigaction(2,&stact,NULL);//指定信号2的处理行为
sigaction(15,&stact,NULL);//指定信号15的处理行为
printf("task begin---\n");
char str[20];
scanf("%s",str);
printf("%s\n",str);
return 0;
}
备注:参考资料