一、目标
- 信号概念
- signal、sigaction函数
- 信号集操作函数
- 时钟
本章先来学习不带参数的信号以及kill、signal函数
二、信号
1、概念
- 信号是系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。可以是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等
- 进程可以生成信号、捕捉并响应信号或屏蔽信号
2、用途
进程间通信,之前提到进程之间的数据是不能进行互通的,所以就要用到进程间通信技术,这个信号就是其中之一 ,就类似Qt里面的信号和槽(在界面切换的时候可以用到),(开发Qt的时候实际他就是参照linux内核编程进行开发)
3、回顾kill命令
kill不是杀死而是发送:我们要突破之前的一个错误认知,单纯的认为kill -9 pid就是杀死进程,实际上并不是这样,要把他理解为发送第九号信号(SIGKILL),去结束后面的pid所对应的进程。查询kill支持的信号,如下图所示
从上图仔细观察会发现,中间少了32号、33号,这其实是做一个分界线,1-31被归为不可靠信号,43到之后的被归为可靠信号(会在以下代码中进行验证)。其中10、12供程序员开发使用
4、信号名称
信号的名称是在头文件 signal.h里定义的,部分名称如图
5、进程与信号(signal库函数)
如果一个进程想要通知另一个进程做一些事情,就可以利用信号,所以涉及到signal库函数,如下
头文件<signal.h>
函数原型:
void (*signal(int sig, void (*func)(int))) (int);
signal是一个带sig和func两个参数的函数,准备捕捉或屏蔽的信号由参数sig给出,接收到指定信号时将要调用的函数由func给出。
func这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void func也可以是下面两个特殊值:
- SIG_IGN 屏蔽该信号
- SIG_DFL 恢复默认行为
linux中查看signal库函数如下图
其中signum是信号的编号,如第九号信号kill(停止进程),sighandler表示信号到达的时候进程要做什么事情;
typedef void (*sighandler_t)(int); 函数名前带一个*,表示函数指针,即做什么函数的逻辑要利用函数指针(一个信号对应一个函数,就类似Qt中将信号和槽绑定一起的connect)
回顾 int kill(pid_t pid, int sig);
进程可以通过调用kill向包括它本身在内的另一个进程发送信号。如果程序没有发送该信号的权限,对 kill的调用就将失败。
kill函数的作用是把参数sig给定的信号发送给标识号为pid的进程。 要想发送一个信号,发送者进程必须拥有相应的权限。这通常意味着两个进程必须拥有同样的用户ID
三、代码及实现效果
1、示例代码
#include <unistd.h>//
#include <string.h>
#include <sys/types.h>
#include<iostream>
#include <signal.h>
using namespace std;
void signalFunc(int i);
int main(){
int pid = 0;
//将信号和函数进行绑定,这个signal再开启子进程的时候会给拷贝走(这样子进程才能认识这个信号以及中断处理的函数)
signal(SIGUSR1, signalFunc);
signal(SIGUSR2, signalFunc);
pid = fork();
if (pid == 0)
{
//信号产生进程中断正在执行的业务
//立刻执行信号对应的函数逻辑
//执行以后回到原来的业务继续执行
while (true)
{
cout << "子进程运行中 ...pid =" << getpid()<< endl;
sleep(1);
}
}
else if(pid >0)
{
//睡一会,等孩子运行起来的时候再发送信号(创造比子进程晚一点 )
sleep(5);
//父亲给孩子发送信号
kill(pid,SIGUSR1);
sleep(5);
kill(pid, SIGUSR2);
while (true)
{
cout << "父进程运行中 ...pid =" << getpid() << endl;
sleep(1);
}
}
return 0;
}
//信号对应的函数,参数对应的就是信号编码
void signalFunc(int i)
{
cout << "函数被调用了signalFunc i= " << i << endl;
}
因为每个进程都会有对应的PCB,代码段,数据段和堆栈段等,fork ()创建的子进程自然也有代码段,通过拷贝一份父进程代码段(即整个cpp文件的内容,自然fork ()之前的signal也会给拷贝走,这样子进程才能认识这个信号以及中断处理的函数),所以就能进行信号的接收和发送的动作
2、效果
①一个信号对应一个函数
为什么i打印出来是10呢?因为信号对应的函数,参数对应的就是信号编码,传递的是SIGUSR1对应10
② 两个信号对应一个函数(signal两次)
③父进程连续发送5次信号
两个不同的信号可以绑定同一个函数,多个信号也一样
④ 删去sleep,连续发送5次信号,就收到一次 i=10
没有sleep则一下子发送五次信号,但是就收到了一次。区别就在于这个sleep,有sleep表示发送一次信号休息一会,五次都能收到。
⑤同样没有sleep的情况,此时我们换成发送34号信号(可靠信号),如下图
没有sleep,却连续接收到5次,这也就是可靠信号和不可靠信号的差别
总结:
可靠信号一定会被发送成功,且一定被收到。不可靠信号在连续不中断的情况下(没有sleep)发送下会丢失
原创不易,转载请注明出处:
基于VS2019 C++的跨平台(Linux)开发(1.4)——信号
下面进入信号第二部分的学习: