史上最详细的信号使用说明(已被收藏和转载N次)
发布日期:2021-06-23 04:43:38 浏览次数:6 分类:技术文章

本文共 21823 字,大约阅读时间需要 72 分钟。

Unix环境高级编程(第三版)

第10章 信号

文章目录

1. 引言

​ 信号是一种软中断。很多比较重要的应用程序都需要处理信号。信号提供了一种异步处理事件的方法,例如:终端用户输入中断键,会通过信号机制终止一个程序等。早期的信号存在丢失的风险,且执行在临界代码区时无法关闭所选择的信号,后来一些系统便增加了可靠信号机制。下面的章节提供详细的说明。

2. 信号的概念

​ 首先,每一个信号都有一个名字。这些名字都是以"SIG"开头的。Linux支持31种基本信号,不同的操作系统可能支持的信号数量略有不同。信号是在头文件<signal.h>中定义的,且每一种信号都被定义为整形常量(信号编号)。

toney@ubantu:~$ kill -l 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR111) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+338) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+843) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+1348) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-1253) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-758) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-263) SIGRTMAX-1	64) SIGRTMAX

​ 不存在编号为0的信号。在后面的章节中会说明编号为0的信号的特殊用途。

​ 产生信号的条件有很多:

  • 当用户按某些终端键时会产生信号。例如使用‘Delete’键会产生SIGINT信号(有些系统中组合键Ctrl+C也会产生相同的效果)。
  • 硬件异常产生信号。例如:除数为0、无法的内存访问(常见的有段错误)等。这些条件通常是由硬件检测到的,并通知内核,之后由内核产生适当的信号并通知该进程。
  • 进程调用kill(2)函数可将任意信号发送给另一个进程或者进程组。对此有一个限制:要么发送信号的进程所有者是超级用户,要么发送进程和接收进程拥有相同的所有者。
  • 用户调用kill(1)命令将信号发送给其他的进程。我们常用此命令终止(个人更喜欢说杀死)一个后台进程。
  • 当检测到某种软件条件发生时,系统也会产生相应的信号通知该进程。例如定时时间到产生SIGALRM信号、管道读进程已经关闭却任然要往管道中写数据时产生SIGPIPE信号。

​ 信号是异步事件的典型示例。产生信号的事件对进程而言是随机出现的。进程不能通过测试一个简单的变量(如errno)来判断是否有信号发生,而是应该告诉内核:“当此信号发生时,应该执行如下操作”。这里一共有三种方式可供选择:

(1)忽略此信号。

(2)捕捉此信号。

(3)执行系统默认操作。

2.1 信号操作之忽略信号

​ 首先来说忽略信号的用法。大多数的信号都可以使用这种方式来处理信号,但是有两种信号是绝不能被忽略的,它们分别是SIGKILLSIGSTOP信号。这有两种信号不能被忽略的原因是:它们向内核和用户提供了使进程终止或者停止的可靠方法。此外,如果忽略某些由硬件产生的信号(例如SIGSEG信号),会导致软件出现无法预料的问题。

2.2 信号操作之捕捉信号

​ 为了实现捕捉信号的目的,我们必须通知内核在某种信号发生时,调用一个用户函数。在用户函数中,我们可以执行我们希望对该信号的处理方式。例如我们可以捕捉SIGALRM信号,当定时时间到时打印某些提示信息等。注意:不能捕捉SIGKILL和SIGSTOP信号

2.3 信号操作之执行系统默认操作

​ 对于大多数信号的系统默认操作都是终止该进程。

2.4 常见的信号

​ 下表中列出了31中信号编号、信号名称,Linux系统的默认操作,并对其中常见或者常用到的信号做了一个简单的说明。如果以后用到再做详细补充说明。

序号 信号名称 说明 默认操作
1 SIGHUP 暂不介绍 terminate
2 SIGINT 当用户按中断键(一般是Ctrl+C或Delete键)时,驱动程序会产生此信号来终止进程。 terminate
3 SIGQUIT 当用户按退出键(一般是Ctrl+)时,中断驱动程序会产生该信号,发送给所有前台进程。 coredump
4 SIGILL 该信号表示已经执行一条非法硬件指令。 coredump
5 SIGTRAP 指示一个实现的硬件故障。 coredump
6 SIGABRT 调用abort()函数来终止进程时会产生该信号。 coredump
7 SIGBUS 指示一个已定义的硬件故障 coredump
8 SIGFPE 表示算数运算异常。例如除0操作,浮点溢出等。 coredump
9 SIGKILL 无法被忽略和捕捉的信号。它向系统提供一种可以杀死任意进程的可靠方法。 terminate
0 SIGUSR1 用户定义的信号,可用于应用程序 terminate
11 SIGSEGV 无效的内存访问。例如经典的“段错误”。 coredump
12 SIGUSR2 用户定义的另一个信号,可用于应用程序 terminate
13 SIGPIPE 管道的读进程已经终止时写管道会产生该信号。 terminate
14 SIGALRM 当使用alarm()函数,或者setitimer()设置的定时时间到时会产生此信号 terminate
15 SIGTERM 是由kill(1)命令发送的系统默认终止信号。该信号可被应用程序捕获,从而进行清理工作,完成优雅的终止(相对于SIGKILL而言,SIGKILL信号不能被捕获或者忽略)。 terminate
16 SIGSTKFLT 暂不介绍
17 SIGCHLD 一个进程终止或者停止时,SIGCHLD信号会发送给其父进程。按系统默认,将忽略此信号。如果父进程需要被告知该子进程退出状态,则需要捕捉此信号。一般在信号处理函数中调用wait()函数回收子进程的资源。 ignore
18 SIGCONT 作业控制信号。它用来发送给需要继续运行,但当前处于停止状态的进程。收到此信号后,挂起的进程继续运行。如果本来已经在运行则忽略该信号。 ignore
19 SIGSTOP 作业控制信号,它停止一个进程。不能被捕捉或忽略 stop
20 SIGTSTP 交互停止信号。当用户按挂起键(一般是Ctrl+z)时,中断驱动程序产生此信号。 stop
21 SIGTTIN 暂不介绍 stop
22 SIGTTOU 暂不介绍 stop
23 SIGURG 暂不介绍 ignore
24 SIGXCPU 暂不介绍 coredump
25 SIGXFSZ 暂不介绍 coredump
26 SIGVTALRM 暂不介绍 terminate
27 SIGPROF 暂不介绍 terminate
28 SIGWINCH 暂不介绍 ignore
29 SIGIO 暂不介绍 terminate
30 SIGPWR 暂不介绍 terminate
31 SIGUNUSED / SIGSYS 一个无效的系统调用 coredump

3. 函数signal

3.1 signal函数介绍

Unix系统信号机制最简单的接口是signal函数。

#include 
void (*signal(int signo, void (*func)(int)))(int); 返回值:若成功,返回之前的信号处理配置;若失败,返回SIG_ERR.

代码中:

  • signo是指信号名称(详情参见2.4常见的信号

  • func是常量SIG_IGNSIG_DFL或者接收到信号时自定义的信号处理函数地址

    • 如果为SIG_IGN, 则向内核表示要忽略此信号
    • 如果为SIG_DFL, 则表示接收到此信号时执行系统的默认操作。
    • 当指定的是函数地址时,则在信号发生时,由内核调用该函数。我们称此函数为信号处理函数,或者信号捕捉函数

    说句心里话,signal的函数原型看起来有点看不懂:( 😦 😦 。下面我们也按前辈先人的说法再熟悉下(原话):

本节开头所示的signal函数原型太复杂了,如果使用下面的typedef,则可以使其简单些。

typedef void Sigfunc(int);			(3-1)

然后,可将signal函数原型写成:

Sigfunc *signal(int, Sigfunc *);	(3-2)

这样,signal的函数看起来就简单了很多:signal函数要求两个参数,并返回一个函数指针(如3-2所示),而该函数指针指向的函数有一个整型参数且无返回值(如3-1所示)。

​ 用通俗一点的话描述:定义一个信号处理函数,它有一个整型参数signo, 无返回值;当调用signal函数设置信号处理程序时,signal函数的第二个参数是指向该信号处理函数的指针,signal函数的返回值是指向未修改之前的信号处理函数指针。

​ 在上述的描述中,我们提到了三个宏定义: SIG_IGNSIG_DFLSIG_ERR。这三个宏Linux上的原型如下:

typedef void __sighandler_t(int);#define SIG_DFL	((__sighandler_t)0)		/* default signal handling */#define SIG_IGN	((__sighandler_t)1)		/* ignore signal */#define SIG_ERR	((__sighandler_t)-1)	/* error return from signal */

这三个常量可用于表示“指向函数的指针”。

3.2 signal函数示例

​ 该实例中定义了两个信号处理函数,捕获了三个信号(SIGUSR1, SIGUSR2共用一个信号处理函数)。

/*************************************************************************             > File Name: signal_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月27日 星期一 11时50分47秒 ************************************************************************/#include 
#include
static void sig_handler(int); /*自定义的信号处理函数*/static void sig_usr(int); /*自定义的信号处理函数*/int signal_install(){
if(signal(SIGINT, sig_handler)==SIG_ERR){
printf("SIGINT handle function register error\n"); } if(signal(SIGUSR1, sig_usr)==SIG_ERR){
printf("SIGUSR1 handle function register error\n"); } if(signal(SIGUSR2, sig_usr)==SIG_ERR){
printf("SIGUSR2 handle function register error\n"); }}void sig_handler(int signo){
if(signo == SIGINT){
printf("Recieved SIGINT signal\n"); }else{
printf("sig_handler receieve Error signal\n"); }}void sig_usr(int signo){
if(signo == SIGUSR1){
printf("Recieved SIGUSR1 signal\n"); }else if(signo == SIGUSR2){
printf("Recieved SIGUSR2 signal\n"); }else{
printf("sig_usr receieve Error signal\n"); }}void main(int argc, char *argv[]){
//signal_demo(); //exec_funcs(); signal_install(); while(1){
pause(); }}

结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out ^CRecieved SIGINT signal^CRecieved SIGINT signal^CRecieved SIGINT signal^Z[3]+  Stopped                 ./demo.outtoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out &[6] 19518toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR2 19518toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR2 signaltoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR1 19518toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR1 signaltoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

3.3 signal函数的限制

  • 如果想使用signal函数来获取当前进程对某一信号的处理方式,会修改当前的处理方式,否则无法确定当前的处理方式。常见的用法如下:
if(signal(SIGINT, SIG_IGN)!=SIG_IGN)	signal(SIGINT, sig_handler);if(signal(SIGUSR1, SIG_IGN)!=SIG_IGN)	signal(SIGINT, sig_usr);

后面我们将使用另一种信号处理方式:sigaction()函数,此函数无需修改便可以查询当前的处理方式。

  • 进程创建

    当一个进程调用fork时,其子进程继承了父进程的信号处理方式。因为子进程在创建时复制了父进程的内存映像,所以信号捕捉函数的地址在子进程中是有效的。

4. 不可靠的信号

4.1 什么是不可靠的信号

​ 在早期的Unix版本中,信号是不可靠的。这里的不可靠指的是:信号可能会丢失(一个信号已经发生了,但是该进程却不知道这一点)。除此之外,进程对信号的控制能力也特别差,它只能捕捉或者忽略信号。但是有时用户希望通知内核阻塞某个信号,不要忽略该信号;而在进程准备好处理该信号时在由内核重新通知该进程。

某些书籍提到signal函数每触发一次,得重新调用signal重新注册安装信号处理函数。这已经很很久以前的了,现在是一次signal注册,之后多次使用,无需每次在信号处理函数中重新调用signal函数(Linux便是如此)

4.2 信号丢失的例子:

​ 正常情况下,信号的发生频率很低,因此信号丢失的情况比较少。但是如果信号发生的频率比较高,且信号处理函数费时的话就很容易发生信号丢失的情形。下面我们通过在信号处理函数中调用延时函数模拟实现:

/*************************************************************************             > File Name: signal_lost.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月27日 星期一 15时03分18秒 ************************************************************************/#include
#include
extern void my_msleep(int mseconds);//自己实现的睡眠函数,与库函数并无区别static void sig_handler(int signo){
static int flag=0; printf("sig_handler finish: flag=%d\n", flag); if(signo == SIGINT){
printf("Recieved SIGINT signal\n"); flag++; my_msleep(5000);//延时5秒 }else{
printf("sig_handler receieve Error signal\n"); } printf("sig_handler finish: flag=%d\n", flag);}int signal_lost_test(){
if(signal(SIGINT, sig_handler)==SIG_ERR){
printf("SIGINT handle function register error\n"); } }

执行结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out ^Csig_handler finish: flag=0Recieved SIGINT signal^C^C^Csig_handler finish: flag=1sig_handler finish: flag=1Recieved SIGINT signalsig_handler finish: flag=2
运行程序后我是通过连续按下四次“Ctrl+C”,产生四个SIGINT信号,但实际上只捕获了其中的两个信号,另外两个则**丢失**了。之所以说丢失,是因为程序只打印了两次信号处理函数中的信息,该进程也只知道发生了两次事件。出现这种情形的主要原因在于信号处理程序处理的太慢(程序中我们把她睡了会儿),而信号又发生的太频繁,CPU处理不过来导致的。

​ 此外,上述结果中还有个奇怪的现象:信号处理程序不是一次性执行完毕的(专业点称为存在竞态),而是在第一个执行过程中又去相应下一个信号处理函数,等下一个处理完毕了再回来继续处理先前未执行完毕的信号处理函数。这个现像应该在编写程序中特别注意下。

5. 可靠的信号

​ 在第4部分,我们简要的说明了下什么是不可靠的信号,这里我们再来简要的说明下什么是可靠的信号。

5.1 常见的术语

  • 递送: 当对信号采取某种动作时,我们说向进程递送了一个信号。
  • 未决的:在信号产生(generate)和信号递送之间的这段时间间隔内,称信号为未决的(pending)

5.2 可靠的信号说明

​ 进程可以选择使用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对信号的动作是系统默认动作或捕捉该信号(非忽略状态),则为该进程将此信号保持为未决状态,直到该进程对此信号接触阻塞状态(或者修改为忽略此信号)。

​ 内核在递送一个原来被阻塞的信号给进程时,才决定对它的处理方式(因此之前的状态称为未决的)。于是乎进程在信号递送之前是可以修改对该信号的动作。系统可以调用sigpending函数来判断哪些信号是设置为阻塞同时悬而未决的。

​ 如果进程在解除多某个信号阻塞之前,该信号已经发生了多次(就是我们4.2的例子),那么该如何处理呢?目前大多数系统仍然是只递送一次该信号,也就是说不支持信号排队

​ 每一个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集。对于每一种可能的信号,该屏蔽字中都有与之相对应的位。如果该位被设置,则当前进程会阻塞该信号。程序中可以使用sigprocmask(后面我会详细说明)来查询和设置当前进程的信号屏蔽字。

​ 信号编号可能会超过一个整数所包含的二进制位数(32位系统是32位),因此POSIX.1专门定义了一个新的类型sigset_t,它可以容纳一个信号集。信号屏蔽字就存放在其中一个信号集中。后面我们对这部分进行详细说明。

6. 函数kill和raise

  • kill函数用来将信号发送给进程或者进程组。
  • raise函数则是进程用来向本进程发送信号的。
#include 
int kill(pid_t pid, int signo);int raise(int signo); 返回值说明:成功返回0;失败返回-1

调用

raise(signo);

相当于调用

kill(getpid(), signo);

函数kill的pid参数有以下四种不同的情况

序号 pid范围 说明
1 pid > 0 将信号发送给进程ID为pid的进程
2 pid == 0 将信号发送给与当前进程属于同一进程组的所有进程(进程组ID相同的进程),当然这里不包括系统进程和内核进程
3 pid < 0 将信号发送给进程组ID等于|pid|的所有进程。同样不包括系统进程和内核进程
4 pid == -1 将信号发送给具有权限的其他所有进程

​ 这里面有一个前提: 要么是超级用户,拥有所有的权限。要么是拥有相同进程ID的进程,否则无法发送信号给其他进程。

7. 函数alarm和pause

7.1 alarm()

​ 使用alarm函数用来设置一个定时器,在将来的某一时间该定时器会超时。当定时器超时时,产生SIGALRM信号。如果忽略或者不捕捉该信号,则执行默认的动作:终止当前进程

#include 
unsigned int alarm(unsigned int seconds); 返回值说明: 0或者以前设置的闹钟时间剩余的秒数。

​ 参数seconds的值是产生SIGALRM信号需要经过的秒数。当定时时间到时由内核产生,但是由于进程调度的延时,时间上有一定的延时。

​ 每一个进程只允许有一个闹钟时间:

/*************************************************************************             > File Name: alarm.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月27日 星期一 17时35分42秒 ************************************************************************/#include 
#include
void alarm_test(void){
int ret; ret=alarm(10); printf("First alarm :ret = %d\n", ret); sleep(2); ret=alarm(2); printf("Second alarm :ret = %d\n", ret);}

验证结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out First alarm :ret = 0Second alarm :ret = 8Alarm clock

​ 这会引入一个问题: 如果先前该进程已经注册了一个闹钟时间,但是还没有超时;如果重新设置定时器,先前剩余的时间会最为alarm()函数的返回值返回,与此同时,以前注册的闹钟时间被取代而不再生效。 如果有以前注册的尚未超时的闹钟时间,而且本地设置的seconds为0,则是取消该闹钟,剩余的时间仍然作为alarm函数的返回值。

7.2 pause()

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

#include 
int pause(void); 返回值:-1, errno设置为EINTR

​ 只有执行了一个信号处理程序并从其返回,pause函数才会返回。它的返回值一直为-1,并设置相应的错误码。

8 信号集

8.1信号集的基本操作函数

​ 前面我们已经知道,不同的信号的编号可能超过一个整型量所包含的位数,因此我们不能使用整型量中的一位来表示一种信号,也就是说我们不能使用整型变量来表示信号集。POSIX.1定义了一种新的数据类型:信号集(sigset_t), 并且还定义了5个处理信号的函数。

#include 
int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo); 上述四个函数返回值:若成功返回0;失败返回-1int sigismember(const sigset_t *set, int signo); 返回值:为真返回1;为假返回0
  • 函数sigemptyset初始化由set指向的信号集,清除其中所有的信号;
  • 函数sigfillset初始化set指向的信号集,使其包含所有的信号;

所有的应用程序在使用信号集之前,都需要调用sigemptyset或者sigfillset对信号集进行初始化。一旦初始化了一个信号集,以后便可以对信号集进行增加、删除特定的信号:

  • 函数sigaddset将一个信号添加到已有的信号集中;
  • 函数sigdelset从一个现有的信号集中删除一个特定的信号;

对所有以信号集为参数的函数,总是以信号集的地址作为传递的参数。

​ 这个原因在于:因为我们要在函数中修改信号集中的信号,因此需要地址传递方式(简单的值传递是行不通的),否则无法实现真正的修改。

8.2 函数sigprocmask: 进程屏蔽字

​ 我们在前面已经提到过:进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。进程的信号屏蔽字是通过函数sigprocmask来进行检测或修改的,或者同时进行检测和修改。

#include 
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); 返回值:成功返回0;失败返回-1;
  • 首先,如果oset是非空指针,那么进程的当前信号屏蔽字通过oset返回
  • 其次,如果set为非空指针,则参数how只是如何修改当前进程的信号屏蔽字;下表便是how的取值以及相关的说明。
  • 如果set为空指针,则不修改该进程的信号屏蔽字,how值是无意义的
序号 how 说明
1 SIG_BLOCK 将set中的信号添加到当前进程的信号屏蔽字中(两者取或运算,即并集)
2 SIG_UNBLOCK 将set中的信号从当前进程信号屏蔽字中删除(set补集的交集)
3 SIG_SETMASK 将set设置为当前进程的信号屏蔽字(不关心原进程的信号屏蔽字)

在调用sigprocmask后如果有任何悬而未决、不再阻塞的信号(原来信号是阻塞,现在改为非阻塞),则在sigprocmask函数返回前至少将其中之一递送给该进程。

示例:获取当前进程屏蔽字

/*************************************************************************             > File Name: sigprocmask_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 08时15分06秒 ************************************************************************/#include 
#include
#include
/*获取当前进程的信号屏蔽字,并测试包括哪些信号。*/void getSigProcMask(){
sigset_t set; printf("Enter %s\n", __func__); if(sigemptyset(&set)!=0){
perror("sigemptyset error:"); }else if(sigprocmask(0,NULL,&set)!=0){
perror("sigprocmask error:"); }else{
printf("Sigprocmask contains following signals:"); if(sigismember(&set, SIGINT)) printf(" SIGINT"); if(sigismember(&set, SIGQUIT)) printf(" SIGQUIT"); if(sigismember(&set, SIGALRM)) printf(" SIGALRM"); if(sigismember(&set, SIGCHLD)) printf(" SIGCHLD"); printf("\n"); } printf("Exit %s\n", __func__);}

8.3 函数sigpending

​ sigpending函数返回一个信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前悬而未决的。该信号集通过set参数返回。

#include 
int sigpending(sigset_t *set); 返回值:成功返回0;失败返回-1;

示例:查询当前挂起的信号

/*************************************************************************             > File Name: sigpending_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 08时36分45秒 ************************************************************************/#include 
#include
/*初始化进程屏蔽字*/void testPendingSignal(){
/*定义三个信号集*/ sigset_t new_set; /*,用来设置新的屏蔽字*/ sigset_t old_set; /*,用来获取之前的信号屏蔽字*/ sigset_t pending_set; /*,获取正在挂起的信号屏蔽字*/ /*清空三个信号集*/ sigemptyset(&new_set); sigemptyset(&old_set); sigemptyset(&pending_set); /*将SIGQUIT信号添加至信号集中*/ sigaddset(&new_set, SIGINT); /*设置当前进程的信号屏蔽字*/ if(sigprocmask(SIG_BLOCK, &new_set, &old_set)!=0){
printf("%s error!!!\n", __func__); return; } printf("sleeping.......\n"); sleep(5); if(sigpending(&pending_set)!=0){
printf("%s error!!!\n", __func__); return; } if(sigismember(&pending_set, SIGINT)) printf("SIGINT is in pending_set\n"); if(sigismember(&pending_set, SIGQUIT)) printf("SIGQUIT is in pending_set\n"); /*将进程的信号屏蔽字回复到修改之前的状态*/ if(sigprocmask(SIG_SETMASK, &old_set, NULL)!=0){
printf("%s error!!!\n", __func__); return; } printf("%s...exit....\n", __func__);}void main(int argc, char *argv[]){
testPendingSignal(); while(1){
pause(); }}

程序的执行结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out sleeping.......^C^C^C^C^CSIGINT is in pending_settoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

​ 因为我们在程序中设置了进程屏蔽字,所以进程会阻塞SIGINT信号。在sleep(5)的睡眠等待的过程中,我连续按了5次"Ctrl+C“产生了五次SIGINT信号。但是由于进程暂时屏蔽该信号,不会讲该信号递送给进程,因此程序也不会响应(SIGINT的默认动作是结束当前进程),而当我重新恢复进程的信号屏蔽字后,先前产生的SIGINT信号被立即递送到进程(应该是在sigprocmask函数返回之前就递送了),因此函数testPendingSignal()的最后一行并没有打印出来。

为了解除对该信号的阻塞,我们用先前的信号屏蔽字重新设置了(SIG_SETMASK)进程的信号屏蔽字。

这样做的原因在于: 可能先前的进程已经屏蔽了该信号(例如其他函数接口设置了,但是自己并不知道)。如果我们简单的使用SIG_UNBLOCK来将信号从进程屏蔽字中删除可能会影响其他程序正常的功能。因此这里推荐使用SIG_SETMASK的方式重新设置修改之前信号屏蔽字。

8.4 函数sigaction

​ sigaction函数的功能是检查或者修改(也可以是检查和修改)与指定信号相关联的动作。 该函数是对signal函数的改进

#include 
int sigaction(int signo, const struct sigaction *restrict act, struct sinaction *restrict oact); 返回值:成功返回0;失败返回-1;

​ 其中:

  • 参数signo是要检测或者修改其具体动作的信号编号。如果act指针非空,则要修改信号的动作;如果oact指针非空,则是通过oact指针将返回该信号的上一个处理动作(如果act为空,即未修改,就是当前信号的处理动作)。struct sigaction的结构如下(Linux形式上可能有所不同):
struct sigaction{
void (*sa_handler)(int); /*addr of signal handler*/ /*or SIG_IGN, or SIG_DFL*/ sigset_t sa_mask; /*adddtional signals to block*/ int sa_flags; /*signal options, 详细信息见下表*/ /*alternate handler*/ void (*sa_sigaction)(int, siginfo_t *, void *);};

​ 当设置信号动作时,如果sa_handler指针指向一个信号捕捉函数的地址(不是常量SIG_IGN、SIG_DEL), 则sa_mask字段说明了一个信号集。 在调用该信号捕捉函数时,该信号屏蔽字要添加到进程的信号屏蔽字中,当信号处理函数处理完毕后,在重新将进程的信号屏蔽字恢复为原先的值。这样做最主要的目的在于:防止两个相同的信号同时(间隔很短)到来时产生竞态。也就是说应该在处理该信号的过程中阻塞该类型的后续信号

​ 当然,这个函数也是不支持信号入队的:如果阻塞过程中发生了多个该信号,那么只会递送一次该信号。

  • sa_flags字段指明了对信号处理的各个选项:
序号 选项 说明
1 SA_INTERRUPT 由此信号中断的系统调用不自动重启(sigaction默认处理方式)
2 SA_NOCLDSTOP 若signo为SIGCHLD, 当子进程停止时不产生此信号;当子进程终止时产生此信号
3 SA_NOCLDWAIT 若signo为SIGCHLD, 当子进程终止时不产生僵尸进程;若调用进程随后调用wait,则阻塞到它所有进程都终止。
4 SA_NODEFER 捕捉到此函数时,在执行信号捕捉函数时系统不自动阻塞该信号(除非sa_mask中包括此信号)
5 SA_ONSTACK 递交给替换栈上的进程
6 SA_RESETHAND 在信号捕捉函数入口处将此信号的处理函数改为SIG_DFL, 并清除SA_SIGINFO标志
7 SA_RESTART 由此信号中断的系统调用自动重启
8 SA_SIGINFO 此选项对信号处理程序提供一个附加信息。
  • sa_sigaction字段是一个替代的信号处理程序,在sigaction结构中使用SA_SIGINFO标志时,使用该信号处理程序。对于sa_handlersa_sigaction,实现上一般是一个共用体,因此应用只能一次使用他们中的一个。下面是Linux2.6.12上的实现,从这里可以看出_sa_sigaction和sa_handler是一个共用体:
struct sigaction {
union {
__sighandler_t _sa_handler; void (*_sa_sigaction)(int, struct siginfo *, void *); } _u; sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void);};

示例:使用sigaction实现signal

/*************************************************************************             > File Name: sigaction2signal.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 10时30分48秒 ************************************************************************/#include 
#include
/** 我们使用sigaction函数来实现signal的功能*/typedef void Sigfunc(int);Sigfunc *signal(int signo, Sigfunc *func){
struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if(sigaction(signo, &act, &oact) < 0) return (SIG_ERR); return (oact.sa_handler);}static void alarm_handler(int signo){
printf("Catch SIGALRM signal!!!\n");}void sigaction2signal_demo(){
signal(SIGALRM, alarm_handler); alarm(1); sleep(2); alarm(1);}void main(int argc, char *argv[]){
//signal_demo(); //exec_funcs(); //signal_lost_test(); sigaction2signal_demo(); while(1){
pause(); }}

执行结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out Catch  SIGALRM signal!!!Catch  SIGALRM signal!!!^Ctoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

8.5 函数sigsuspend

​ 首先看sigsuspend的函数原型:

#include 
int sigsuspend(const sigset_t *sigmask); 返回值:-1

sigsuspend函数的使用方法在下面的示例中进行说明。·

示例1:错误用法

​ 我们已经知道,修改进程的信号屏蔽字可以阻塞所选择的信号,或者解除对他们的阻塞。使用这种技术可以保护不希望被信号打断的临界代码区。如果希望对一个信号解除阻塞,然后调用pause以等待以前被阻塞的信号发生,发生什么事情呢? 我们依然使用程序来说明:

/*************************************************************************             > File Name: sigsuspend_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 10时58分19秒 ************************************************************************/#include 
#include
static void handler(int signo){
printf("Catch SIGINT!!!\n");}void invalid_usages(){
sigset_t newmask, oldmask; sigfillset(&newmask);//阻塞所有信号 sigemptyset(&oldmask); //sigaddset(&newmask, SIGINT); signal(SIGINT, handler); if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0){
printf("sigprocmask SIG_BLOCK error\n"); return; } /*临界代码区:critical region of code begin*/ sleep(2); /*此区域的代码不会被信号打断*/ /*临界代码区:critical region of code end*/ /*解除对SIGINT的阻塞*/ if(sigprocmask(SIG_SETMASK, &oldmask, NULL)<0){
printf("sigprocmask SIG_SETMASK error\n"); return; } printf("Before pause\n"); pause(); printf("After pause\n");}void main(int argc, char *argv[]){
//signal_demo(); //exec_funcs(); //signal_lost_test(); invalid_usages();}

我在程序执行到sleep(2);时连续按下“Ctrl+C”, 这里会被阻塞,没有什么问题。2秒过后,进程屏蔽字解除信号阻塞,然后调用pause()等待信号的到来。那么问题来了,信号是在sigprocmask调用返回之前就递送到进程的,因此paus()是捕捉不到SIGINT信号的,之后程序会一直阻塞,直到下一个信号的到来(后面我又重新按下“Ctrl+C“退出程序的)。结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out ^C^C^C^C^CCatch SIGINT!!!Before pause^CCatch SIGINT!!!After pausetoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

针对上述的问题,系统实现了一个原子操作:解除阻塞信号并等待

之前,我对于这个sigsuspend()函数的功能总是不得要领,不明白它的真正目的。现在有一点清楚了: suspend()函数的出现就是为了解决上述的问题。它的作用包括两部分:

  • 修改当前的进程屏蔽字(将其修改为sigmask
  • 阻塞当前进程
  • 非sigmask中的信号到来时:
    • 首先执行信号的处理函数
    • 然后恢复进程的信号屏蔽字(调用suspend之前的信号屏蔽字)
    • 最后进程继续运行

上述这几步都是suspend函数自动完成的。

示例2:suspend的用法

void sigsuspend_demo(){
sigset_t newmask, oldmask, waitmask; sigfillset(&newmask);//阻塞所有信号 sigemptyset(&oldmask); sigfillset(&waitmask);//等待所有信号 sigdelset(&waitmask, SIGINT);//等待除了SIGINT之外的所有信号 signal(SIGINT, handler); if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0){
printf("sigprocmask SIG_BLOCK error\n"); return; } if(sigsuspend(&waitmask) < 0){
printf("sigsuspend error\n"); return; } /*临界代码区:critical region of code begin*/ sleep(2); /*此区域的代码不会被SIGINT信号打断*/ /*临界代码区:critical region of code end*/ /*阻塞等待SIGINT信号*/ if(sigsuspend(&waitmask) < 0){
printf("sigsuspend error\n"); return; } /* .... */}

转载地址:https://blog.csdn.net/s2603898260/article/details/105810406 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:IPSec 专栏目录锦集(openswan)
下一篇:我运了盘“外国人”的“泡子”(有吴哥,金

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年03月17日 21时57分40秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

php 实现版本号对比,如何在PHP中实现比较版本号 2019-04-21
php sql 给数据库追加内容,php如何向数据库中的某串数据后追加内容【急】 2019-04-21
php微信小程序获取用户信息,微信小程序授权获取用户详细信息openid的实例详解... 2019-04-21
Java三元运算和if,Java三元运算符与<JDK8兼容性中的if / else 2019-04-21
graphql-php enum,php – 如何在不写长查询的情况下查询所有的GraphQL类型字段? 2019-04-21
php date 函数用法,php中date()日期时间函数使用方法 2019-04-21
php除法获取整数和余数,PHP除法取整和取余数 2019-04-21
java迷宫路径,Java中的迷宫路径查找器 2019-04-21
php substr cnblog,php中substr用法示例 2019-04-21
php链接怎么截取,PHP 截取网页中的固定种子链接 2019-04-21
iis运行不起来php报500,解决IIS上安装thinkphp6运行报500错误 2019-04-21
php ajax上传图片过大500错误,javascript – JQuery AJAX文件上传错误500 2019-04-21
matlab 图中的legend,matlab中legend加图示命令的使用 2019-04-21
PHP exec xargs 不执行,Linux中的xargs命令及示例 2019-04-21
php 枚举cookie内容,php设置和获取cookie 2019-04-21
单防区扩展模块怎么用_AB罗克韦尔自动化Micro800 扩展 I/O模块型号及功能介绍 2019-04-21
java矩阵类_Java泛型——泛型矩阵类 2019-04-21
java车牌正则表达式_车牌正则表达式 2019-04-21
wordpress4.9.4 mysql_WordPress 将不再支持 PHP4 和 MySQL 4 2019-04-21
安卓是用java语言写的吗_android开发是用java语言吗? 2019-04-21