【Linux系统编程】信号 (下)

00. 目录

01. 信号集

为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友。

信号集是用来表示多个信号的数据类型(sigset_t)。

信号集相关的操作主要有如下几个函数:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigismember(const sigset_t *set, int signum);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

测试代码:

#include <signal.h>
#include <stdio.h>
 
int main(int argc, char *argv[])
{
	sigset_t set;	// 定义一个信号集变量
	int ret = 0;
 
	sigemptyset(&set); // 清空信号集的内容
	
	// 判断 SIGINT 是否在信号集 set 里
	// 在返回 1, 不在返回 0
	ret = sigismember(&set, SIGINT);
	if(ret == 0){
		printf("SIGINT is not a member of set \nret = %d\n", ret);
	}
		
	sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
	sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
	
	// 判断 SIGINT 是否在信号集 set 里
	// 在返回 1, 不在返回 0
	ret = sigismember(&set, SIGINT);
	if(ret == 1){
		printf("SIGINT is a member of set \nret = %d\n", ret);
	}
	
	sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
	
	// 判断 SIGQUIT 是否在信号集 set 里
	// 在返回 1, 不在返回 0
	ret = sigismember(&set, SIGQUIT);
	if(ret == 0){
		printf("SIGQUIT is not a member of set \nret = %d\n", ret);
	}
	
	return 0;
}

测试结果

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c 
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out  
SIGINT is not a member of set 
ret = 0
SIGINT is a member of set 
ret = 1
SIGQUIT is not a member of set 
ret = 0
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ 

02. 信号阻塞集

信号阻塞集(屏蔽集、掩码)

信号阻塞集也称信号屏蔽集、信号掩码。**每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。**信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。

所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
	检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

参数:
how: 信号阻塞集合的修改方法,有 3 种情况:
	SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。
	SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。
	SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照set
    中的信号重新设置信号阻塞集。

set: 要操作的信号集地址。
	若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。

oldset: 
	保存原先信号阻塞集地址

返回值:
	成功:0,
	失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。

注意:不能阻塞 SIGKILL 和 SIGSTOP 等信号,但是当 set 参数包含这些信号时 sigprocmask() 不返回错误,只是忽略它们。另外,阻塞 SIGFPE 这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

void fun(int signo)
{
    printf("\033[31mcatch signal %d\033[0m\n", signo);
}

int main(void)
{
    int i = 0;
    sigset_t set;
    sigset_t oldset;

    signal(SIGINT, fun);
    signal(SIGQUIT, fun);

    printf("================================\n");
    sigemptyset(&set);

    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    //设置阻塞信号  并集
    if (-1 == sigprocmask(SIG_BLOCK, &set, &oldset))
    {
        printf("sigprocmask failed...\n"); 
        goto err0;
    }

    sleep(5);
    
    //还原信号集
    if (-1 == sigprocmask(SIG_SETMASK, &oldset, NULL))
    {
        printf("sigprocmask failed...\n"); 
        goto err0;
    }


    return 0;
err0:
    return 1;
}

测试结果:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$ gcc 15sigprocmask.c 
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$ ./a.out 
================================
^C^C^C^C^C^Ccatch signal 2
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$ 

03. sigaction函数

从 UNIX 系统继承过来的信号(SIGHUP~SIGSYS,前 32 个)都是不可靠信号,不支持排队(多次发送相同的信号, 进程可能只能收到一次,可能会丢失)。

SIGRTMIN 至 SIGRTMAX 的信号支持排队(发多少次, 就可以收到多少次, 不会丢失),故称为可靠信号

可靠信号就是实时信号,非可靠信号就是非实时信号

signal() 函数只能提供简单的信号安装操作,使用 signal() 函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。

signal() 函数主要用于前面 32 种不可靠、非实时信号的处理,并且不支持信号传递信息。

Linux 提供了功能更强大的 sigaction() 函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。

下面我们一起学习其相关函数的使用。

sigqueue函数

#include <signal.h>


int sigqueue(pid_t pid, int sig, const union sigval value);
功能:
	给指定进程发送信号。
参数:
	pid: 进程号。
	sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)
    进行相应查看。
	value: 通过信号传递的参数。

    union sigval 类型如下:
    union sigval
    {
        int   sival_int;
        void *sival_ptr;
    };

返回值:
	成功:0
	失败:-1

sigaction函数

int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );

功能:
	检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
	signum:要操作的信号。
	act:要设置的对信号的新处理方式(设置)。
	oldact:原来对信号的处理方式(设置)。

	如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,
    则系统将此前指定信号的处理方式(设置)存入 oldact。

返回值:
	成功:0
	失败:-1

struct sigaction 结构体

struct sigaction
{
	/*旧的信号处理函数指针*/
	void (*sa_handler)(int signum) ;
	/*新的信号处理函数指针*/
	void (*sa_sigaction)(int signum, siginfo_t *info, void *context);
	sigset_t sa_mask;/*信号阻塞集*/
	int sa_flags;/*信号处理的方式*/
};
sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,
应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:
    SIG_IGN:忽略该信号
    SIG_DFL:执行系统默认动作
    处理函数名:自定义信号处理函数

	sa_mask:信号阻塞集
	sa_flags:用于指定信号处理的行为,它可以是一下值的“按位或”组合:
        SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)
        SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
        SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程
    				 如果退出也不会成为僵尸进程。
        SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
        SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
        SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

    
    

信号处理函数

信号处理函数:
void (*sa_sigaction)( int signum, siginfo_t *info, void *context );
参数说明:
    signum:信号的编号。
    info:记录信号发送进程信息的结构体。
    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程
    或线程的上下文

测试程序: 一个进程在发送信号,一个进程在接收信号的发送。

发送信号测试代码:

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
 
/*******************************************************
*功能:     发 SIGINT 信号及信号携带的值给指定的进程
*参数:		argv[1]:进程号
			argv[2]:待发送的值(默认为100)
*返回值:	0
********************************************************/
int main(int argc, char *argv[])
{
	if(argc >= 2)
	{
		pid_t pid,pid_self;
		union sigval tmp;
 
		pid = atoi(argv[1]); // 进程号
		if( argc >= 3 )
		{
			tmp.sival_int = atoi(argv[2]);
		}
		else
		{
			tmp.sival_int = 100;
		}
		
		// 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去
		sigqueue(pid, SIGINT, tmp);
		
		pid_self = getpid(); // 进程号
		printf("pid = %d, pid_self = %d\n", pid, pid_self);
		
	}
	
	return 0;
}

接收信号测试代码:

#include <signal.h>
#include <stdio.h>
 
// 信号处理回电函数
void signal_handler(int signum, siginfo_t *info, void *ptr)
{
	printf("signum = %d\n", signum); // 信号编号
	printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号
	printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息
}
 
int main(int argc, char *argv[])
{
	struct sigaction act, oact;
	
	act.sa_sigaction = signal_handler; //指定信号处理回调函数
	sigemptyset(&act.sa_mask); // 阻塞集为空
	act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler
	
	// 注册信号 SIGINT
	sigaction(SIGINT, &act, &oact);
	
	while(1)
	{
		printf("pid is %d\n", getpid()); // 进程号
		
		pause(); // 捕获信号,此函数会阻塞
	}
	
	return 0;
}

两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

在这里插入图片描述

04. 附录

4.1 参考博客: 【linux系统编程】进程间通信:信号中断处理

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值