进程间信号的相关概念

一.概念

信号是什么?
信号是进程事件通知机制。提供了异步处理事件的方法。
在linux下输入指令kill -l,可以查看所有信号

在这里插入图片描述

二.产生信号的方式

  1. 通过终端按键,例如ctrl c,产生的就是2号信号

  2. 通过系统调用函数产生信号,例如kill,raise,abort

 int kill(pid_t pid, int sig); 给对应进程发送对应信号,成功返回0,失败返回-1
 kill命令就是调用kill函数实现的
 int raise(int sig);给当前进程发送对应信号,成功返回0,失败返回-1
void abort(void);给当前进程发送6号信号,一定会成功,无返回值
  1. 由软件条件产生信号,比如alarm
unsigned int alarm(unsigned int seconds);相当于定一个闹钟,在xx秒后给当前进程发送14号信号
该函数的返回值是以前闹钟还余留下的秒数。
意思是如果在调用alarm之前,进程已经有闹钟了,则该函数返回的是上一个闹钟剩下的时间,如果返回0,则表示上一个闹钟取消,出错返回-1
  1. 硬件异常产生信号,比如除0操作,非法访问,野指针等

三.信号的处理

信号有三种处理方式,但是真正在处理的时候只能选择一种。

处理方式:
1.默认(大部分进程默认都是直接终止进程),比如ctrl+c ,默认终止进程,不过ctrl+c只能终止前台进程,如果在./a.out 后加上&,那么这个进程将会在后台运行,ctrl+c是终止不掉的

2.自定义(捕捉)handler

3.忽略(不理这个信号)

重点讲一下自定义(捕捉)
自定义是用户自己定义的处理方法,当信号递达的时候,执行用户的处理方法。

设置信号处理的函数

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);

signum:信号编号

handler:是处理信号的方式,有三种选择。本质是一个函数指针
SIG_DFL:默认
SIG_IGN:忽略
自定义handler方法 typedef void (*sighandler_t)(int) 自定义函数无返回值,参数是信号编号

一个例子,对2号信号做自定义处理
代码:

#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
	printf("handler success!,sig:%d\n",sig);
}
int main()
{
	while(1)
	{
		signal(2,handler);
	}
	return 0;
}

结果可以看到,ctrl+c产生的2号信号被捕捉了,那么此时ctrl+c不能终止进程了
在这里插入图片描述

四.发送信号的本质

从产生信号可以看到,不光系统调用可以产生信号,就连键盘输入,硬件中断都会产生信号,那么这些信号都是怎么产生的呢?

原因:在创建一个进程时,会创建PCB,PCB中有一个信号位图,可以是一个unsigned int类型,收到一个信号,OS会找到这个信号的编号,然后在位图上将该信号对应的比特位从0置1。比如说接收了一个2号信号,那么OS就会将PCB中的位图的第二个比特位从0置1,表示收到2号信号。然后再进行相对应的处理

五.信号在内核中的表示

1.block,pending,handler

信号递答:执行信号的处理动作,就是进行处理信号叫做信号递答
信号未决:信号从产生到递答的过程,就是信号收到但还没有处理叫做信号未决
信号阻塞:将一个信号阻塞(屏蔽)掉

注:
阻塞不等于忽略,只要出现信号阻塞就不会递答;忽略是对信号递答的一种处理动作。
信号没有受到阻塞可以未决,信号没有未决也可以阻塞信号,如果一个信号被阻塞了,那么如果发送这个信号就一直未决,永远不会递答,除非取消阻塞

在这里插入图片描述

在PCB中,信号有2个位图,分别是pending位图,表示信号是否未决,block位图,表示信号是否阻塞,和一个handler函数指针数组,表示对信号的处理动作


pending位图:表示信号是否未决。当发送一个信号时,当这个信号没有被阻塞时,就会找到该信号的编号,并在pending位图中将该信号的编号所对应的比特位从0置1,pending位图中,每一个比特位表示信号编号,比特位的内容表示该信号有没有被未决,0表示未被未决,1表示未决;发送信号的本质就是修改pending位图中的比特位内容


block位图:表示信号是否阻塞。每一个比特位表示信号的编号,比特位的内容表示该信号有没有被阻塞,0表示未被阻塞,1表示阻塞。


handler函数指针数组:表示数组的处理方式。都有默认值,如果不自定义(捕捉),就是执行默认方法,如果对某个信号实行了捕捉,那么这个信号就会执行用户自定义方法。handler数组的下标即为信号编号。


pending位图,block位图,handler数组都是一一对应的。

综上所述:当产生一个信号,OS会在PCB中找到block位图,查找该信号所在的比特位是否被阻塞,当没有被阻塞时,会将pending位图中信号所在的比特位从0置1.然后在handler数组里找到信号所对应的下标,再通过函数指针找到对应函数,执行相对应的处理方法,执行完成后,再在pending位图中信号的比特位恢复为0

2.信号集(sigset_t)

每个信号只有一个bit的未决标志,非0即1,阻塞也一样。所以未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。

3.信号集操作函数

接下来的函数头文件全是#include <signal.h>

操作阻塞信号集和未决信号集

int sigemptyset(sigset_t*set); 将信号集的所有比特位清0
int sigfillset(sigset_t*set); 将信号集的所有比特位置1
int sigaddset(sigset_t*set,int signo); 将signo信号的比特位置1
int sigdelset(sigset_t*set,int signo); 将signo信号的比特位清0
前四个成功返回0,失败返回-1
int sigismember(const sigset_t*set,int signo); 判断signo信号是否在信号集set中
如果signo在信号集内返回1,不在返回0,失败返回-1.

操作阻塞信号集

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);改变或查看阻塞信号集。

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信
号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后
根据set和how参数更改信号屏蔽字。

how的取值:
SIG_BLOCK:将set中屏蔽字的信号添加到现在的阻塞信号中
SIG_UNBLOCK:从当前信号屏蔽字中解除阻塞的信号
SIG_SETMASK:设置屏蔽字为set所指向的值

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递
达

未决信号集

int sigpending(sigset_t *set);获取当前未决信号集,由set传出
成功返回0,失败返回-1

我们来做一个实验,我们将2号信号阻塞,然后发送2号信号,观察pending位图的变化,然后将2号信号取消阻塞,再捕捉2号信号,会出现什么效果?

代码:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void printpendingmap(sigset_t* p)
{
	int i =0;
	for(i = 1;i<=31;i++)
	{
		if(sigismember(p,i))
			printf("1");
		else
			printf("0");
	}
	printf("\n");
}
void handler(int sig)
{
	printf("handler success.sig:\n",sig);
}
int main()
{
	sigset_t block,oblock;
	sigemptyset(&block);
	sigaddset(&block,2);
	signal(2,handler);
	sigprocmask(SIG_BLOCK,&block,&oblock);
	int count = 0;
	while(1)
	{
		sigset_t pend;
		sigpending(&pend);
		printpendingmap(&pend);
		count++;
		sleep(1);
		if(count==5)
		{
			sigprocmask(SIG_SETMASK,&oblock,NULL);
			printf("blockmap is empty!\n");
			break;
		}
	}
}

效果
在这里插入图片描述

六.信号的捕捉

信号在产生时不会立即被处理,而是在合适的时候进行处理,那么什么是合适的时候的呢?

回答:在内核态切换到用户态进行处理

用户态以及内核态

在虚拟地址空间中,一共有4G,前3G是用户的内核地址空间,后1G是内核地址空间,虚拟地址空间通过页表映射到真实的物理内存上,而用户地址空间使用的页表是用户级页表的,每个进程都会保存属于自己的用户级页表,而内核地址空间使用的是内核级页表,所有进程共享内核级页表
在这里插入图片描述
那么如何实现信号捕捉呢?

答:当硬件中断或者有系统调用时,需要从用户态切换到内核态,进行信号的递答,如果信号的处理动作是自定义(捕捉),则需要从内核态再返回到用户态执行用户的自定义函数,执行完成后再通过系统调用再一次返回内核,如果这时候没有信号要递答了,就会从内核态返回到用户态,从上次中断的位置继续执行用户代码。整个动作涉及四次状态的转变。
在这里插入图片描述
上图不好记,通过下图可以可以帮助我们很轻松的记忆。
在这里插入图片描述
信号捕捉函数:
1.signal,上面已经讲过
2.sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum:信号编号
act:根据act进行信号处理动作
oldact:将原本的信号的处理工作传入里面,不关心可设为NULL
act和oldact都指向struct sigaction结构体
成功返回0,失败返回-1.

参考:
sigprocmask函数的使用
信号在内核中的表示
信号的捕捉

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值