一、实现要求
二、实现思路
大致框架:
信号绑定函数(触发函数)如下:
1、确定A\B\C\D四个进程的关系是父子关系:A fork B、B fork C、C fork D(或者A是父亲,BCD都是A的孩子;再或者main函数是父亲,ABCD是兄弟也可以)
2、此时D就是A的重孙子,可以看出B、C既有父亲也有孩子;A有孩子没有父亲;D有父亲没有孩子。那么怎么区分B、C呢?因为触发函数中有num(收到的信号编号),B、C收到的信号不同,所以可以根据num判断(num为10表示信号SIGUSER1判断是B进程、num为12判断是C进程,注意不能直接判断num是否等于12,因为可能是C也可能是A),这些都是放在信号绑定函数中操作
3、可以区分A\B\C\D四个进程,接下来就好办了。用两个变量存四个进程的父亲或者孩子,比如A有孩子没有父亲,只要存孩子的pid;B、C有孩子都要存孩子pid(因为发送都是针对孩子);D有父亲没有孩子存父亲pid。接着就能在信号绑定函数判断这两个变量,从而知道是哪个进程发送什么信号,但是D进程给A进程发送信号就不能直接利用父亲或者孩子变量,而是要使用getpgid函数返回一个进程组组长ID(A进程ID)。
4、绑定和发送信号
- 绑定信号sigaction,在fork之前(注意不能放在进程开始之前,否则4个进程都会有4个信号,第一,会产生信号冲突,还要进行信号屏蔽;第二,会占用更多内存空间,即A进程也会认识信号SIGRTMIN等,没有意义)
- A用sigqueue发送信号在main中,B、C、D发送信号在信号绑定函数中
经典错误:
①在子进程或者父进程中发送信号(可以直接在触发函数中发送 )
②认为有4个进程要转接4次,写了4个触发函数(函数中都是收到数据+1发送给下一个,他们的操作的逻辑一样,可以直接封装成一个信号绑定函数)
三、代码
#include <unistd.h>//
#include <string.h>
#include <sys/types.h>
#include<iostream>
//信号屏蔽
#include <signal.h>
#include<stdio.h>
#include <stdlib.h>
using namespace std;
//带参数的信号
void sigaction_func(int num, siginfo_t* info, void* vo);
//为了区分 保存父亲和孩子(fork之后也会拷贝走)
int father_flag = 0;
int child_flag = 0;
pid_t pid = 0;
int main()
{
pid = fork();
if (pid > 0)//A进程 绑定SIGUSR2 发送SIGUSR1
{
struct sigaction sig_act2;
sig_act2.sa_sigaction = sigaction_func;//绑定函数
sig_act2.sa_flags = SA_SIGINFO;//带参数
sigaction(SIGUSR2, &sig_act2, NULL);
cout << "A进程 pid = " << getpid() << endl;
child_flag = pid;//father_flag = 0
sleep(3);
union sigval sig_val;
sig_val.sival_int = 1001;
sigqueue(child_flag, SIGUSR1, sig_val);
while (true)
{
}
}
else if (pid == 0)//B进程 发送SIGUSR2
{
cout << "B进程 pid = " << getpid() << endl;
pid = fork();
if (pid > 0)
{
struct sigaction sig_act2;
sig_act2.sa_sigaction = sigaction_func;//绑定函数
sig_act2.sa_flags = SA_SIGINFO;//带参数
sigaction(SIGUSR1, &sig_act2, NULL);
child_flag = pid;
father_flag = getppid();
while (true)
{
}
}
else if (pid == 0)//C进程 发送SIGRTMIN
{
struct sigaction sig_act2;
sig_act2.sa_sigaction = sigaction_func;//绑定函数
sig_act2.sa_flags = SA_SIGINFO;//带参数
sigaction(SIGUSR2, &sig_act2, NULL);
cout << "C进程 pid = " << getpid() << endl;
pid = fork();
if (pid > 0)
{
child_flag = pid;
father_flag = getppid();
while (true)
{
}
}
else if (pid == 0)//D进程 绑定SIGRTMIN 发送SIGUSR2
{
struct sigaction sig_act2;
sig_act2.sa_sigaction = sigaction_func;//绑定函数
sig_act2.sa_flags = SA_SIGINFO;//带参数
sigaction(SIGRTMIN, &sig_act2, NULL);
cout << "D进程 pid = " << getpid() << endl;
father_flag = getppid();
while (true)//死循环避免孤儿进程或者僵尸进程
{
}
}
}
}
return 0;
}
//信号对应的函数,参数对应的就是信号编码
void signalFunc(int i)
{
cout << "函数被调用了signalFunc i= " << i << endl;
}
void sigaction_func(int num, siginfo_t* info, void* vo)
{
//cout << "触发函数被调用了sigaction_func num= " << num << "info->si_int= " << info->si_int << endl;
int x = info->si_int;//保存传递的数据
if (father_flag != 0 && child_flag != 0)//B或者C进程
{
if (num == 10)
{
cout << "进程pid = " << getpid() << " 数据x= " << x << endl;
union sigval sig_val;
sig_val.sival_int = x + 1;//加1操作
sigqueue(child_flag,SIGUSR2, sig_val);//发送SIGUSR2信号
}
else if (num == 12)
{
cout << "进程pid = " << getpid() << " 数据x= " << x << endl;
union sigval sig_val;
sig_val.sival_int = x + 1;//加1操作
sigqueue(child_flag, SIGRTMIN, sig_val);//发送SIGRTMIN信号
}
}
else if (father_flag == 0 && child_flag != 0)//A进程
{
//只要接收信号即可
cout << "进程pid = " << getpid() << " 数据x= " << x << endl;
}
else if (father_flag != 0 && child_flag == 0)//D进程
{
cout << "进程pid = " << getpid() << " 数据x= " << x << endl;
if (num == 34)
{
union sigval sig_val;
sig_val.sival_int = x + 1;//加1操作
//getpgid返回一个进程组组长ID ——A进程
sigqueue(getpgid(father_flag), SIGUSR2, sig_val);//发送SIGUSR2信号
}
else
{
cout << "这里不会被执行 " << endl;
}
}
}
四、代码分析以及运行结果
1、进程A一定是先开启的,B、C、D进程都是A开启过程中才陆续开启,即使可以认为是同步执行,但是按毫秒级别上来看,还是有一定的差距。所以在A进程中做一个延时(slepp),目的是为了让B、C、D进程陆续创建完成并打开,信号全部绑定成功。否则在A进程里面马上发送信号就会失败(没绑上就发了,B进程根本接收不到)
2、每一个进程都要加一个死循环,让他们不会走到return退出,避免出现孤儿进程或者僵尸进程(A是最早发送信号也是最晚接收信号的,如果这个过程B、C、D进程其中一个进程结束就会导致A收不到信号)
3、注意,点击运行后出现如下图的异常(用户定义的信号有2个),控制台未打印出A进程收到的数据(即A进程未收到信号),这时候可能就懵逼了,因为A进程就绑定了一个信号,那么为什么会出现2个呢,该如何解决呢。
- VS2019运行起来就是进程,VM虚拟机运行起来也是一个进程,这两个进程跨平台通信,如果在VS2019代码运行失败出现问题(比如这个异常),就会从虚拟机中抛出错误发送到VS2019,这个过程就叫做IPC通信(即两个进程同时运行的情况下还能传递数据)。
- 因为我们使用的是gbd调试工具,这个gdb通过在VS中打断点去接收虚拟机发送的信号,也就是ubuntu虚拟机在编译c++之后,如果产生异常,通过信号的方式返回到VS。实质这个异常不是一个错误,因为同时有两个信号(gdb、SIGUSR2)从ubuntu中发给VS的A进程发送了冲突,导致A进程不知所措,不知道先处理哪个信号。
- 这时你可以会想用信号屏蔽的方式解决信号冲突,但两个信号都是必须接收的,加上你也不知道gdb是哪种信号,就算你知道gdb是哪种信号,你如果把他屏蔽了就用不了gdb调试工具,就会导致工程在VS中运行不起来,所以不可行。
- 解决:只要点击VS菜单栏上的“继续”,就会走过gdb、SIGUSR2两个信号。因为代码没有报错,不需要处理gdb信号,只会处理SIGUSR2信号。此时A进程就会收到SIGUSR2信号,在控制台打印出数据
转载请注明出处