Linux系统编程与网络编程——信号介绍,未决信号集,信号屏蔽字,捕捉信号(十)

什么是信号机制

信号(signal)机制是Linux系统中最为古老的进程之间的通信机制。Linux信号也可以称为软中断,是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达, 信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程发生了什么。

信号的发生有两个来源:
(1)硬件来源,比如我们按下了键盘或者其他硬件信号触发,硬件异常如除以零运算,内存非法访问。
(2)软件来源,最常见发送信号的系统函数 kill() raise() alarm() 和 setitimer() 等函数以及ctl+c发出SIGINT、ctl+z SIGTSTP、ctl+\ SIGQUIT。

Linux系统中定义了一系列的信号,可以使用 kill -l 命令列出所有的信号。
Linux的信号机制是从Unix继承下来的,早期Unix系统只定义了32种信号,现在Linux支持64种信号。
在这里插入图片描述
在这里插入图片描述


信号集

信号集顾名思义就是信号的集合,信号集的类型为sigset_t,每一种信号用1bit来表示,前面我们提到信号有64种,那么这个sigset_t类型至少占64bit,可以通过sizeof(sigset_t)来查看。

每个进程的PCB进程控制块中都有两个信号集一个叫作未决信号集,一个叫作信号屏蔽字,信号集的每一位不是0就是1,初始状态下,两个信号集的值都为0。

当有信号传递到该进程的时候,未决信号集的对应位设置为1,其他位不变,这个时候信号只是传递到进程,并未被处理,叫作未决状态。常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。用户只能获取未决信号集值,无法改变其值。

未决信号想要递达程序的信号处理函数(默认、忽略、自定义),还要经过信号屏蔽字的过滤,一旦该信号对应bit为1,则该信号将阻塞,不能传递到信号的处理函数。用户可以设置获取信号屏蔽字的值。

信号集处理函数
在这里插入图片描述
sigprocmask,调用函数sigprocmask可以读取或更改进程的信号屏蔽字。
在这里插入图片描述
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前, 至少将其中一个信号递达。

读取当前进程的未决信号集,sigpending:
在这里插入图片描述
sigpending获取的是未决信号集,不能获取信号关键字

综合示例

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

void printsigset(const sigset_t *set)
{
	int i;
	for (i = 1; i < 32; i++)
	if (sigismember(set, i) == 1)
		putchar('1');
	else
		putchar('0');
	puts("");
}
int main(void)
{
	sigset_t s, p;
	sigemptyset(&s);
	sigaddset(&s, SIGINT); //将信号集s中的SIGINT置为1
	sigprocmask(SIG_BLOCK, &s, NULL); //设置信号屏蔽字
	
	while (1)
	{
		sigpending(&p); //获取未决信号集
		printsigset(&p); //打印未决信号集
		sleep(1);
	}
	return 0;
}

信号传递过程

在这里插入图片描述

sigaction函数

#include <signal.h>

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

struct sigaction
{
	void (*sa_handler)(int);
	void (*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t sa_mask;
	int sa_flags;
	void (*sa_restorer)(void);
}

在这里插入图片描述
sigaction例子

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sig_handle(int sig)
{
	puts("recv SIGINT");
	sleep(5);
	puts("end");
}

int main(int argc, char** argv)
{
	struct sigaction act;
	act.sa_handler = sig_handle;
	act.sa_flags = 0;
	
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGQUIT); //当进入信号处理函数的时候,屏蔽掉SIGQUIT的递达
	sigaction(SIGINT, &act, NULL);
	
	while(1)
		sleep(1);
	return 0;
}

在这里插入图片描述


进程对信号的响应和处理

进程可以通过三种方式来响应和处理一个信号:
(1)忽略信号:即对信号不做任何处理,但是两个信号不能忽略: SIGKILL 以及 SIGSTOP .
(2)捕捉信号:当信号发生时,执行用户定义的信号处理函数。
(3)执行默认操作: Linux对每种信号都规定了默认操作,man 7 signal查看。
在这里插入图片描述
进程对收到的信号有对应的缺省操作,如果进程要自定义处理某个信号,那么就要在进程中安装该信号。
信号之间不存在相对的优先权。信号在产生时也并不马上送给进程,信号必须等待直到进程再一次被调度运行。


read函数的EINTR错误

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

void sig_handle(int sig)
{
	printf("SIGINT\n");
}

i
nt main(int argc, char** argv)
{
	char buf[10];
	struct sigaction act;
	act.sa_handler = sig_handle;
	act.sa_flags = 0;
	//act.sa_flags = SA_RESTART; //先试一试sa_flags为0时,然后再试一试SA_RESTART的情况
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT, &act, NULL);
	puts("read stdio:");
	int ret = read(STDIN_FILENO, buf, 9);
	if(ret == -1)
	{
		if(errno == EINTR)
		perror("read:");
	}
	else	
	{
		buf[ret] = '\0';
		printf("read %d : %s", ret, buf);
	}
	return 0;
}

以上的运行结果的是,act.sa_flags = 0时一旦发送了一个信号,read将被打断,返回-1,并且设置errno为
EINTR。如果想打断后继续运行read,可以设置一下act.sa_flags = SA_RESTART。或者也可以使用signal函数来处理信号signal相当于默认设置了SA_RESTART标志


可重入函数

不含全局变量和静态变量是可重入函数的一个要素, 可重入函数见man 7 signal, 在信号捕捉函数里应使用可重入函数,在信号捕捉函数里禁止调用不可重入函数

例如:strtok就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱,应用strtok_r版本,r表示可重入。

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

static char buf[] = "hello world good book";

void sig_handle(int sig)
{
	strtok(NULL, " ");
} 

int main(int argc, char** argv)
{
	signal(SIGINT, sig_handle);
	printf("%s\n", strtok(buf, " "));
	printf("%s\n", strtok(NULL, " "));
	sleep(5); //可以被信号打断,返回剩余的时间,想想看这个函数应该怎么调用
	printf("%s\n", strtok(NULL, " "));
	return 0;
}

运行的时候,发现通过Ctrl+c发射信号与没发射信号的结果不一样,可以改用strtok_r函数。

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/88997708