并发_信号(一)

一、信号

1.信号的概念

信号是软件层面的中断。
信号的响应依赖于中断;

补充:
中断牵涉两个概念:同步和异步;
异步事件的处理方式:查询法和通知法;
举例:江边钓鱼,鱼是否到来是异步事件。如果用抄网往水里抄,抄起来看有没有鱼,则为查询法;如果是用鱼竿钓鱼,观察鱼漂来看有没有鱼,则为通知法;
适用场景:
如果异步事件发生的比较稀疏,用通知法合适,比如监测宾馆火灾;
发生频率高,查询法合适;

查看终端所有信号:

kill -l

并发_信号(一)_第1张图片
编号34~64为实时信号,RT代表实时信号

2.signal

并发_信号(一)_第2张图片

void (*signal(int signum, void (*func)(int)))(int); 
作用1:站在应用程序的角度,注册一个信号处理函数
作用2:忽略信号,设置信号默认处理 信号的安装和回复

参数
–signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出
–handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
–handler也可以是下面两个特殊值:① SIG_IGN 屏蔽该信号 ② SIG_DFL 恢复默认行为
返回值
sighandler_t是自定义类型,使用返回值时前面需要加上
typedef void (*sighandler_t )(int);
正常返回,返回先前行为的值;
出错则返回SIG_ERR;

注意点1:
signal给信号规定的工作什么时候执行?
1.程序未结束
2.信号到来

注意点2
信号会打断阻塞的系统调用,让系统调用完成不了。
如果仍旧想在信号被打断的时候,继续完成系统调用,则需要把系统调用中信号打断出错的errno给屏蔽掉。
系统调用函数如read,write,open等;
在这里插入图片描述

#include
#include
#include

void func(int i)
{
	write(1, "!", 1);
}

void hook(void)
{
	write(1, "\n", 1);
}

int main()
{
	int i; 
	
	//signal(SIGINT, SIG_IGN);
	signal(SIGINT, func);
	
	for(i = 0; i < 10; i++)
	{
		write(1, "*", 1);
		sleep(1);
	}
	
	atexit(hook);
	
	exit(0);
}

在这里插入图片描述
不断按ctrl+c发送中断信号,程序执行不到10s就结束。

do
	{
		sfd = open(argv[1], O_RDONLY);
		if(sfd < 0)
		{
			if(errno != EINTR)
			{
				perror("open src_file");
				exit(1); 
			}
		}
	}while(sfd < 0);

想要继续进行的open操作把信号打断屏蔽掉。

3.信号的不可靠

行为不可靠。信号在处理行为的同时,过来一个相同的信号,因为执行现场是内核帮忙布置的,很有可能把这两次现场布置在同一位置,即第二次执行现场把第一次执行现场冲掉。
解决办法之一:可重入函数

4.可重入函数

概念:
第一次调用还没有结束,又进行第二次调用,并且不会出错。

所有的系统调用都是可重入的,一部分库函数也是可重入的,如:mencpy
在这里插入图片描述
比如localtime返回值为struct tm*,指向静态空间,不指向堆。
localtime_r是localtime的可重入版本;
如果函数有_r版本,则此函数非_r版本一定不能用到信号处理当中。

标准IO就不可以用在信号处理函数中,因为不是可重入函数,共用缓冲区;

5.信号的响应过程

内核为每个进程维护至少两个位图,一个位图是mask(信号屏蔽字),另一个是pending。
理论上讲,mask和pending都是32位。
mask:用来表示当前信号的状态
pending:当前进程收到哪些信号

信号从收到到响应有一个不可避免的延迟,因为需要有中断,中断后才能执行mask&pending,观察有没有信号。
单个信号响应过程:
刚开始mask位图全为1,pending位图全为0,如果有信号,会将pending对应位置为1,等待中断发生后,会带当前现场扎入内核中。进入内核的调度队列,排队等待调度。调度后,当从kernel态即将回到usr态的时候,进行mask&pending,检测到信号。然后进行响应信号,首先把mask和pending都置为0,在将内核中压入的地址替换为信号处理函数的地址,函数执行完毕后(此处非原子操作),会回到内核。把之前地址替换回来,将mask置为1,在做一次mask&pending,发现没有信号后,在回到现场;

static void daemon_exit(int s)
{
	fclose(fp);
	closelog();
}

/*
	signal(SIGINT, daemon_exit);
	signal(SIGQUIT, daemon_exit);
	signal(SIGTERM, daemon_exit);
*/

接收SIFINT信号后,执行daemon_exit,执行到fclose(fp)时,被中断打断,再次扎内核。调度后返回时,进行mask&pending检测到了SIGQUIT信号,这时候类似于嵌套或者递归调用,会先去到QUIT入口地址执行,仍旧执行daemon_exit,fclose(fp)会被执行两次。一块空间被free多次,会产生内存泄漏。

解决办法:
当多个信号共用同一个处理函数的时候,希望在响应某一个信号期间,把其他信号阻塞住。

signal函数有这种问题,可以用sigaction

信号可以理解为从kernel态即将回到usr态响应的。

思考:
1.如何忽略掉一个信号的?
SIG_ING,将信号对应的mask位图置为0,使kernel永远看不到信号;
标准信号为什么要丢失。
标准信号的响应没有严格的顺序。

目前仅限于从进程角度来分析信号的响应过程,线程角度分析放在后面。
mask屏蔽字的值一般情况下全为1,pending初始值为0;

每次中断(硬件中断或者内核时间片耗尽)会把当前现场进行压栈存放,然后此任务会进入内核的调度队列,排队等待调度。调度后,重新被调用,恢复刚才压栈的现场。执行过程中,如果再被打断,重复上述过程。
并发_信号(一)_第3张图片
2.标准信号为什么要丢失
3.标准信号的响应没有严格的顺序
4.不能从信号处理函数中随意的往外跳
,否则mask置1操作可能不执行,监测不到信号。(setjmp, longjmp)
使用sigsetjmp, siglongjmp

你可能感兴趣的:(linux系统编程,c++,开发语言,c语言)