Linux信号基本使用

当进程接收到内核或用户发送过来的信号之后,根据具体信号可以采取不同的处理方式:忽略信号、捕获信号或者执行系统默认操作。Linux 系统提供了系统调用 signal()和 sigaction()两个函数用于设置信号的处理方式。

概述

信号的目的是用来通信的

一个具有合适权限的进程能够向另一个进程发送信号,信号的这一用法可作为一种同步技术,甚至是进程间通信(IPC)的原始形式。信号可以由“谁”发出呢?以下列举的很多情况均可以产生信号:

⚫ 硬件发生异常,即硬件检测到错误条件并通知内核,随即再由内核发送相应的信号给相关进程。硬件检测到异常的例子包括执行一条异常的机器语言指令,诸如,除数为 0、数组访问越界导致引用了无法访问的内存区域等,这些异常情况都会被硬件检测到,并通知内核、然后内核为该异常情况发生时正在运行的进程发送适当的信号以通知进程。

⚫ 发生了软件事件,即当检测到某种软件条件已经发生。这里指的不是硬件产生的条件(如除数为 0、引用无法访问的内存区域等),而是软件的触发条件、触发了某种软件条件(进程所设置的定时器已经超时、进程执行的 CPU 时间超限、进程的某个子进程退出等等情况)。

⚫ 进程调用 kill()系统调用可将任意信号发送给另一个进程或进程组。当然对此是有所限制的,接收信号的进程和发送信号的进程的所有者必须相同,亦或者发送信号的进程的所有者是 root 超级用户。

⚫ 用户可以通过 kill 命令将信号发送给其它进程。kill 命令想必大家都会使用,通常我们会通过 kill命令来“杀死”(终止)一个进程,譬如在终端下执行"kill -9 xxx"来杀死 PID 为 xxx 的进程。kill命令其内部的实现原理便是通过 kill()系统调用来完成的。

⚫ 用户在终端下输入了能够产生信号的特殊字符。譬如在终端上按下 CTRL + C 组合按键可以产生中断信号(SIGINT),通过这个方法可以终止在前台运行的进程;按下 CTRL + Z 组合按键可以产生暂停信号(SIGCONT),通过这个方法可以暂停当前前台运行的进程。

进程同样也可以向自身发送信号,然而发送给进程的诸多信号中,大多数都是来自于内核。

以上便是可以产生信号的多种不同的条件,总的来看,信号的目的都是用于通信的,当发生某种情况下,通过信号将情况“告知”相应的进程,从而达到同步、通信的目的。

前两种情况是被动触发的,第三种情况是程序里主动发送的,后两种情况是用户执行的。

信号由谁处理、怎么处理

信号通常是发送给对应的进程,当信号到达后,该进程需要做出相应的处理措施,通常进程会视具体信号执行以下操作之一:

⚫ 忽略信号。也就是说,当信号到达进程后,该进程并不会去理会它、直接忽略,就好像是没有出该信号,信号对该进程不会产生任何影响。事实上,大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略,它们是 SIGKILL 和 SIGSTOP,这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号,则进程的运行行为是未定义的。

⚫ 捕获信号。当信号到达进程后,执行预先绑定好的信号处理函数。为了做到这一点,要通知内核在某种信号发生时,执行用户自定义的处理函数,该处理函数中将会对该信号事件作出相应的处理,Linux 系统提供了 signal()系统调用可用于注册信号的处理函数,将会在后面向大家介绍。

⚫ 执行系统默认操作。进程不对该信号事件作出处理,而是交由系统进行处理,每一种信号都会有其对应的系统默认的处理方式。需要注意的是,对大多数信号来说,系统默认的处理方式就是终止该进程。

信号是异步的

信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的,进程无法预测该事件产生的准确时间,进程不能够通过简单地测试一个变量或使用系统调用来判断是否产生了一个信号,这就如同硬件中断事件,程序是无法得知中断事件产生的具体时间,只有当产生中断事件时,才会告知程序、然后打断当前程序的正常执行流程、跳转去执行中断服务函数,这就是异步处理方式。

信号本质上是 int 类型数字编号

信号本质上是 int 类型的数字编号,这就好比硬件中断所对应的中断号。内核针对每个信号,都给其定义了一个唯一的整数编号,从数字 1 开始顺序展开。并且每一个信号都有其对应的名字(其实就是一个宏),信号名字与信号编号乃是一一对应关系,但是由于每个信号的实际编号随着系统的不同可能会不一样,所以在程序当中一般都使用信号的符号名(也就是宏定义)。

注意:

信号处理器程序(也称为信号捕捉器)是当指定信号传递给进程时将会调用的一个函数。

调用信号处理器程序,可能会随时打断主程序流程。内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。

可见,信号与硬件中断的相似之处在于能够打断程序当前执行的正常流程,其实是在软件层次上对中断机制的一种模拟。

当进程接收到内核或用户发送过来的信号之后,根据具体信号可以采取不同的处理方式:忽略信号、捕获信号或者执行系统默认操作。Linux 系统提供了系统调用 signal()和 sigaction()两个函数用于设置信号的处理方式。

signal()函数

本节描述系统调用 signal(),signal()函数是 Linux 系统下设置信号处理方式最简单的接口,可将信号的处理方式设置为捕获信号、忽略信号以及系统默认操作,此函数原型如下所示:

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

使用该函数需要包含头文件<signal.h>。

函数参数和返回值含义如下:

signum:此参数指定需要进行设置的信号,可使用信号名(宏)或信号的数字编号,建议使用信号名。

handler:sig_t 类型的函数指针,指向信号对应的信号处理函数,当进程接收到信号后会自动执行该处理函数;参数 handler 既可以设置为用户自定义的函数,也就是捕获信号时需要执行的处理函数,也可以设置为 SIG_IGN 或 SIG_DFL,SIG_IGN 表示此进程需要忽略该信号,SIG_DFL 则表示设置为系统默认操作。

sig_t 函数指针的 int 类型参数指的是,当前触发该函数的信号,可将多个信号绑定到同一个信号处理函数上,此时就可通过此参数来判断当前触发的是哪个信号。

Tips:SIG_IGN、SIG_DFL 分别取值如下:

/* Fake signal functions. */
#define SIG_ERR ((sig_t) -1)/* Error return. */
#define SIG_DFL ((sig_t) 0)/* Default action. */
#define SIG_IGN ((sig_t) 1)/* Ignore signal. */

返回值:此函数的返回值也是一个 sig_t 类型的函数指针,成功情况下的返回值则是指向在此之前的信号处理函数;如果出错则返回 SIG_ERR,并会设置 errno。

由此可知,signal()函数可以根据第二个参数 handler 的不同设置情况,可对信号进行不同的处理。

测试

signal()函数的用法其实非常简单,为信号设置相应的处理方式,接下来编写一个简单地示例代码对signal()函数进行测试。

示例代码 8.4.1 signal()函数使用示例 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
static void sig_handler(int sig) 
{ 
     printf("Received signal: %d\n", sig); 
} 
int main(int argc, char *argv[]) 
{ 
     sig_t ret = NULL; 
     ret = signal(SIGINT, (sig_t)sig_handler); 
     if (SIG_ERR == ret) {
         perror("signal error"); 
         exit(-1); 
     } 
     /* 死循环 */ 
     for ( ; ; ) { } 
     exit(0); 
}

在上述示例代码中,我们通过 signal()函数将 SIGINT(2)信号绑定到了一个用户自定的处理函数上sig_handler(int sig),当进程收到 SIGINT 信号后会执行该函数然后运行 printf 打印语句。

sigaction()函数

除了signal()之外,sigaction()系统调用是设置信号处理方式的另一选择,事实上,推荐大家使用sigaction()函数。虽然 signal()函数简单好用,而 sigaction()更为复杂,但作为回报,sigaction()也更具灵活性以及移植性。

sigaction()允许单独获取信号的处理函数而不是设置,并且还可以设置各种属性对调用信号处理函数时的行为施以更加精准的控制,其函数原型如下所示:

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

使用该函数需要包含头文件<signal.h>。

函数参数和返回值含义如下:

signum:需要设置的信号,除了 SIGKILL 信号和 SIGSTOP 信号之外的任何信号。

act:act 参数是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构,该数据结构描述了信号的处理方式,稍后介绍该数据结构;如果参数 act 不为 NULL,则表示需要为信号设置新的处理方式;如果参数 act 为 NULL,则表示无需改变信号当前的处理方式。

oldact:oldact 参数也是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构。如果参数oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来;如果无意获取此类信息,那么可将该参数设置为 NULL。

返回值:成功返回 0;失败将返回-1,并设置 errno。

struct sigaction 结构体

示例代码 8.4.2 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);
};

结构体成员介绍:

⚫ sa_handler:指定信号处理函数,与 signal()函数的 handler 参数相同。

⚫ sa_sigaction:也用于指定信号处理函数,这是一个替代的信号处理函数,他提供了更多的参数,可以通过该函数获取到更多信息,这些信号通过 siginfo_t 参数获取,稍后介绍该数据结构;sa_handler 和sa_sigaction 是互斥的,不能同时设置,对于标准信号来说,使用 sa_handler 就可以了,可通过SA_SIGINFO 标志进行选择。

⚫ sa_mask:参数 sa_mask 定义了一组信号,当进程在执行由 sa_handler 所定义的信号处理函数之前,会先将这组信号添加到进程的信号掩码字段中,当进程执行完处理函数之后再恢复信号掩码,将这组信号从信号掩码字段中删除。当进程在执行信号处理函数期间,可能又收到了同样的信号或其它信号,从而打断当前信号处理函数的执行,这就好点像中断嵌套;通常我们在执行信号处理函数期间不希望被另一个信号所打断,那么怎么做呢?那么就是通过信号掩码来实现,如果进程接收到了信号掩码中的这些信号,那么这个信号将会被阻塞暂时不能得到处理,直到这些信号从进程的信号掩码中移除。在信号处理函数调用时,进程会自动将当前处理的信号添加到信号掩码字段中,这样保证了在处理一个给定的信号时,如果此信号再次发生,那么它将会被阻塞。如果用户还需要在阻塞其它的信号,则可以通过设置参数 sa_mask 来完成(此参数是sigset_t 类型变量,即信号集),信号掩码可以避免一些信号之间的竞争状态(也称为竞态)。

⚫ sa_restorer:该成员已过时,不要再使用了。

⚫ sa_flags:参数 sa_flags 指定了一组标志,这些标志用于控制信号的处理过程,可设置为如下这些标志(多个标志使用位或" | "组合):

SA_NOCLDSTOP

如果signum为SIGCHLD,则子进程停止时(即当它们接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU中的一种时)或恢复(即它们接收到 SIGCONT)时不会收到 SIGCHLD 信号。

SA_NOCLDWAIT

如果 signum 是 SIGCHLD,则在子进程终止时不要将其转变为僵尸进程。

SA_NODEFER

不要阻塞从某个信号自身的信号处理函数中接收此信号。也就是说当进程此时正在执行某个信号的处理函数,默认情况下,进程会自动将该信号添加到进程的信号掩码字段中,从而在执行信号处理函数期间阻塞该信号,默认情况下,我们期望进程在处理一个信号时阻塞同种信号,否则引起一些竞态条件;如果设置了 SA_NODEFER 标志,则表示不对它进行阻塞。

SA_RESETHAND

执行完信号处理函数之后,将信号的处理方式设置为系统默认操作。

SA_RESTART

被信号中断的系统调用,在信号处理完成之后将自动重新发起。

SA_SIGINFO

如果设置了该标志,则表示使用 sa_sigaction 作为信号处理函数、而不是 sa_handler,关于 sa_sigaction信号处理函数的参数信息。

以上就是关于 struct sigaction 结构体相关的内容介绍了,接下编写程序进行实战测试。

例程:

示例代码 8.4.4 sigaction()函数使用示例 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
static void sig_handler(int sig) 
{ 
     printf("Received signal: %d\n", sig); 
} 
int main(int argc, char *argv[]) 
{ 
     struct sigaction sig = {0}; 
     int ret; 
     sig.sa_handler = sig_handler; 
     sig.sa_flags = 0; 
     ret = sigaction(SIGINT, &sig, NULL); 
     if (-1 == ret) { 
         perror("sigaction error"); 
         exit(-1); 
     } 
     /* 死循环 */ 
     for ( ; ; ) { } 
     exit(0); 
} 

运行结果

关于信号处理函数说明

一般而言,将信号处理函数设计越简单越好,这就好比中断处理函数,越快越好,不要在处理函数中做大量消耗 CPU 时间的事情,这一个重要的原因在于,设计的越简单这将降低引发信号竞争条件的风险。

补充说明:

signal() 的行为在不同Unix实现间存在差异,这也意味着对可移植性有所追求的程序绝不能使用此调用来建立信号处理函数。故此,sigaction() 是建立信号处理器的首选API(强力推荐)。

向进程发送信号

与 kill 命令相类似,Linux 系统提供了 kill()系统调用,一个进程可通过 kill()向另一个进程发送信号;除了 kill()系统调用之外,Linux 系统还提供了系统调用 killpg()以及库函数 raise(),也可用于实现发送信号的功能,本小节将向大家进行介绍。

kill函数 

kill()系统调用可将信号发送给指定的进程或进程组中的每一个进程,其函数原型如下所示:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

使用该函数需要包含头文件<sys/types.h>和<signal.h>。

函数参数和返回值含义如下:

pid:参数 pid 为正数的情况下,用于指定接收此信号的进程 pid;除此之外,参数 pid 也可设置为 0 或-1 以及小于-1 等不同值,稍后给说明。

sig:参数 sig 指定需要发送的信号,也可设置为 0,如果参数 sig 设置为 0 则表示不发送信号,但任执行错误检查,这通常可用于检查参数 pid 指定的进程是否存在。

返回值:成功返回 0;失败将返回-1,并设置 errno。

参数 pid 不同取值含义:

⚫ 如果 pid 为正,则信号 sig 将发送到 pid 指定的进程。

⚫ 如果 pid 等于 0,则将 sig 发送到当前进程的进程组中的每个进程。

⚫ 如果 pid 等于-1,则将 sig 发送到当前进程有权发送信号的每个进程,但进程 1(init)除外。

⚫ 如果 pid 小于-1,则将 sig 发送到 ID 为-pid 的进程组中的每个进程。

进程中将信号发送给另一个进程是需要权限的,并不是可以随便给任何一个进程发送信号,超级用户root 进程可以将信号发送给任何进程,但对于非超级用户(普通用户)进程来说,其基本规则是发送者进程的实际用户 ID 或有效用户 ID 必须等于接收者进程的实际用户 ID 或有效用户 ID。

从上面介绍可知,当 sig 为 0 时,仍可进行正常执行的错误检查,但不会发送信号,这通常可用于确定一个特定的进程是否存在,如果向一个不存在的进程发送信号,kill()将会返回-1,errno 将被设置为 ESRCH,表示进程不存在。

raise函数

有时进程需要向自身发送信号,raise()函数可用于实现这一要求,raise()函数原型如下所示(此函数为 C 库函数):

#include <signal.h>
int raise(int sig);

使用该函数需要包含头文件<signal.h>。

函数参数和返回值含义如下:

sig:需要发送的信号。

返回值:成功返回 0;失败将返回非零值。

raise()其实等价于:

kill(getpid(), sig);

Tips:getpid()函数用于获取进程自身的 pid。

alarm()和 pause()函数

alarm()函数

使用 alarm()函数可以设置一个定时器(闹钟),当定时器定时时间到时,内核会向进程发送 SIGALRM信号,其函数原型如下所示:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

函数参数和返回值:

seconds:设置定时时间,以秒为单位;如果参数 seconds 等于 0,则表示取消之前设置的 alarm 闹钟。

返回值:如果在调用 alarm()时,之前已经为该进程设置了 alarm 闹钟还没有超时,则该闹钟的剩余值作为本次 alarm()函数调用的返回值,之前设置的闹钟则被新的替代;否则返回 0。

参数 seconds 的值是产生 SIGALRM 信号需要经过的时钟秒数,当这一刻到达时,由内核产生该信号,每个进程只能设置一个 alarm 闹钟;虽然 SIGALRM 信号的系统默认操作是终止进程,但是如果程序当中设置了 alarm 闹钟,但大多数使用闹钟的进程都会捕获此信号。

需要注意的是 alarm 闹钟并不能循环触发,只能触发一次,若想要实现循环触发,可以在 SIGALRM 信号处理函数中再次调用 alarm()函数设置定时器。

pause()函数

pause()系统调用可以使得进程暂停运行、进入休眠状态,直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause()才返回,在这种情况下,pause()返回-1,并且将 errno 设置为 EINTR。其函数原型如下所示:

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

信号集

通常我们需要有一个能表示多个信号(一组信号)的数据类型---信号集(signalset),很多系统调用都使用到了信号集这种数据类型来作为参数传递,譬如 sigaction()函数等。本小节向大家介绍信号集这个数据类型。

信号集其实就是 sigset_t 类型数据结构,来看看:

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

使用这个结构体可以表示一组信号,将多个信号添加到该数据结构中,当然 Linux 系统也提供了用于操作sigset_t 信号集的 API,譬如 sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember(),接下来向大家介绍。

初始化信号集

sigemptyset()和 sigfillset()用于初始化信号集。sigemptyset()初始化信号集,使其不包含任何信号;而sigfillset()函数初始化信号集,使其包含所有信号(包括所有实时信号),函数原型如下:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

使用这些函数需要包含头文件<signal.h>。

函数参数和返回值含义如下:

set:指向需要进行初始化的信号集变量。

返回值:成功返回 0;失败将返回-1,并设置 errno。

使用示例

初始化为空信号集:

sigset_t sig_set;
sigemptyset(&sig_set);

初始化信号集,使其包含所有信号:

sigset_t sig_set;
sigfillset(&sig_set);

向信号集中添加/删除信号

分别使用 sigaddset()和 sigdelset()函数向信号集中添加或移除一个信号,函数原型如下:

#include <signal.h>
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

函数参数和返回值含义如下:

set:指向信号集。

signum:需要添加/删除的信号。

返回值:成功返回 0;失败将返回-1,并设置 errno。

使用示例

向信号集中添加信号:

sigset_t sig_set;
sigemptyset(&sig_set);
sigaddset(&sig_set, SIGINT);

从信号集中移除信号:

sigset_t sig_set;
sigfillset(&sig_set);
sigdelset(&sig_set, SIGINT);

测试信号是否在信号集中

使用 sigismember()函数可以测试某一个信号是否在指定的信号集中,函数原型如下所示:

#include <signal.h>
int sigismember(const sigset_t *set, int signum);

函数参数和返回值含义如下:

set:指定信号集。

signum:需要进行测试的信号。

返回值:如果信号 signum 在信号集 set 中,则返回 1;如果不在信号集 set 中,则返回 0;失败则返回-1,并设置 errno。

使用示例

判断 SIGINT 信号是否在 sig_set 信号集中:

sigset_t sig_set;
......
if (1 == sigismember(&sig_set, SIGINT))
    puts("信号集中包含 SIGINT 信号");
else if (!sigismember(&sig_set, SIGINT))
    puts("信号集中不包含 SIGINT 信号");

信号掩码

内核为每一个进程维护了一个信号掩码(其实就是一个信号集),即一组信号。当进程接收到一个属于信号掩码中定义的信号时,该信号将会被阻塞、无法传递给进程进行处理,那么内核会将其阻塞,直到该信号从信号掩码中移除,内核才会把该信号传递给进程从而得到处理。 有点像关小黑屋。

向信号掩码中添加一个信号,通常有如下几种方式:

⚫ 当应用程序调用 signal()或 sigaction()函数为某一个信号设置处理方式时,进程会自动将该信号添加到信号掩码中,这样保证了在处理一个给定的信号时,如果此信号再次发生,那么它将会被阻塞;当然对于 sigaction()而言,是否会如此,需要根据 sigaction()函数是否设置了 SA_NODEFER 标志而定;当信号处理函数结束返回后,会自动将该信号从信号掩码中移除。

⚫ 使用 sigaction()函数为信号设置处理方式时,可以额外指定一组信号,当调用信号处理函数时将该组信号自动添加到信号掩码中,当信号处理函数结束返回后,再将这组信号从信号掩码中移除;通过 sa_mask 参数进行设置。

⚫ 除了以上两种方式之外,还可以使用 sigprocmask()系统调用,随时可以显式地向信号掩码中添加/移除信号。

sigprocmask()函数原型如下所示:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

使用该函数需要包含头文件<signal.h>。

函数参数和返回值含义如下:

how:参数 how 指定了调用函数时的一些行为。

set:将参数 set 指向的信号集内的所有信号添加到信号掩码中或者从信号掩码中移除;如果参数 set 为NULL,则表示无需对当前信号掩码作出改动。

oldset:如果参数 oldset 不为 NULL,在向信号掩码中添加新的信号之前,获取到进程当前的信号掩码,存放在 oldset 所指定的信号集中;如果为 NULL 则表示不获取当前的信号掩码。

返回值:成功返回 0;失败将返回-1,并设置 errno。

参数 how 可以设置为以下宏:

⚫ SIG_BLOCK:将参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中。换言之,将信号掩码设置为当前值与 set 的并集。

⚫ SIG_UNBLOCK:将参数 set 指向的信号集内的所有信号从进程信号掩码中移除。

⚫ SIG_SETMASK:进程信号掩码直接设置为参数 set 指向的信号集。

使用示例

将信号 SIGINT 添加到进程的信号掩码中:

int ret;

/* 定义信号集 */
sigset_t sig_set;

/* 将信号集初始化为空 */
sigemptyset(&sig_set);

/* 向信号集中添加 SIGINT 信号 */
sigaddset(&sig_set, SIGINT);

/* 向进程的信号掩码中添加信号 */
ret = sigprocmask(SIG_BLOCK, &sig_set, NULL);

if (-1 == ret) {
    perror("sigprocmask error");
    exit(-1);
}

从信号掩码中移除 SIGINT 信号:

int ret;

/* 定义信号集 */
sigset_t sig_set;

/* 将信号集初始化为空 */
sigemptyset(&sig_set);

/* 向信号集中添加 SIGINT 信号 */
sigaddset(&sig_set, SIGINT);

/* 从信号掩码中移除信号 */
ret = sigprocmask(SIG_UNBLOCK, &sig_set, NULL);

if (-1 == ret) {
    perror("sigprocmask error");
    exit(-1);
}

下面我们编写一个简单的测试代码,验证信号掩码的作用,测试代码如下所示:

示例代码 8.9.1 测试信号掩码的作用 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <unistd.h> 
static void sig_handler(int sig)
{ 
     printf("执行信号处理函数...\n"); 
} 
int main(void) 
{ 
     struct sigaction sig = {0}; 
     sigset_t sig_set; 
     /* 注册信号处理函数 */ 
     sig.sa_handler = sig_handler; 
     sig.sa_flags = 0; 
     if (-1 == sigaction(SIGINT, &sig, NULL)) 
         exit(-1); 
     /* 信号集初始化 */ 
     sigemptyset(&sig_set); 
     sigaddset(&sig_set, SIGINT); 
     /* 向信号掩码中添加信号 */ 
     if (-1 == sigprocmask(SIG_BLOCK, &sig_set, NULL)) 
         exit(-1); 
     /* 向自己发送信号 */ 
     raise(SIGINT); 
     /* 休眠 2 秒 */ 
     sleep(2); 
     printf("休眠结束\n"); 
     /* 从信号掩码中移除添加的信号 */ 
     if (-1 == sigprocmask(SIG_UNBLOCK, &sig_set, NULL)) 
     exit(-1); 
     exit(0); 
} 

上述代码中,我们为 SIGINT 信号注册了一个处理函数 sig_handler,当进程接收到该信号之后就会执行它;然后调用 sigprocmask 函数将 SIGINT 信号添加到信号掩码中,然后再调用 raise(SIGINT)向自己发送一个 SIGINT 信号,如果信号掩码没有生效、也就意味着 SIGINT 信号不会被阻塞,那么调用 raise(SIGINT)之后应该就会立马执行 sig_handler 函数,从而打印出"执行信号处理函数..."字符串信息;如果设置的信号掩码生效了,则并不会立马执行信号处理函数,而是在 2 秒后才执行,因为程序中使用 sleep(2)休眠了 2 秒钟之后,才将 SIGINT 信号从信号掩码中移除,故而进程才会处理该信号,在移除之前接收到该信号会将其阻塞。

编译测试结果如下:

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值