接上一篇文章本章先来学习带参数的信号以及sigqueue、sigaction等函数
基于VS2019 C++的跨平台(Linux)开发(1.4)——信号
一、引言
上篇文章我们提到的kill函数发送信号只是针对某一个进程,并没有体现带参数。之前也提到进程中不能进行数据共享,即使定义一个全局变量,在子进程间进行修改,在父进程里并不知道修改了没有。所以这种情况就需要IPC通信来解决,而kill不能传递数据,那么就需要使用带参数的信号
二、函数原型
1、sigaction库函数——绑定信号
功能:用于改变进程接收到特定信号后的行为。
头文件 <signal.h>
原型: int sigaction(int signum,const struct sigac tion *act,const struct sigaction *old);
参数
- 第一个参数为信号的值,可以为除sigkill及sigstop外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
- 第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
- 第三个参数oldact指向的对象用来保存原来对相应信号的处理,通常为null。
注意:参数二、三都是结构体指针。参数二最为重要,在这个结构体中 包含了信号处理函数,但是在使用的时候不能直接把函数赋值给第二个参数
返回值:函数成功返回0,失败返回-1
linux中查看sigaction函数如下
对应的第二个参数sigaction的结构体如下
c结构体中不能有函数,但这里只是保留函数指针,并没有破坏结构体中不能有函数的规定,对于结构体而言,它只是一个指针变量(比较特殊,指向函数)
其中第一个类似于之前提到的不带参数的函数指针,所以我们使用的是第二个函数实现带参数的信号,然后由sa_flags来判断是否带参数
2、 sigqueue库函数——发送信号
功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()成对出现使用。
原型: int sigqueue(pid_t pid, int sig, const union sigval value);
参数
- 第一个参数是指定接收信号的进程id
- 第二个参数确定即将发送的信号
- 第三个参数是一个联合数据结构union sigval(类似结构体 ,很少用),指定了信号传递的参数,即通常所说的4字节值。
返回值:成功返回0,失败返回-1
linux中查看sigqueue函数如下
对应的第二个参数的sigval联合体如下
其中 sigval_int就是要发送的整形数据
三、实现
1、示例代码
该例子实现父进程发送信号并传递数据给子进程
①之前的void signalFunc(int i) 只适用于无参信号的使用,所以重新定义的函数如下
void sigaction_func(int num, siginfo_t* info, void* vo)
第一个参数num就是收到的信号编码,收到的数据存在第二个参数info中
②sigqueue发送的数据必须是联合体的形式
#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);
int main(){
int pid = 0;
//准备结构体
struct sigaction act;
act.sa_sigaction = sigaction_func;
act.sa_flags = SA_SIGINFO;//带参数的信号
//绑定信号
sigaction(SIGUSR1,&act,NULL); //发送是sigqueue
pid = fork();
if (pid == 0)
{
while (true)
{
cout << "子进程运行中 ...pid =" << getpid() << endl;
sleep(1);
}
}
else if (pid > 0)
{
sleep(2);
//准备要传递的数据
union sigval value;
value.sival_int = 111;
//带参数发送信号
sigqueue(pid, SIGUSR1, value);//第三个参数跟之前不一样,是一个联合体
//卡住父进程
while (1) {}
}
return 0;
}
void sigaction_func(int num, siginfo_t* info, void* vo)
{
int x = info->si_int;
cout <<"当前进程pid = "<<getpid()<<" sigaction_func函数被调用了,num = " << num << " 信号传递过来的数据是 x = " << x<< endl;
}
2、运行结果
四、总结
前面例子中传递的数据只是一个整形,那么有的人可能注意到sigval联合体中的第二个参数void *(如下图),以为可以利用它来实现其他类型的数据,但并非如此。
1、在最早开发者开发信号的时候只支持int类型的数据传递,就想到预留一个void * 以便后续使用,但是遗憾的是到现在ubuntu20版本中还是不支持void *的数据传递,还没有进行更新,所以复杂点的数据(数组、结构体)就没法传递 ,就显得非常鸡肋。
2、因为IPC有五种通信方式,并不是只能用信号来实现进程间传递数据,因为信号麻烦的操作导致它只是通信的基础使用
3、当子进程接收到一个不认识的信号(比如把上述代码中sigqueue(pid, SIGUSR1, value);中的SIGUSR1改成SIGUSR2),而且没有进行绑定函数,运行结果如下,子进程不知道做什么事情就会被卡死
linux中再进行编译
此时,查看进程状态为Z+(僵尸状态——子进程先于父进程结束。子进程中有死循环,本不应该先结束,但因为子进程收到了一个不认识的信号就会被打断,子进程卡死在那,即原来本的事情不做,接下来也不懂做什么,就进入了僵尸状态,占用资源,应该避免的)
4、当我只有一个进程的时候,在linux中用kill给他发送信号
进程收到一个不认识的信号(10号信号),打印出用户自定义信号1,查看进程状态,发现找不到这个pid,即被中断销毁,就像起到了杀死进程的作用(前面的父进程给子进程发送不认识的信号,只是卡死)。所以要保证进程的正常执行,就需要使用信号屏蔽
小试牛刀
装载请注明出处
基于VS2019 C++的跨平台(Linux)开发(1.4.2)——带参数信号
学习信号屏蔽如下