目录
一、前言
由fork()开出的子进程和父进程间虽然是共享代码段、数据段、堆栈段但是父子进程间的数据是不共享的,也就是无法进行数据传递。进程间的IPC通信方式有两种,一种是带参数的信号sigaction()绑定,通过sigqueue()发送信号。另一种是不带参数的信号signal()绑定,通过kill()发送信号。
带参数的意思是信号发送可以将一个int类型的数据也发送出去,即实现父子进程数据传递。带参数的信号可以进行int类型的数据传递,而不带参数的信号则无法进行数据传递。本文介绍带参数的信号sigaction()绑定,和sigqueue()信号发送,验证int类型数据在两个进程间传递。
本文介绍带参数的信号sigaction()绑定,通过sigqueue()发送信号。
二、sigaction()库函数介绍
1、查看signation()函数手册
在linux下的终端输入man sigaction来查看函数原型及函数的相关介绍。
2、sigaction()函数原型
sigaction()函数用于改变进程接收到特定信号后的行为。
头文件:
#include <signal.h>
函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数:
signum:除sigkill及sigstop外的任何一个特定有效的信号
act:struct sigaction结构体指针
oldact:可指定为NULL
返回值:
函数成功返回0,失败返回-1
3、struct sigaction结构体
结构体前两个数据类型为函数指针,不是函数,不会破坏结构体的结构。sa_flags是一个标志位,当sa_flags=SA_SIGINFO时指定信号带参数,sa_flags=0时指定信号不带参数。
结构体原型:
struct sigaction {
void (*sa_handler)(int); //函数指针
void (*sa_sigaction)(int, siginfo_t *, void *); //函数指针
sigset_t sa_mask; //信号集
int sa_flags; //标志位
void (*sa_restorer)(void);
};
三、sigqueue()函数介绍
1、函数功能
sigqueue()函数为新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
sigaction():将信号绑定函数。
sigqueue():发送带参数信号后,执行信号绑定的函数。
2、函数原型
头文件:
#include <signal.h>
函数原型:
int sigqueue(pid_t pid, int sig, const union sigval value);
参数:
pid:指定接收信号的进程id
sig:发送的信号
value:联合数据结构union sigval,指定了信号传递的参数
返回值:
成功返回0,失败返回-1
3、联合数据结构union sigval
union sigval 联合数据结构指定了信号传递的参数,也就是通过sival_int保存一个int类型的数据,通过sigqueue函数将该int类型的数据传递出去。
虽然可以进行进程间数据传递,但是只能传递int类型数据,因为sival_ptr的功能还未开发,所以这个数据传递还是比较鸡肋的。
结构体原型:
union sigval {
int sival_int; //保存int类型的数据,用于数据传递
void *sival_ptr; //保存各种类型的指针,但是未开发这种方法
};
四、进程间数据传递测试
1、封装用于绑定的函数
先封装一个函数,用于sigaction()绑定信号。
void signal_function(int signum, siginfo_t* info, void*);
void signal_function(int signum, siginfo_t* info, void*)
{
int x = info->si_int;
cout << "pid = " << getpid() << " 函数被调用了 signum = " << signum << "传递的信息info = " << x << endl;
}
2、用到的头文件
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
3、主函数
测试一下父进程发送的信号所带的参数保存整型数据12345,子进程能否收到这个数据。
int main()
{
//带参数的信号
int pid = 0;
int status = 0;
struct sigaction act;
act.sa_sigaction = signal_function;//保存函数指针
act.sa_flags = SA_SIGINFO;//标志位,指定信号是带参数的
//带参数的信号绑定
sigaction(SIGRTMIN, &act, NULL);
pid = fork();
if (pid == 0)//子进程
{
while (1)
{
cout << "子进程运行中 pid = " << getpid() << endl;
sleep(1);
}
}
else if (pid > 0)//父进程
{
sleep(3);//先让子进程先处理业务,测试sigqueue函数
union sigval value;//联合结构体
value.sival_int = 12345; //保存传递的int类型的数据
for (int i = 1; i <= 5; i++)
{
cout << "父进程给子进程发送第 " << i << " 个信号" << endl;
//带参数发送信号
sigqueue(pid, SIGRTMIN, value);
}
sleep(3);//延时3秒
kill(pid, SIGKILL);//给子进程发送停止信号
waitpid(pid, &status, 0);//等待子进程结束,防止子进程托孤
cout << "子进程结束" << endl;
}
return 0;
}
运行结果如下:
可以发现子进程所在的函数收到了父进程发送的带参数信号,并且参数保存的数据12345也被传递给了子进程,也就是父子进程间数据传递成功。
五、信号屏蔽
当一个进程收到了一个信号,信号会导致进程中断,如果进程认识这个信号就去做绑定的函数,不认识的话,进程就终止了,也就是说不能随便发不认识的信号给进程。
那么如何避免呢?这时候就需要一个sigset_t(信号集),用于屏蔽信号集存放的信号,这样就不会因为乱发信号导致进程终止,不过前提是乱发的信号在屏蔽信号集里面。
信号集操作函数
函数 | 作用 |
int sigemptyset(sigset_t *set); | 清空信号集 |
int sigfillset(sigset_t *set); | 所有信号加进去 |
int sigaddset(sigset_t *set, int signo); | 增加信号 |
int sigdelset(sigset_t *set, int signo); | 删除信号集的某个信号 |
int sigismember(const sigset_t *set, int signo); | 信号是否在集合里面 |
sigprocmask()函数
函数原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how含义 :
SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set(位或运算)
SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask^set(位异或运算)
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set
set和oldset:
如果oldset是非空指针,则读取进程的当前信号屏蔽状态字通过oldset参数传出。
如果set是非空指针,则更改进程的信号屏蔽状态字,参数how只是如何更改。
如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。
返回值:
若成功则返回0,若出错则返回-1
代码测试
下面通过一段代码测试信号屏蔽
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int main()
{
//准备两个信号集()
sigset_t arry;//存放需要屏蔽的信号
sigset_t temp_arry;//存放未决信号
//信号集初始化
sigemptyset(&arry);
sigemptyset(&temp_arry);
//添加屏蔽的信号
sigaddset(&arry, SIGUSR1);//屏蔽第10个信号
if (sigprocmask(SIG_BLOCK, &arry, NULL) < 0) //更改进程的信号屏蔽字
{
perror("signal error");
exit(0);
}
while(1)
{
cout << "程序运行中 pid = " << getpid() << endl;
sleep(3);
sigpending(&temp_arry);//读取当前进程的未决信号集
if (sigismember(&temp_arry, SIGUSR1)) //如果未决信号集里有第10个信号
{
cout << "有未决信号进来 SIGUSER1 发过来了" << endl;
sigdelset(&temp_arry, SIGUSR1);
}
}
}
点击Visual Studio 菜单栏:生成——>重新生成解决方案。这时候在windows下代码所做的修改也会同步到linux下。
在linux下找到工程目录所在文件夹,鼠标右键打开终端输入:g++ main.cpp -o main
编译成功后输入:./main 运行一下
在另一个终端通过kill给这个main进程发送信号
可以发现被屏蔽第10号信号发送过去,会进入未决结果集,不会影响进程。而未被屏蔽的第13号信号发送过去,进程不知道这个信号,就直接结束进程了。
同时也印证了如果一个进程收到一个不认识的信号,进程不知道要干嘛(因为没有绑定相关函数),进程就直接终止掉了。
六、信号冲突
假设进程认识两个信号(两个信号都绑定了相关函数),当第一个信号发送过来,进程去执行这个信号绑定的函数业务,而业务还没做完,又发过来第二个信号。这时候第一个信号处理的函数业务就被打断了,转去做第二个信号绑定的函数业务,那这个时候就会出现信号冲突。
那么如何解决呢?可以通过sigaction()绑定信号,给处理业务时间长的函数通过sigaddset()添加屏蔽信号。下面通过一段代码来测试一下:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void test1(int i);
void test2(int i);
int main()
{
struct sigaction act1;
struct sigaction act2;
//保存函数指针
act1.sa_handler = test1;
act2.sa_handler = test2;
//指定信号不带参数
act1.sa_flags = 0;
act2.sa_flags = 0;
//信号绑定函数
sigaction(SIGUSR1, &act1, NULL);
sigaction(SIGUSR2, &act2, NULL);
//初始化信号集
sigemptyset(&(act1.sa_mask));
//屏蔽信号SIGUSR2
sigaddset(&(act1.sa_mask), SIGUSR2);
while (1)
{
cout << "进程运行中 pid = " << getpid() << endl;
sleep(2);
}
return 0;
}
void test1(int i)
{
cout << "函数 test1 被调用了 i = " << i << endl;
sleep(10);//延时10秒
cout << "函数 test1 结束了" << endl;
}
void test2(int i)
{
cout << "函数 test2 被调用了 i = " << i << endl;
}
和上面一样在linux下的终端输入:g++ main.cpp -o main
编译成功后输入:./main 运行一下
可以看到,发送第10号信号(SIGUSER1)后,立马再发送第12号信号(SIGUSER2),函数test1()并没有被打断,而是等函数test1()结束后才调用函数test2(),也就是sigaddset(&(act1.sa_mask), SIGUSR2)这段代码解决了信号冲突的问题。
那么将 sigaddset(&(act1.sa_mask), SIGUSR2) 这段代码注释掉,再测试一下。
可以看到函数test2()在函数test1()结束前被调用了,也就是产生了信号冲突,函数test1()被中断了。
注:
新的信号和正在处理的信号为同种信号,那么会进行排队,不会打断前一个信号执行的业务。
新的信号和正在处理的信号为异种信号,则会打断前一个信号执行的业务。
七、总结
信号的优点
1、进程和进程间可以起到通知的作用。
2、可以进行数据传递。
信号的缺点
1、只能传递int类型的数据。
2、进程收到不认识的信号的时候,会打断该进程的执行,需要通过信号屏蔽解决。
3、当进程正在处理一个信号后续操作时,还没做完,又来一个新的信号,会出现信号冲突问题。
原创不易,转载请标明出处。
对您有帮助的话可以点赞收藏+关注,会持续更新的(嘻嘻)。