linux信号机制全解析

在谈linux信号处理函数前,有必要先聊下linux信号的机制。包含信号的作用、信号的产生、信号的阻塞等。

信号机制

信号是linux进程通信的一种方式,很多情况下,信号是由一个错误产生的,通知进程修改行为,但是,也由很大一部分场景是由人为产生信号,通知进程执行某些动作。

信号的产生

信号的产生主要有以下几种情况:

  1. 用户在终端(比如:键盘)按下某些按键,终端(键盘)驱动程序会发送信号给前台进程。比如:ctrl+c产生SIGINT(2)信号;ctrl+\产生SIGQUIT(3)信号;ctrl+z产生SIGTSTP(20)信号。
  2. 硬件异常产生信号,硬件检测到异常就通知内核,然后内核进程向当前用户进程发送适当的信号。比如:用户进程进行了除以0的操作,CPU运算单元会产生异常,内核将这个异常解释为SIGFPE(8)信号发送给用户进程。比如:当前用户进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGEGV(11)信号发送个用户进程。
  3. 用户调用某些函数(例如kill函数),就可以发送信号到另一个进程。比如:终端下,输入kill命令发送信号给某个进程(kill命令内部也是调用kill函数实现的),如果kill命令没有明确指定信号,默认发送的是SIGTERM(15)信号,该信号的默认处理是终止进程。
  4. 当内核检测到某种软件条件发生时,也会通过信号通知用户进程。比如:闹钟超时信号SIGALRM(14),向读端已经关闭的管道写数据时会产生SIGPIPE(13)信号。

信号的阻塞

实际执行信号的处理动作称为信号抵达,信号从产生到抵达之间的状态,称为信号未决。进程可以选择阻塞某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行抵达的动作。阻塞和忽略是不相同的,只要信号被阻塞就不会抵达,而忽略是在抵达之后可选的一种处理动作。
举个例子:
在这里插入图片描述
上图中,

  • SIGHUP(1)信号未产生过(pending=0)也未阻塞(block=0),当它抵达时执行默认处理动作。
  • SIGINT(2)信号产生过(pending=1),但正在被阻塞(block=1),所以暂时不能抵达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT(3)信号未产生过(pending=0),一旦产生SIGQUIT信号将被阻塞(block=1),它的处理动作是用户自定义函数sighandler。

linux信号相关数据结构

相关数据结构可以直接到linux上grep -R查找,一般是在<signal.h>头文件中。

struct sigaction

struct sigaction定义如下,链接

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

struct sigset_t

struct sigset_t就是信号集合,看代码就知道是一个信号数组。定义如下:

# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
	unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
...
typedef __sigset_t sigset_t;

linux信号相关处理函数

sigemptyset

sigemptyset
初始化set所指向的信号集,使其中所有信号的对应的bit清零,表示该信号集不包含任何有效信号。
在这里插入图片描述

sigfillset

sigfillset
初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
在这里插入图片描述

sigaddset

sigaddset
在该信号集中添加某种有效信号。
在这里插入图片描述

sigdelset

sigdelset
在该信号集中删除某种有效信号。
在这里插入图片描述

sigismember

sigismember
是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含贼返回1,不包含则返回0,出错返回-1。
在这里插入图片描述

sigprocmask

sigprocmask
读取或更改进程的信号掩码,如果成功返回0,失败返回-1。
信号掩码是进程当前被阻塞的信号的集合,即阻塞信号集合。
SIG_BLOCK,就是进程当前已有的阻塞信号集和set参数指定的信号集的并集。
SIG_UNBLOCK,就是进程当前已有的阻塞信号集和set参数指定的信号集的差集。
SIG_SETMASK,就是直接用set参数指定的信号集来设置进程的阻塞信号集。
在这里插入图片描述
在这里插入图片描述

sigpending

sigpending
读取当前进程的未决信号集,通过set参数传出,调用成功则返回0,出错则返回-1。
在这里插入图片描述

举例

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
 
void printsigset(sigset_t *set)
{
	int i;
	for(i=1;i<=31;i++){
		if(sigismember(set,i))
			putchar('Y');
		else
			putchar('N');
	}
	puts("");
}
 
int main()
{
	sigset_t s,p;			//定义两个信号集,s和p
	sigemptyset(&s);		//初始化清空信号集s
	sigemptyset(&p);		//初始化清空信号集p
	sigaddset(&s,SIGINT);	//往信号集s中添加信号SIGINT
	sigprocmask(SIG_BLOCK,&s,NULL);	//设置阻塞信号集,把SIGINT加入阻塞信号集
	while(1)
	{
		sigpending(&p);		//获取未决信号集,通过参数p返回
		printsigset(&p);
		sleep(1);
	}
	return 0;
}

运行结果:
在这里插入图片描述
这个程序的大概意思就是 我们阻塞一个信号集(在本程序里,这个信号集s只含有一个信号SIGINT),让它一直处于未决状态,然后在while循环过程中,我们手动ctrl+c(即发送SIGINT信号给进程),后面我们用sigpending获取进程的未决信号集(即参数p),它里面就会出现这个信号,然后他还是一直处于未决状态。

sigsuspend

临时替换掉进程阻塞信号集(信号掩码),挂起进程,等待,直到一个是调用handler 或者 终止进程的信号抵达。
信号抵达后,该函数就返回,此时进程阻塞信号集又还原成了sigsuspend执行之前的阻塞信号集。
在这里插入图片描述

举例

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

void handler_int(int sig)
{
	printf("recive sig INT..\n");
}

void handler_term(int sig)
{
	printf("recive sig TERM..\n");
}



int main()
{
    printf("pid=%d\n", getpid());
    signal(SIGINT, handler_int);
    signal(SIGTERM, handler_term);
    
    sigset_t           set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGTERM);

    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) 
    {
        printf("sigprocmask() failed\n");
    }

    printf("start sleep 3s..\n");
    sleep(3);
    printf("end sleep 3s..\n");

    sigemptyset(&set);

    while(1)
    {
        printf("while..\n");
        sigsuspend(&set);
        sleep(3);
    }

    return 0;
}

// gcc -g test_signal.c -o test_signal

信号抵达的处理函数

linux信号抵达处理函数有两个,一个是signal,一个是sigaction。
signal各个UNIX系统版本实现不同,不符合POSIX标准,即:移植性不好。sigaction符合POSIX标准,移植性好。并且sigaction功能比signal更全更强大。
在这里插入图片描述

signal

signal
signal函数就是给信号注册信号抵达时候的动作。
signal的不可移植性主要体现在:
当正在执行signal handler函数时,再来相同的信号,是否阻塞,已经如何处理的。
比如:

  1. 在最原始的UNIX系统上,当正在执行signal handler函数时,该信号的handler会被reset成SIG_DFL默认处理方式,并且不会阻塞后续的相同信号。相当于sigaction的sa.sa_flags = SA_RESETHAND | SA_NODEFER;
  2. 在System V标准中,当正在执行signal handler函数时,如果又来了相同信号,也是不阻塞,但是没有reset成SIG_DFL,就会递归调用signal handler。
  3. 在BSD标准中,当正在执行signal handler函数时,如果又来了相同信号,会阻塞,没有reset成SIG_DFL,当本次handler执行完毕后,被handler打断的一些系统调用会自动重启。相当于sigaction的sa.sa_flags = SA_RESTART;

在这里插入图片描述
注意:

  1. 在执行的signal handler函数会堵塞相同的信号,但是没有办法阻塞其它其他的信号。比如:正在处理SIG_INT,再来一个SIG_INT则会堵塞,但是来SIG_QUIT则会被其中断,如果SIG_QUIT有处理,则需要等待SIG_QUIT处理完了,SIG_INT才会接着刚才处理。下面的代码例子可以验证这一点。
  2. 在执行的singal handler函数,如果这时候又来了多个相同信号,那么这些相同信号会合并成一个,当handler函数执行完毕后抵达。下面的代码例子可以验证这一点。
    在这里插入图片描述
    在这里插入图片描述
举例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler_int(int sig)
{
	printf("handler_int start, handler = %d\n", sig);
	sleep(5);
	printf("handler_int end, handler = %d\n", sig);
}

void handler_quit(int sig)
{
	printf("handler_quit start, handler = %d\n", sig);
	sleep(10);
	printf("handler_quit end, handler = %d\n", sig);
}

int main()
{
	signal(SIGINT, handler_int);	//注册信号处理函数
	signal(SIGQUIT, handler_quit);

	while(1)
	{
		sleep(1);
	}
}

执行结果:
在这里插入图片描述
在这里插入图片描述

sigaction

sigaction
sigaction的内容很长,直接点上面链接查看吧,就不贴出来了。
先再贴下函数原型和struct sigaction数据结构,如下:
在这里插入图片描述
在这里插入图片描述
挑重点说:

  1. sa_handler就是指定信号抵达后的处理函数。
  2. sa_mask指定当正在执行sa_handler时,要被阻塞的信号集。
  3. sa_flags,当sa_flags是默认的(即0),那么在执行sa_handler时,相同信号再来时也会被阻塞,如果设置为SA_NODEFER,则不会。
举例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handler_int(int sig)
{
	printf("handler_int start, handler = %d\n", sig);
	sleep(5);
	printf("handler_int end, handler = %d\n", sig);
}

int main()
{
	struct sigaction act;
	act.sa_handler = handler_int;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGQUIT);	
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);

	while(1)
	{
		sleep(1);
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述

signal和sigaction对比总结

sigaction比signal功能强大处就在于,可以明确地设置:当正在处理某个信号的handler时,可以阻塞其他信号。不至于循环嵌套引起语义不明确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值