Linux信号1

信号(signal)是一种软中断,他提供了一种处理异步事件的方法,也是进程间唯一的异步通信方式。在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某进程发生了什么事件,还可以给进程传递数据。

1.1信号的来源

(1)硬件方式

·用户在终端上按下某些键时,将产生信号。如死循环时经常按的Ctrl+c

·硬件异常产生信号:除数为0、无效的存储访问等。

(2)软件方式

·用户在终端下调用kill命令向进程发送任意信号。

·进程调用kill或sigqueue函数发送信号。

·当检测到某种软件条件已经具备时发出信号,如alarm或settimer设置的定时器超时时将生成SIGALRM信号。

1.2信号的种类

在Shell下输入 kill -l 可显示Linux系统支持的全部信号。

 信号的值在signal.h中定义

前30个信号的意义

https://blog.51cto.com/peacefulmind/1948591

从31号信号到第64信号是Linux的实时信号,他们没有固定的含义(可由用户自由使用)。Linux线程机制使用了前3个实时信号。所有实时信号的默认动作都是终止进程。

①可靠信号与不可靠信号

第1到第31号信号都是继承自UNIX系统,是不可靠信号。第33到第64号信号都是可靠信号,也称为实时信号

信号的可靠性是指信号是否会丢失,或者说该信号是否支持排队。当导致产生信号的事件发生时,内核就产生一个信号。信号产生以后,内核通常会在进程表中设置某种形式的标志。当内核设置了这个标志以后,我们就称内核向进程传递了一个信号。信号产生和信号传递之间的时间间隔,称之为信号未决

进程可以调用sigpending将信号设置为阻塞,若为进程产生了一个阻塞的信号,而对该信号的动作是捕捉该信号,则内核将为该进程的此信号保持为为决状态,直到该进程对此信号解除阻塞或者将此信号的响应更改为忽略。如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么如果信号被递送多次(即信号在为决信号队列里排队),则称之为可靠信号;只被递送一次的信号被称之为不可靠信号。

②信号的优先级

由于信号的实质是软中断,中断有优先级,那信号同样有优先级。如果一个进程有多个为决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。若存在多个未决的实时信号,则值越小的越先被递送。如果既存在不可靠信号,有存在可靠信号,则Linux系统优先递送不可靠信号。

1.3进程对信号的响应

当信号发生时,用户可以要求进程以下列3种方式之一对信号做出响应。

①捕捉信号。对于要捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对该信号的处理。

②忽略信号。大多数信号都可以使用这种方式进行处理,但是SIGKILL和SIGSTOP这俩信号不可以被忽略,这两个信号也不能被捕获或者阻塞。此外,如果忽略某些由硬件异常产生的信号,则进程的行为是不可预测的。

③按照系统默认方式处理。大部分信号的默认操作是终止进程,且所有的实时信号的默认动作都是终止进程

2.信号处理

 2.1信号的捕捉和处理

Linux系统中对信号的处理主要由signal和sigaction函数来完成。

①signal函数

signal函数用来设置进程在接收到信号时的动作

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

 signal会根据参数signum指定的信号编辑来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是常数SIG_IGN(忽略该信号)或SIG_DFI(对该信号执行默认操作)。handler是一个函数指针,它所指向的函数的类型时sighandler_t,即它所指向的函数有一个int型参数,且返回值的类型为void。

signal函数执行成功时返回以前的信号处理函数指针,当有错误发生时则返回SIG_ERR。

例程看一下signal怎么用

#include<stdio.h>
#include<signal.h>
void handler_sigint(int signo)
{
	printf("recv SIGINT\n");
}

int main()
{
	signal(SIGINT, handler_sigint);
	while(1);
	return 0;
}

 运行程序以后按 Ctrl+c

 程序先使用signal()安装信号SIGINT的处理函数handler_sigint,然后进入死循环。当程序接收到SIGINT信号以后(Ctrl+c就是在给程序发送SIGINT信号),程序自动跳转到信号处理函数处执行。

 ②sigaction函数

sigaction函数可用来检查或设置进程在接收到信号时的动作。

#include<signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 sigaction会根据参数signum指定的信号来设置该信号的处理函数。参数signum可以是SIGKILL和SIGSTOP以外的任何信号。如果参数act不是空指针,则为signum设置新的信号处理函数;如果oldact不是空指针,则旧的信号处理函数将被存储在oldact中。struct sigaction定义如下

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

 sa_handler和sa_sigaction在某些体系结构上被定义为共用体,即这两个值在某一时刻只有一个有效。

数据成员sa_restorer已经作废,不再使用。

sa_handler可以是常数SIG_DFL或SIG_IGN,或者是一个信号处理函数的函数名。信号处理函数只有一个参数即信号编号。该参数和参数sa_sigaction实际上都是函数指针。

sa_sigaction也是用来指定函数signum的处理函数,它有三个参数,第一个参数是信号编号;第二个参数是指向siginfo_t结构的指针;第三个参数是一个指向任何类型的指针,一般不使用。

sa_mask成员声明了一个信号集,在调用信号捕捉函数前,该信号集会增加到进程的信号屏蔽码中,新的信号屏蔽码会自动包括正在处理的函数信号。当从信号捕捉函数返回时,进程的信号屏蔽码会恢复为原来的值。因此,当处理一个给定的信号时,如果这个信号再次发生,那么他会被阻塞到本次信号处理结束为止。若这个信号发生了多次,则对于不可靠信号,他只会被阻塞一次,即本次信号处理结束以后只会再处理一次(相当于丢失了信号);对于可靠信号(实时信号),则会被阻塞多次,即信号不会丢失,信号发生了多少次就会调用信号处理函数多少次。

sa_flags成员用来说明信号处理的一些其他相关操作。他可以取以下值或他们的组合

 

 注:Linux下signal函数是由sigaction函数实现的

例程

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


int temp = 0;


void handler_sigint(int signo)
{
	printf("recv SIGINT\n");
	sleep(5);
	temp += 1;
	printf("the value of temp is: %d\n", temp);
	printf("in handler_sigint, after sleep\n");
}

int main()
{
	struct sigaction act;
	act.sa_handler = handler_sigint;
	act.sa_flags = SA_NOMASK;
	//act.sa_flags = NULL;
	sigaction(SIGINT, &act, NULL);
	while(1);
	return 0;
}

 程序定义了一个act结构,并设置了act的sa_handler微信号处理函数handler_sigint,并且将sa_flags赋值为SA_NOMASK,即支持信号的嵌套处理。

快速按下 Ctrl+c 组合键两次,执行结果如下(实际上我按了4次)

 程序启动后,接收SIGINT信号(我们按得Ctrl+c就是给程序发信号),程序打印出“recv SIGINT”表示信号处理函数处理了信号SIGINT,然后sleep(5)。在函数挂起期间,我们又按了三次 Ctrl+c ,由于我们设定了sa_flags的值为SA_NOMASK,因此程序又响应一次信号SIGINT,程序·从sleep()处嵌套调用信号处理函数handler_sigint,再一次打印出“recv SIGINT”。睡眠5秒后,将temp的值打印出来并返回到本次信号处理程序的跳入点sleep处,然后再打印出temp的值并返回到主函数。

从程序执行顺序可以看出来,temp的值随着被调用的次数而自增,而由于实际应用中信号总是随机发生的,这样temp的值也会随机变化。若main函数或其他地方还用到了这个全局变量,则程序将产生不可预料的结果。我们将这种函数称之为不可重入函数。编写信号处理函数不能使用不可重入函数。一般来说,满足一下条件的函数为不可重入函数:

(1)使用了静态的数据结构,如getgrgid(),全局变量等

(2)函数实现时,调用了malloc()函数或free()函数

(3)函数实现时,使用了标准I/O函数

将程序中的“act.sa_flags = SA_NOMASK”这行代码注释,将下一行注释取消,然后重新编译并执行,快速按3次及以上Ctrl+c,结果如下

 可以看到,sigaction按照默认方式阻塞当前正在处理的信号,但为什么结果只有两个呢?这是因为SIGINT是不可靠信号,不可靠信号不支持排队,从而有可能丢失信号,从程序可以看出,第三个信号确实被丢失了。

 ③pause

pause函数使调用进程挂起直至捕捉到一个信号。

#include<unistd.h>
int pause(void);

 pause函数会令目前的进程暂停(睡眠),直至被信号(signal)所中断。该函数只返回-1并将errno设置为EINTR.

猜你喜欢

转载自www.cnblogs.com/area-h-p/p/11642692.html