信号的阻塞
8.3.1 信号的处理方式
在Linux系统下,信号的处理方式有3种:一是忽略信号;而是按照系统提供的缺省处理规则进行处理;三是捕捉信号,在程序中定义自己的信号处理函数,在信号处理函数中完成相应的功能。Linux系统分别为前两种方式提供了相应的宏定义:SIG_IGN和SIG_DFN。下面列出几个重要信号及其处理方式。
-----------------------------------------------------------------------------------------------------------------
信号名称 缺省处理方式 信号产生原因/说明
-----------------------------------------------------------------------------------------------------------------
SIGHUP 终止进程 控制终端挂起或者退出
SIGINT 终止进程 <Ctrl>+<C>
SIGQUIT 终止进程并进行内核映像转储 <Ctrl>+<>
SIGKILL 终止进程 不能阻塞、忽略、捕捉
SIGALRM 终止进程 定时器超时信号
SIGTERM 终止进程 可以阻塞、捕捉
SIGCHLD 混略信号 子进程退出时向父进程发送该信号
SIGSTOP 暂停进程执行 不能阻塞、忽略、捕捉
-----------------------------------------------------------------------------------------------------------------
8.3.2 信号的阻塞处理 信号的阻塞就是通知系统内核暂时停止向进程发送指定的信号,而是由内核对进程接收到的相应信号进行缓存排队,直到进程解除对相应信号的阻塞为止。一旦进程解除对该信号的阻塞,则缓存的信号将被发送到相应的进程。 信号在几种情况下会进入阻塞状态。 1)系统自动阻塞:在信号的处理函数执行过程中,该信号将被阻塞,直到信号处理函数执行完毕,该阻塞将会解除。这种机制的作用主要是避免信号的嵌套。 2)通过sigaction实现人为阻塞:在使用sigaction安装信号时,如果设置了sa_mask阻塞信号集,则该信号集中的信号在信号处理函数执行期间将会阻塞。这种情况下进行信号阻塞的主要原因是:一个信号处理函数在执行过程中,可能会有其他信号到来。此时,当前的信号处理函数就会被中断。而这往往是不希望发生的。此时,可以通过sigaction系统调用的信号阻塞掩码对相关信号进行阻塞。通过这种方式阻塞的信号,在信号处理函数执行结束后就会解除。 3)通过sigprocmask实现人为阻塞:可以通过sigprocmask系统调用指定阻塞某个或者某几个信号。这种情况下进行信号阻塞的原因较多,一个典型的情况是:某个信号的处理函数与进程某段代码都要某个共享数据区进行读写。如果当进程正在读写共享数据区的过程中,一个信号过来,则进程的读写过程将被中断转而执行信号处理函数,而信号处理函数也要对该共享数据区进行读写,这样共享数据区就会发生混乱。这种情况下,需要在进程读写共享数据区前阻塞该信号,在读写完成后再解除该信号的阻塞。 提示:在信号的接收过程中可能存在这样的情况:若干个相同的信号同时到达。通过上面的介绍可以知道,当信号处理函数正在执行时,同类信号将被阻塞处理。但是,如果此时信号处理函数还没有来得及执行,那么该同类信号就不会阻塞,在这种情况下,将会发生一种成为“信号合并”的现象。同时到达的同类信号将被合并处理,就像只有一个信号到达一样。 被阻塞的信号的集合成为当前进程的信号掩码。每个进程都有惟一的信号掩码。为了对信号进行阻塞或者解除阻塞,Linux提供了专门的系统调用sigprocmask完成这一任务。该函数的原型为: #include <signal.h> int sigprocmask(int how,const sigset_t *set,sigset_t *oset); 参数说明: 1)how:输入参数,设置信号阻塞掩码的方式。可以包括3种方式对信号的掩码进行设置,分别是阻塞信号的SIG_BLOCK、解除阻塞的SIG_UNBLOCK和设置阻塞掩码的SIG_SETMASK。 2)set:输入参数,阻塞信号集。当参数how为SIG_BLOCK时,该参数表明要阻塞的信号集;当how参数为SIG_UNBLOCK时,该参数表明要解除阻塞的信号集;当how参数为SIG_SETMASK时,该参数表明要阻塞的信号集。 3)oset:输出参数,原阻塞信号集。 返回值: 若成功,返回0;若失败,返回-1。
例8-3:编程实现下面功能:为进程安装SIGINT信号,先阻塞该信号,休眠10秒,再解除该信号的阻塞。 代码如下: #include <stdio.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //信号掩码结构变量,用于保存原来的信号处理掩码 sigset_t omask; //安装SIGINT信号 signal(SIGINT,CbSigInt); //清空信号掩码变量 sigemptyset(&mask); //向掩码结构中增加信号SIGINT sigaddset(&mask,SIGINT); //阻塞SIGINT信号 sigprocmask(SIG_BLOCK,&mask,&omask); //休眠10秒 sleep(10); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); } 编译运行该程序,在进程休眠期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,注意观看信号是否被阻塞。在休眠结束后,验证刚才被阻塞的SIGINT信号是否被重新发送。
提示:在创建新的子进程时,子进程将继承父进程的信号掩码。
8.3.3 信号集的操作 通过上节对信号阻塞的介绍可以知道,信号的阻塞实际上是对一个集合的操作。这个集合中可能包含多种信号,这就是信号集。信号集的数据类型为sigset_t,实际上是个结构体,它的定义如下所示。 typedef struct { unsigned long sig[_NSIG_WORDS]; }sigset_t; Linux系统提供了一系列函数对信号集进行操作。这些函数的原型如下所示。 #include <signal.h> sigemptyset(sigset_t *set); //初始化由set指定的信号集,信号集里面的所有信号被清空; sigfillset(sigset_t *set); //调用该函数后,set指向的信号集中将包含linux支持的64种信号; sigaddset(sigset_t *set,int signo); //在set指向的信号集中加入signo信号; sigdelset(sigset_t *set,int signo); //在set指向的信号集中删除signo信号; sigismember(const sigset_t *set,int signo); //判定信号signo是否在set指向的信号集中。 参数说明: 1)set:输入参数,信号集。 2)signo:输入参数,要增加或删除或判断的信号。 返回值: 1)对于sigismember函数:返回1表示信号属于信号集;返回0表示信号不属于信号集。 2)对于其他函数:若成功,返回0;若失败,返回-1。
8.3.4 未决信号的处理 信号的未决是信号产生后的一种状态,是指从信号产生后,到信号被接收进程处理之前的一种过渡状态。由于信号的未决状态时间非常短,所以通常情况下,处于未决状态的信号非常少。如果程序中使用了sigprocmask阻塞了某种信号,则向进程发送的这种信号将处于未决状态。Linux提供了专门的函数sigpending获取当前进程中处于未决状态的信号。该函数的原型为: #include <signal.h> int sigpending(sigset_t *set); 参数说明: 1)set:输出参数,处于未决状态的信号集。 返回值: 若成功,返回0;若失败,返回-1。
例8-4:编程实现下面功能:为进程安装SIGINT信号,先阻塞该信号,休眠10秒,最后查看当前进程未决的信号。 代码如下: #include <stdio.h> #include <signal.h> void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //信号掩码结构变量,用于保存原来的信号处理掩码 sigset_t omask; //信号掩码结构变量,用于保存未决的信号集 sigset_t pendmask; //清空信号掩码变量 sigemptyset(&mask); //向掩码结构中增加信号SIGINT sigaddset(&mask,SIGINT); //阻塞SIGINT信号 sigprocmask(SIG_BLOCK,&mask,&omask); //休眠10秒 sleep(10); //获取当前未决的信号集 if(sigpending(&pendmask)<0) { perror("sigpending"); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); return; } //判断SIGINT是否在未决信号集中 if(sigismember(&pendmask,SIGINT)) printf("SIGINT signal is pending. "); else printf("SIGINT signal is not pending. "); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); } 编译运行该程序,在进程休眠期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否处于未决状态。
8.3.5 等待信号
8.3.5.1 pause 在有些情况下,程序需要暂停执行,进入休眠状态,以等待信号的到来。这时可以使用pause系统调用。pause一旦被调用,则进程将进入休眠状态。之后,只有在进程接收到信号后,pause才会返回。pause的原型为: #include <unistd.h> int pause(); 返回值: pause的返回值永远是-1,错误码errno为EINTR。
例8-5:用pause编程实现等待SIGINT信号到来的功能。 代码如下: #include <stdio.h> #include <unistd.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //安装SIGINT信号 signal(SIGINT,CbSigInt); //等待信号 pause(); } 编译运行该程序,在进程pause期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否被处理。 注意:由于此测试程序没有屏蔽其他信号,因此任何一个信号的到来都能唤醒pause。
8.3.5.2 sigsuspend pause系统调用可以实现暂停进程的执行等待某个信号的到来,但是,如果在pause被调用之前,指定的信号到达进程,那么,在随后的pause调用中,假定不再有信号到来,则进程将进入无限期的等待中。为此Linux提供了功能更强大的sigsuspend以满足这种需求。sigsuspend的工作过程如下: 1)设置进程的信号掩码并阻塞进程。 2)收到信号,恢复原来的信号掩码。 3)调用进程设置的信号处理函数。 4)等待信号处理函数返回后,sigsuspend返回。 上述四个步骤是一次性完成的,操作系统保证操作过程的原子性。特别需要注意的是第三步调用信号处理函数是由sigsuspend完成的。sigsuspend的原型为: #include <signal.h> int sigsuspend(const sigset_t *set); 参数说明: 1)set:输入参数,执行sigsuspend过程中需要被阻塞的信号集。 返回值: sigsuspend的返回值永远是-1,错误码errno为EINTR。
例8-6:用sigsuspend编程实现等待SIGINT信号到来的功能。 代码如下: #include <stdio.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //安装SIGINT信号 signal(SIGINT,CbSigInt); //设置信号集为所有信号,准备阻塞所有信号 sigfillset(&mask); //从信号集中删除SIGINT信号,该信号为目标信号,不能被阻塞。 sigdelset(&mask,SIGINT); //等待SIGINT信号 sigsuspend(&mask); } 编译运行该程序,在进程suspend期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否被处理。 注意:由于此测试程序屏蔽了除SIGINT外的所有其他信号,因此只有在收到SIGINT信号后进程才会退出。
提示: 一个阻塞式系统调用在执行过程中如果没有符合条件的数据,将进入休眠状态,直到有符合条件的数据到来。比较典型的例子是从网络连接上读取数据,如果没有数据到来,那么这个读操作将会阻塞。此时有两种情况可以中断该读操作的执行:一是网络上有数据到来,则读操作将获取到所需要的数据后返回;二是当前进程收到了某个信号,此时,读操作将被中断并返回失败,错误码errno为EINTR。
8.3.6 信号处理函数的实现 信号处理函数是进程接收到信号后要执行的函数,该函数应该尽量简洁,一般不要执行过多的代码。最好只是改变一个外部标志变量的值,而在另外的程序中不断的检测该变量,繁杂的工作都留给那些程序去做。在定义信号处理函数时,应该特别注意以下几点。 1)如果信号处理程序中需要存取某个全局变量,则应该在程序中使用关键字volatile声明此变量。通知编译器,在编译过程中不要对该变量进行优化。 2)如果在信号处理函数中调用某个函数,那么那么该函数必须是可重入的,或者保证在信号处理函数执行期间不会有信号到达进程。Linux系统下存在许多不可重入的函数,如malloc、gethostbyname等。 在信号处理函数里,有时需要用到长跳转的操作。所谓长跳转,就是从信号处理函数直接跳转到函数体外指定的代码位置继续运行。Linux系统提供了两个函数实现该功能:设置跳转点的sigsetjmp和执行跳转的siglongjmp。sigsetjmp用来设置跳转点,在成功调用后,sigsetjmp语句所在的位置就是跳转点,这个位置指针将被保存到sigsetjmp的第一个参数中。这个两个函数的原型为: #include <setjmp.h> int sigsetjmp (struct __jmp_buf_tag env[1], int savemask); void siglongjmp(sigjmp_buf env,int val); 参数说明: 1)env[1]:输出参数,该参数实际上是一个结构体的指针。该结构体中包含了长跳转指针,是否保存信号掩码及保存的信号掩码值等信息。对于应用人员来说,该结构是透明的。 2)env:输入参数,等效于env[1]。 3)savemask:是否保存信号掩码。如果该参数非零,则在调用sigsetjmp后,当前进程的信号掩码将被保存;在调用siglongjmp时,将恢复由sigsetjmp保存的信号掩码。 4)val:当由siglongjmp调用sigsetjmp时,该参数将会被隐含传给sigsetjmp作为返回值。如果val等于0,那么sigsetjmp函数将忽略该参数而返回其他非零值。 返回值: 1)sigsetjmp函数:若返回0,表明sigsetjmp不是由siglongjmp调用的;若返回非零值,则是由siglongjmp调用而返回。
例8-7:编程实现捕捉SIGINT信号,在信号处理函数中用长跳转跳转至主程序。 代码如下: #include <stdio.h> #include <signal.h> #include <setjmp.h> //全局变量,用于保存跳转点及其现场 static sigjmp_buf jmpbuff; //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf(" receive signal %d ",signo); //长跳转到jmpbuff(即sigsetjmp函数入口处),并平衡堆栈 siglongjmp(jmpbuff,88); } void main() { int res; //安装SIGINT信号 signal(SIGINT,CbSigInt); //设置跳转点 res=sigsetjmp(jmpbuff,1); //第一次调用sigsetjmp时将返回0 if(res==0) printf("First call sigsetjmp! "); //从信号处理函数中跳转过来时,sigsetjmp将返回非零值 else { //输出提示信息后退出进程 printf("res=%d ",res); printf("sigsetjmp is called by siglongjmp! "); return; } //暂停执行等待信号 pause(); }
编译运行该程序,在进程pause期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证信号处理函数是否跳转到指定的位置。
8.3.2 信号的阻塞处理 信号的阻塞就是通知系统内核暂时停止向进程发送指定的信号,而是由内核对进程接收到的相应信号进行缓存排队,直到进程解除对相应信号的阻塞为止。一旦进程解除对该信号的阻塞,则缓存的信号将被发送到相应的进程。 信号在几种情况下会进入阻塞状态。 1)系统自动阻塞:在信号的处理函数执行过程中,该信号将被阻塞,直到信号处理函数执行完毕,该阻塞将会解除。这种机制的作用主要是避免信号的嵌套。 2)通过sigaction实现人为阻塞:在使用sigaction安装信号时,如果设置了sa_mask阻塞信号集,则该信号集中的信号在信号处理函数执行期间将会阻塞。这种情况下进行信号阻塞的主要原因是:一个信号处理函数在执行过程中,可能会有其他信号到来。此时,当前的信号处理函数就会被中断。而这往往是不希望发生的。此时,可以通过sigaction系统调用的信号阻塞掩码对相关信号进行阻塞。通过这种方式阻塞的信号,在信号处理函数执行结束后就会解除。 3)通过sigprocmask实现人为阻塞:可以通过sigprocmask系统调用指定阻塞某个或者某几个信号。这种情况下进行信号阻塞的原因较多,一个典型的情况是:某个信号的处理函数与进程某段代码都要某个共享数据区进行读写。如果当进程正在读写共享数据区的过程中,一个信号过来,则进程的读写过程将被中断转而执行信号处理函数,而信号处理函数也要对该共享数据区进行读写,这样共享数据区就会发生混乱。这种情况下,需要在进程读写共享数据区前阻塞该信号,在读写完成后再解除该信号的阻塞。 提示:在信号的接收过程中可能存在这样的情况:若干个相同的信号同时到达。通过上面的介绍可以知道,当信号处理函数正在执行时,同类信号将被阻塞处理。但是,如果此时信号处理函数还没有来得及执行,那么该同类信号就不会阻塞,在这种情况下,将会发生一种成为“信号合并”的现象。同时到达的同类信号将被合并处理,就像只有一个信号到达一样。 被阻塞的信号的集合成为当前进程的信号掩码。每个进程都有惟一的信号掩码。为了对信号进行阻塞或者解除阻塞,Linux提供了专门的系统调用sigprocmask完成这一任务。该函数的原型为: #include <signal.h> int sigprocmask(int how,const sigset_t *set,sigset_t *oset); 参数说明: 1)how:输入参数,设置信号阻塞掩码的方式。可以包括3种方式对信号的掩码进行设置,分别是阻塞信号的SIG_BLOCK、解除阻塞的SIG_UNBLOCK和设置阻塞掩码的SIG_SETMASK。 2)set:输入参数,阻塞信号集。当参数how为SIG_BLOCK时,该参数表明要阻塞的信号集;当how参数为SIG_UNBLOCK时,该参数表明要解除阻塞的信号集;当how参数为SIG_SETMASK时,该参数表明要阻塞的信号集。 3)oset:输出参数,原阻塞信号集。 返回值: 若成功,返回0;若失败,返回-1。
例8-3:编程实现下面功能:为进程安装SIGINT信号,先阻塞该信号,休眠10秒,再解除该信号的阻塞。 代码如下: #include <stdio.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //信号掩码结构变量,用于保存原来的信号处理掩码 sigset_t omask; //安装SIGINT信号 signal(SIGINT,CbSigInt); //清空信号掩码变量 sigemptyset(&mask); //向掩码结构中增加信号SIGINT sigaddset(&mask,SIGINT); //阻塞SIGINT信号 sigprocmask(SIG_BLOCK,&mask,&omask); //休眠10秒 sleep(10); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); } 编译运行该程序,在进程休眠期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,注意观看信号是否被阻塞。在休眠结束后,验证刚才被阻塞的SIGINT信号是否被重新发送。
提示:在创建新的子进程时,子进程将继承父进程的信号掩码。
8.3.3 信号集的操作 通过上节对信号阻塞的介绍可以知道,信号的阻塞实际上是对一个集合的操作。这个集合中可能包含多种信号,这就是信号集。信号集的数据类型为sigset_t,实际上是个结构体,它的定义如下所示。 typedef struct { unsigned long sig[_NSIG_WORDS]; }sigset_t; Linux系统提供了一系列函数对信号集进行操作。这些函数的原型如下所示。 #include <signal.h> sigemptyset(sigset_t *set); //初始化由set指定的信号集,信号集里面的所有信号被清空; sigfillset(sigset_t *set); //调用该函数后,set指向的信号集中将包含linux支持的64种信号; sigaddset(sigset_t *set,int signo); //在set指向的信号集中加入signo信号; sigdelset(sigset_t *set,int signo); //在set指向的信号集中删除signo信号; sigismember(const sigset_t *set,int signo); //判定信号signo是否在set指向的信号集中。 参数说明: 1)set:输入参数,信号集。 2)signo:输入参数,要增加或删除或判断的信号。 返回值: 1)对于sigismember函数:返回1表示信号属于信号集;返回0表示信号不属于信号集。 2)对于其他函数:若成功,返回0;若失败,返回-1。
8.3.4 未决信号的处理 信号的未决是信号产生后的一种状态,是指从信号产生后,到信号被接收进程处理之前的一种过渡状态。由于信号的未决状态时间非常短,所以通常情况下,处于未决状态的信号非常少。如果程序中使用了sigprocmask阻塞了某种信号,则向进程发送的这种信号将处于未决状态。Linux提供了专门的函数sigpending获取当前进程中处于未决状态的信号。该函数的原型为: #include <signal.h> int sigpending(sigset_t *set); 参数说明: 1)set:输出参数,处于未决状态的信号集。 返回值: 若成功,返回0;若失败,返回-1。
例8-4:编程实现下面功能:为进程安装SIGINT信号,先阻塞该信号,休眠10秒,最后查看当前进程未决的信号。 代码如下: #include <stdio.h> #include <signal.h> void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //信号掩码结构变量,用于保存原来的信号处理掩码 sigset_t omask; //信号掩码结构变量,用于保存未决的信号集 sigset_t pendmask; //清空信号掩码变量 sigemptyset(&mask); //向掩码结构中增加信号SIGINT sigaddset(&mask,SIGINT); //阻塞SIGINT信号 sigprocmask(SIG_BLOCK,&mask,&omask); //休眠10秒 sleep(10); //获取当前未决的信号集 if(sigpending(&pendmask)<0) { perror("sigpending"); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); return; } //判断SIGINT是否在未决信号集中 if(sigismember(&pendmask,SIGINT)) printf("SIGINT signal is pending. "); else printf("SIGINT signal is not pending. "); //解除SIGINT信号的阻塞 sigprocmask(SIG_SETMASK,&omask,NULL); } 编译运行该程序,在进程休眠期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否处于未决状态。
8.3.5 等待信号
8.3.5.1 pause 在有些情况下,程序需要暂停执行,进入休眠状态,以等待信号的到来。这时可以使用pause系统调用。pause一旦被调用,则进程将进入休眠状态。之后,只有在进程接收到信号后,pause才会返回。pause的原型为: #include <unistd.h> int pause(); 返回值: pause的返回值永远是-1,错误码errno为EINTR。
例8-5:用pause编程实现等待SIGINT信号到来的功能。 代码如下: #include <stdio.h> #include <unistd.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //安装SIGINT信号 signal(SIGINT,CbSigInt); //等待信号 pause(); } 编译运行该程序,在进程pause期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否被处理。 注意:由于此测试程序没有屏蔽其他信号,因此任何一个信号的到来都能唤醒pause。
8.3.5.2 sigsuspend pause系统调用可以实现暂停进程的执行等待某个信号的到来,但是,如果在pause被调用之前,指定的信号到达进程,那么,在随后的pause调用中,假定不再有信号到来,则进程将进入无限期的等待中。为此Linux提供了功能更强大的sigsuspend以满足这种需求。sigsuspend的工作过程如下: 1)设置进程的信号掩码并阻塞进程。 2)收到信号,恢复原来的信号掩码。 3)调用进程设置的信号处理函数。 4)等待信号处理函数返回后,sigsuspend返回。 上述四个步骤是一次性完成的,操作系统保证操作过程的原子性。特别需要注意的是第三步调用信号处理函数是由sigsuspend完成的。sigsuspend的原型为: #include <signal.h> int sigsuspend(const sigset_t *set); 参数说明: 1)set:输入参数,执行sigsuspend过程中需要被阻塞的信号集。 返回值: sigsuspend的返回值永远是-1,错误码errno为EINTR。
例8-6:用sigsuspend编程实现等待SIGINT信号到来的功能。 代码如下: #include <stdio.h> #include <signal.h> //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf("receive signal %d ",signo); } void main() { //信号掩码结构变量,用于指定新的信号掩码 sigset_t mask; //安装SIGINT信号 signal(SIGINT,CbSigInt); //设置信号集为所有信号,准备阻塞所有信号 sigfillset(&mask); //从信号集中删除SIGINT信号,该信号为目标信号,不能被阻塞。 sigdelset(&mask,SIGINT); //等待SIGINT信号 sigsuspend(&mask); } 编译运行该程序,在进程suspend期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证该信号是否被处理。 注意:由于此测试程序屏蔽了除SIGINT外的所有其他信号,因此只有在收到SIGINT信号后进程才会退出。
提示: 一个阻塞式系统调用在执行过程中如果没有符合条件的数据,将进入休眠状态,直到有符合条件的数据到来。比较典型的例子是从网络连接上读取数据,如果没有数据到来,那么这个读操作将会阻塞。此时有两种情况可以中断该读操作的执行:一是网络上有数据到来,则读操作将获取到所需要的数据后返回;二是当前进程收到了某个信号,此时,读操作将被中断并返回失败,错误码errno为EINTR。
8.3.6 信号处理函数的实现 信号处理函数是进程接收到信号后要执行的函数,该函数应该尽量简洁,一般不要执行过多的代码。最好只是改变一个外部标志变量的值,而在另外的程序中不断的检测该变量,繁杂的工作都留给那些程序去做。在定义信号处理函数时,应该特别注意以下几点。 1)如果信号处理程序中需要存取某个全局变量,则应该在程序中使用关键字volatile声明此变量。通知编译器,在编译过程中不要对该变量进行优化。 2)如果在信号处理函数中调用某个函数,那么那么该函数必须是可重入的,或者保证在信号处理函数执行期间不会有信号到达进程。Linux系统下存在许多不可重入的函数,如malloc、gethostbyname等。 在信号处理函数里,有时需要用到长跳转的操作。所谓长跳转,就是从信号处理函数直接跳转到函数体外指定的代码位置继续运行。Linux系统提供了两个函数实现该功能:设置跳转点的sigsetjmp和执行跳转的siglongjmp。sigsetjmp用来设置跳转点,在成功调用后,sigsetjmp语句所在的位置就是跳转点,这个位置指针将被保存到sigsetjmp的第一个参数中。这个两个函数的原型为: #include <setjmp.h> int sigsetjmp (struct __jmp_buf_tag env[1], int savemask); void siglongjmp(sigjmp_buf env,int val); 参数说明: 1)env[1]:输出参数,该参数实际上是一个结构体的指针。该结构体中包含了长跳转指针,是否保存信号掩码及保存的信号掩码值等信息。对于应用人员来说,该结构是透明的。 2)env:输入参数,等效于env[1]。 3)savemask:是否保存信号掩码。如果该参数非零,则在调用sigsetjmp后,当前进程的信号掩码将被保存;在调用siglongjmp时,将恢复由sigsetjmp保存的信号掩码。 4)val:当由siglongjmp调用sigsetjmp时,该参数将会被隐含传给sigsetjmp作为返回值。如果val等于0,那么sigsetjmp函数将忽略该参数而返回其他非零值。 返回值: 1)sigsetjmp函数:若返回0,表明sigsetjmp不是由siglongjmp调用的;若返回非零值,则是由siglongjmp调用而返回。
例8-7:编程实现捕捉SIGINT信号,在信号处理函数中用长跳转跳转至主程序。 代码如下: #include <stdio.h> #include <signal.h> #include <setjmp.h> //全局变量,用于保存跳转点及其现场 static sigjmp_buf jmpbuff; //SIGINT信号处理函数 void CbSigInt(int signo) { //输出信号的值 printf(" receive signal %d ",signo); //长跳转到jmpbuff(即sigsetjmp函数入口处),并平衡堆栈 siglongjmp(jmpbuff,88); } void main() { int res; //安装SIGINT信号 signal(SIGINT,CbSigInt); //设置跳转点 res=sigsetjmp(jmpbuff,1); //第一次调用sigsetjmp时将返回0 if(res==0) printf("First call sigsetjmp! "); //从信号处理函数中跳转过来时,sigsetjmp将返回非零值 else { //输出提示信息后退出进程 printf("res=%d ",res); printf("sigsetjmp is called by siglongjmp! "); return; } //暂停执行等待信号 pause(); }
编译运行该程序,在进程pause期间,按下<Ctrl>+<C>键向进程发送SIGINT信号,验证信号处理函数是否跳转到指定的位置。
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: Qt中创建excel文件
- 下一篇: h5中的一些配置