Linux——信号集操作函数、signal实现信号捕捉、sigaction实现信号捕捉、内核实现信号捕捉简析、⭐借助信号捕捉回收子进程⭐

一、信号集操作函数

在这里插入图片描述只能通过阻塞信号集mask才能去改变未决信号集,而阻塞信号又需要通过自定义一个信号集去改变,通过以下函数就可以创建一个信号集set
sigset_t set;//自定义信号集
int sigemptyset(sigset_t *set);//清空信号集
int sigfillset(sigset_t *set);//全部置1
int sigaddset(sigset_t *set, int signum);//将一个信号添加到集合中
int sigdelset(sigset_t *set,int signum);//将一个信号从集合中移除
int sigismember(const sigset_t *set, int signum);//查看某一个信号是否在集合中,在返回1,不在返回0

再用以下函数将set与mask进行操作:
一、sigprocmask函数,用来屏蔽信号、解除屏蔽,其本质是读取或修改进程的信号屏蔽字(PCB中)
【注意:】屏蔽信号,只是将信号处理延后执行(延至解除屏蔽),而忽略则表示将信号丢处理
int sigprocmask(int how, const sigset_t *set, sigset_t *oldest);成功返回0,失败返回-1,设置errno
set参数:是一个位图,set中哪个位置1,就表示当前进程屏蔽哪个信号
oldest参数:传出参数,保存旧的信号屏蔽集
how参数取值:
1,SIG_BLOCK:set表示需要屏蔽的信号,mask = mask|set
2,SIG_UNBLOCK:set表示需要解除屏蔽的信号,mask = mask&~set
3,SIG_SETMASK:set用于替代原始屏蔽集的新屏蔽集,mask = set

信号是否被处理,是反应在未决信号集上的,所以我们还得需要函数去读未决信号

二、sigpending函数,读取当前进程未决信号集
int sigpending(sigset_t *set); set传出参数,返回值成功为0,失败为-1设置errno

三、信号集操作函数练习👇

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

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}
void print_set(sigset_t *set)
{
	int i;
	for(i = 1; i<32; i++)
	{
		if(sigmember(set,i))
			putchar("1");
		else
			putchar("0");
	}
	printf("\n");
}
int main(int argc, char *argv[])
{
	sigset_t set, oldest,pedset;
	int ret = 0;
	sigemptyset(&set);//设立set
	sigaddset(&set,SIGINT);//加一个阻塞信号,2号信号
	ret = sigprocmask(SIG_BLOCK,&set, &oldset);//set作用到mask上
	if(ret == -1)
		sys_err("sigprocmask error");
	//查看未决信号集
	ret = sigpending(&pedset);
	if(ret == -1)
		sys_err("sigpending error");
	
	print_set(&pedset);
	
	return 0;
}

如果ctrl+c就会2号阻塞的。如果要增加其他的比如ctrl+/之类的只要sigaddset(&set,对应信号名)即可。

二、signal实现捕捉函数

signal函数:注册一个信号捕捉函数
typedef void (*sighandler_t )(int);//函数指针类型
sighandler_t signal(int signum, sighandler_t handler);//捕捉到signum函数,让这个信号去做handler指的函数

signal数实现捕捉函数实例👇

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>

void sys_err(const char*str)
{
	perror(str);
	exit(1);
}
void sig_catch(int signo)
{
	printf("catch you! %d\n",signo);
	return;
}

int main(int argc, char *argv[])
{
	signal(SIGINT, sig_catch);.//如果产生INIT信号,就会去做sig_catch函数
	//
	//
	//
	//
	return 0;
}

三、sigaction实现信号捕捉(一般用sigaction

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);,成功返回0,失败返回-1;第一个参数是类型,第二个参数是动作,一个结构体类型

struct sigaction
{
	void (*sa_handler)(int);//一旦捕捉到信号要做的函数
	void (*sa_sigaction)(int, siginfo_t*, void*);//一般不用
	sigset sa_mask;
	int sa_flags;
	void (*sa_restorer)(void);//废弃
};

例子👇

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

void sys_err(const char*str)
{
	perror(str);
	exit(1);
}
void sig_catch(int signo)//捕捉函数/回调函数---内核是调动者
{
	if(signo == SIGINT)
		printf("catch you %d\n", signo);
	else if(signo == SIGQUIT)
		printf("------catch you %d\n",signo);//一个回调函数两个捕捉
	return ;
}

//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int main(int argc, char *argv[])
{
	struct sigaction act, oldact;//构造了这个结构体类型,结构体内的就都有了
	act.sa_handler = sig_catch;//设置回调参数
	sigemptyset(&(act.sa_mask));//清空sa_mask屏蔽字,只在sig_catch工作时有效
	act.sa_flags = 0;//默认处理动作
	int ret = sigaction(SIGINT, &act, &oldact);//注册信号捕捉函数
	if(ret == -1)
		sys_err("sigaction error");
	ret = sigaction(SIGAUIT, &act, &oldact);//注册第二个信号捕捉函数
	return 0;
}

所以上述代码的ctrl+c和ctrl+/都不能结束程序,要是ctrl+c或ctrl+/都会被捕捉,只能kill才能结束

四、信号捕捉特性

1. 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用回调函数,而该函数又可能执行很长时间。在这期间所屏蔽的信号不由☆来指定。而是sa_mask来指定。调用完信号处理函数(即回调函数),再恢复为☆
2. XXX信号捕捉函数执行期间,XXX信号自动被屏蔽
3. 阻塞的常规信号不支持排队,产生多次只记录一次(后32各实时信号支持排队)

五、内核实现信号捕捉过程:用户态和内核态的切换

在这里插入图片描述
注意第四步,信号处理函数返回时执行特殊的系统调用sigreturn再次进内核

六、⭐熟练掌握使用信号完成子进程的回收

1. SIGCHILD信号
  • SIGCHILD的产生条件:
    子进程终止时;
    子进程接收到SIGSTOP信号停止时;
    子进程处在停止态,接受到SIGCONT后唤醒时
    就是发生变化就会产生这个信号
2.借助SIGCHLD信号回收子进程

背景:在有些地方是没有办法设置回收的,比如exec函数,exec函数成功运行就不会再返回,就只能你通过系统的隐式回收来达到回收目的。但通过信号可以实现:给信号SIGCHLD设置捕捉,一旦子进程状态发生变化,内核就回调函数,还是该干啥干啥,就可以进行回收了
——即:
子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认动作是忽略,可以捕捉到该信号,在捕捉函数中完成子进程状态的回收

例子👇

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include<sys/wait.h>

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}
//创建捕捉函数
void catch_child(int signo)
{
	pid_t wpid;
	wpid = wait(NULL);
	if(wpid == -1)
		sys_err("wait error");
	printf("catch child id %d\n", wpid);
	return;
}

int main(int argc, char *argv[])
{
	//⭐循环创建多个子进程⭐
	pid_t pid;
	int i;
	for(i = 0; i<5; ++i)
	{
		if((pid = fork()) == 0)
			break;
	}
	if(i == 5)
	{
		struct sigaction act;
		act.st_handler = catch_child;
		sigemptyset(&act.sa_mask);
		act.sa_flags = 0;
		sigaction(SIGCHLD, &act, NULL);
		printf("I am parent = %d\n", getpid());
	}else
	{
		printf("I am child = %d\n", getpid());
	}
	return 0;
}

但是上述代码会出现多个进程同时死亡,即同时发送信号,而信号是不排队的,再阻塞后只能处理一个信号即回收一个子进程,这样就会出现僵尸现象。(僵尸:复习就是子进程死了父进程没给回收)
解决方法:
在回调函数中,用循环实现一次回调多次回收

void catch_child(in signo)
{
	pid_t wpid;
	while(wpid = wait(NULL) != -1)
	{
		printf("-------catch child id = %d\n",wpid);
	}
	return;
}

循环停止条件是wait(NULL)!=-1,处理一个进程之后,会继续去调用wait函数,如果还有进程死亡,就继续回收

七、 慢速系统调用中断⭐⭐,先有个概念就行

系统调用可以分为两类:慢速系统调用和其他系统调用

  1. 慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行,也可以设定系统调用是否重启,如read、write、pause、wait等
  2. 其他系统调用:getpid、getppid、fork等

用sa_flags参数设置来选择中断后是否重启,sa_interrupt不重启(默认)、sa_restart重启

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值