linux远程开发——sigaction()库函数(带参数的信号发送与接收)

目录

一、前言

二、sigaction()库函数介绍

1、查看signation()函数手册

2、sigaction()函数原型

3、struct sigaction结构体

三、sigqueue()函数介绍

1、函数功能

2、函数原型

3、联合数据结构union sigval

四、进程间数据传递测试

1、封装用于绑定的函数

2、用到的头文件

3、主函数

五、信号屏蔽

信号集操作函数

sigprocmask()函数

代码测试

六、信号冲突

七、总结

信号的优点

信号的缺点


一、前言

        由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、当进程正在处理一个信号后续操作时,还没做完,又来一个新的信号,会出现信号冲突问题。

原创不易,转载请标明出处。

对您有帮助的话可以点赞收藏+关注,会持续更新的(嘻嘻)。

猜你喜欢

转载自blog.csdn.net/wmcy123/article/details/123604822