调用 sigprocmask 函数可以检测或更改进程的信号屏蔽字
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
定义于 signal.h 中,调用成功返回0,否则返回-1
该函数的参数和返回值有如下几种情况
- 若 oset 为非空指针,则当前进程的信号屏蔽字会通过 oset 返回
- 若 set 为非空指针,则参数 how 指示如何修改当前信号屏蔽字
- 若 set 为空指针,则不改变该进程的信号屏蔽字,how参数也没有意义
下表中是 how 参数的意义
sigprocmask 用法
how | 说明 | 用途 |
SIG_BLOCK | 该进程的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集 | 用来附加阻塞信号 |
SIG_UNBLOCK | 该进程的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的交集 | 用来解除阻塞 |
SIG_SETMASK | 该进程的信号屏蔽字设置为 set 指向信号集的值 | 设置阻塞信号 |
在调用 sigprocmask 函数后对于未决的、不在阻塞的信号,都会在 sigprocmask 函数返回前递送至少一个信号给进程
- 注意:sigprocmask 是仅为单线程的进程定义的,为处理多线程的进程中信号的屏蔽有另一个单独的函数,这个函数我们后面介绍
实例(1)
下面的例子中,使用sigprocmask 向进程信号屏蔽字中加入了 SIGINT 信号,因此在程序执行过程中无法使用 CTRL+C 中断程序的执行
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <malloc.h>
int main()
{
sigset_t *set = (sigset_t*)malloc(sizeof(set));
sigemptyset(set);
sigaddset(set, SIGINT);
int ret = sigprocmask(SIG_BLOCK, set, NULL);
printf("ret: %d\n", ret);
sleep(15);
return 0;
}
实例(2)
#include <stdio.h>
#include <signal.h>
#include <errno.h>
int main ()
{
sigset_t sigset, osigset;
int errno_save;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
errno_save = errno;
if (sigprocmask(SIG_BLOCK, &sigset, NULL) != 0)
printf("set sigprocmask error\n");
if (sigprocmask(0, NULL, &osigset) != 0)
printf("sigprocmask error\n");
printf("sig_mask:\n");
if (sigismember(&osigset, SIGINT))
printf("SIGINT ");
if (sigismember(&osigset, SIGQUIT))
printf("SIGQUIT ");
if (sigismember(&osigset, SIGUSR1))
printf("SIGUSR1 ");
if (sigismember(&osigset, SIGALRM))
printf("SIGALRM ");
printf("\n");
errno = errno_save;
return 0;
}
需要注意的是,如果同时提供了第二个参数和第三个参数,sigprocmask 函数会首先返回当前信号屏蔽字到第三个参数,然后根据第一个和第二个参数设置新的信号屏蔽字
sigpending 返回未决信号集
int sigpending(sigset_t *set);
调用成功则返回0,信号集由 set 参数返回,出错返回 -1
示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static void sig_quit(int);
int main ()
{
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
{
printf("catnot catch SIGQUIT\n");
exit(-1);
}
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
{
printf("SIG_BLOCK error\n");
exit(-1);
}
sleep(5);
if (sigpending(&pendmask) < 0)
{
printf("sigpending error\n");
exit(-1);
}
if (sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending\n");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
printf("SIG_SETMASK error\n");
exit(-1);
}
printf("SIGQUIT unblocked\n");
sleep(5);
return 0;
}
static void sig_quit (int signo)
{
printf("caught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
printf("cannot reset SIGQUIT");
exit(-1);
}
}
执行结果如下图所示:

由于在信号被阻塞期间,内核向进程递送了信号,所以 SIGQUIT 信号存在于 sigpending 返回的未决信号集中,即解除阻塞后信号会被递送到进程
图中可以看到,信号并没有被排队递送而是只递送了一次

由于信号阻塞期间没有产生过 SIGQUIT 信号,所以在未决信号集中,也就不包含该信号了
sigaction 函数的功能是检查或修改与指定信号相关联的处理动作(或同时执行这两种操作)
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
定义于 signal.h 中
调用成功返回0,否则返回-1
参数 signo 是要检测或修改的具体动作的信号编号,若 act 指针为非空,则要修改其动作,若 oact 指针为非空,则系统经由 oact 指针返回该信号的上一动作
sigaction 结构体结构如下:
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags
void (*sa_sigaction) (int, siginfo_t*, void*);
}
当执行信号动作时,如果 sa_handler 字段包含一个信号捕捉函数的地址,则 sa_mask 字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中
从信号捕捉函数返回时,进程的信号屏蔽字会被复位为原先值
这样就保证了在处理一个给定的信号时,如果信号再次发生则会先阻塞以完成前一个信号的处理
sa_flags 字段指定对信号进行处理的各个选项,如下表:
sigaction 选项说明
选项 | 说明 |
SA_INTERRUPT | (默认处理方式)此信号中断的系统调用不会自动重启动 |
SA_NOCLDSTOP | 若 signo 是 SIGCHLD,当子进程停止或由停止继续运行时(作业控制),不产生此信号,只有当子进程终止时才产生此信号 |
SA_NOCLDWAIT | 若 signo 是 SIGCHLD,则当调用进程的子进程终止时,不创建僵死进程,若调用进程调用了 wait,则调用进程阻塞直至所有子进程都终止,此时wait返回-1,并将errno设置为 ECHILD |
SA_NODEFER | 当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号,除非sa_mask包括了此信号,注意,此种类型的操作对应于早期不可靠信号 |
SA_ONSTACK | 若用 sigaltstack 声明了一个替换栈,则将信号地送给替换栈上的进程 |
SA_RESETHAND | 在信号捕捉函数的入口处,将此信号处理方式复位为 SIG_DFL,并清除 SA_SIGINFO 标志,注意,次类型的信号对应于早期不可靠信号,但是,不能自动复位 SIGKILL 和 SIGTRAP 这两个信号的配置 |
SA_RESTART | 由此信号中断的系统调用会自动重启 |
SA_SIGINFO | 此选项对信号提供了附加信息:一个指向 siginfo 结构的指针以及一个指向进程上下文标识符的指针 |
sigaction 结构中有两个信号处理函数字段,但是应用程序一次只能使用这两个中的一个,因为系统可能使用同一存储区去实现这两者
通常使用:
void handler (int signo);
但是如果设置了 SA_SIGINFO 标志,则使用下列方式处理信号:
void handler (int signo, siginfo_t *info, void *context);
siginfo_t 结构包含了信号产生原因的相关信息,POSIX 规定至少需要包含 si_signo 和 si_code 成员,XSI 扩展要求至少实现下列字段:
struct siginfo
{
int si_signo; /* signal number */
int si_errno; /* if nonzero, errno value from <errno.h> */
int si_code; /* additional info (depends on signal) */
pid_t si_pid; /* sending process ID */
uid_t si_uid; /* sending process real user ID */
void *si_addr; /* address that caused the fault */
int si_status; /* exit value or signal number */
long si_band; /* band number for SIGPOLL */
}
si_code 如下表所示:

示例 -- 用 sigaction 实现 signal 函数
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
}
对除 SIGALRM 信号外的所有信号,我们都尝试设置 SA_RESTART 标志,是为了让这些信号中断的系统调用能重新启动
而不希望重启动由 SIGALRM 信号中断的系统调用的原因是,我们可能希望通过 SIGALRM 信号对 IO 操作设置时间限制
之前我们看到,调用 setjmp 和 longjmp 函数在信号处理程序中是非常有用的,用来直接返回到程序的主循环中,而不是从信号处理程序正常返回
但是,当进入信号捕捉函数时,此时当前I型您好被自动地加到进程的信号屏蔽字中,如果信号处理程序没有正常返回,则进程将一直屏蔽该信号的产生
因此,POSIX 提供了 sigsetjmp 和 siglongjmp 两个函数
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
定义于 setjmp.h 中
第一个函数如果直接调用返回0,若从 siglongjmp 调用返回则返回非0
这两个函数与 setjmp 和 longjmp 函数相比,sigsetjmp 增加了一个参数 -- savemask
如果 savemask 非0,则sigsetjmp 在 env 中保存进程的当前信号屏蔽字,调用 siglongjmp 时,如果带非0的 savemask 的 sigsetjmp 调用已经保存了 env,则 siglongjmp 从其中恢复保存的信号屏蔽字
示例
下例展示了两个函数的具体用法:
#include "../apue.2e/include/apue.h"
#include <setjmp.h>
#include <time.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
int main ()
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
err_sys("signal(SIGUSR1) error\n");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error\n");
printf("starting main\n");
if (sigsetjmp(jmpbuf, 1))
{
printf("ending main\n");
return 0;
}
canjump = 1;
while (1)
pause();
}
static void sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return;
printf("starting sig_usr1\n");
alarm(3);
starttime = time(0);
while (1)
{
if (time(0) > starttime + 5)
break;
}
printf("finishing sig_usr1\n");
canjump = 0;
siglongjmp(jmpbuf, 1);
}
static void sig_alrm(int signo)
{
printf("in sig_alrm\n");
}
执行结果如下图:

书中叙述,如果将sigsetjmp和siglongjmp分别换成_setjmp和_longjmp,则结果的最后一行输出会变成ending main: SIGUSR1
事实上,无论是换成_setjmp 和 _longjmp 还是 setjmp 和 longjmp 结果都是一样的
是因为在信号处理函数执行过程中并不屏蔽当前信号吗?
将程序改成下面这样:
#include "../apue.2e/include/apue.h"
#include <signal.h>
#include <setjmp.h>
#include <time.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
int main ()
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
printf("signal(SIGUSR1) error\n");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
printf("signal(SIGALRM) error\n");
printf("starting main\n");
if (sigsetjmp(jmpbuf, 1))
{
printf("ending main\n");
return 0;
}
canjump = 1;
while (1)
pause();
}
static void sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return;
printf("starting sig_usr1\n");
alarm(3);
starttime = time(0);
while (1)
{
if (time(0) > starttime + 5)
break;
}
printf("finishing sig_usr1\n");
canjump = 0;
siglongjmp(jmpbuf, 1);
}
static void sig_alrm(int signo)
{
int ret;
printf("starting sig_alrm\n");
sigset_t oset;
ret = sigemptyset(&oset);
if (ret != 0)
{
printf("init sigset error\n");
return;
}
if ((ret = sigprocmask(0, NULL, &oset)) != 0)
{
printf("sigprocmask error\n");
return;
}
printf("sigismember SIGUSR1 ? %d\n", sigismember(&oset, SIGUSR1));
printf("sigismember SIGALRM ? %d\n", sigismember(&oset, SIGALRM));
printf("finishing sig_alrm\n");
}
执行结果如下图:

可见信号处理函数在信号处理过程中并不屏蔽该信号,如果在信号处理函数中,该信号再次产生,则可能导致程序陷入死循环
但是如果将signal函数换成 sigaction 函数,则信号在信号处理函数中确实会被屏蔽,因此在信号处理中,应使用sigaction函数,而不是使用signal函数
上面的程序演示了另一种技术,仅在调用 sigsetjmp 之后才将变量 canjump 设置为非0 值,在信号处理程序中检查该变量,仅当他为非0值时,才调用 siglongjmp,这样,可以保证在跳转以前 jmpbuf 一定已经被初始化过,如果 jmpbuf 尚未初始化,则不调用信号处理程序
canjmp 的数据类型是 sig_atomic_t,这是由 ISO C 定义的变量类型,保证写这种类型的变量时不会被中断,这种类型的变量总是包括 ISO 修饰符 volatitle,保证每一个操作都不会因编译器优化而被跳过
通过阻塞所选择的信号,可以保护不希望被信号中断的代码临界区,但有时我们希望解除一个信号的阻塞状态,然后pause等待信号的发生
下例解除并等待 SIGINT 信号
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
if (sigprocmask(SIG_SETMASK, &oldmaks, NULL) < 0)
err_sys("SIG_SETMASK error");
pause();
考虑上面的程序,可能出现在解除阻塞后,尚未执行到pause时,此时产生了一个信号,因此当最终执行到pause时,由于需要捕捉的信号已经产生过了,可能不会再次产生,而造成进程的永远挂起
因此,需要原子地先恢复信号屏蔽字,然后使进程休眠
这就是 sigsuspend 函数的功能
sigsuspen 函数
int sigsuspend(const sigset_t *sigmask);
返回-1,并将 errno 设置为 EINTR
将进程的信号屏蔽字设置为 sigmask 指向的值,并挂起进程直至有信号产生
下例展示了如何保护临界区不被信号中断
/*
* author: 龙泉居士
* date: 2014-09-15
* file: main.c
*/
#include "function/function.h"
#include <setjmp.h>
#include <time.h>
int main()
{
sigset_t newmask, oldmask, waitmask;
struct sigaction sa, osa;
sa.sa_flags = 0;
sigemptyset(&(sa.sa_mask));
sa.sa_handler = sig_int;
if (sigaction(SIGINT, &sa, &osa) != 0)
{
printf("sigaction error\n");
return -1;
}
pr_mask("program start: ");
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) != 0)
{
printf("SIG_BLOCK error\n");
return -1;
}
pr_mask("in critical region: ");
if (sigsuspend(&waitmask) != -1)
{
printf("sigsuspend error\n");
return -1;
}
pr_mask("after sigsuspend: ");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)
{
printf("SIG_SETMASK error\n");
return -1;
}
if (sigaction(SIGINT, &osa, NULL) != 0)
{
printf("recover sigaction error\n");
return -1;
}
return 0;
}
/*
* author: 龙泉居士
* date: 2014-09-15
* file: function.h
*/
#ifndef _FUNCTION_H_20140916_
#define _FUNCTION_H_20140916_
#include "../../apue.2e/include/apue.h"
#define true 1
#define false 0
void sig_int(int);
void pr_mask(const char*);
#endif
/*
* author: 龙泉居士
* date: 2014-09-15
* file: function.c
*/
#include "function.h"
#include <stdio.h>
#include <errno.h>
void pr_mask(const char* str)
{
int save_errno = errno;
int signo_array[] =
{
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP,
SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGUSR1,
SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM,
SIGSTKFLT, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP,
SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ,
SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR,
SIGSYS, SIGRTMIN, SIGRTMIN+1, SIGRTMIN+2, SIGRTMIN+3,
SIGRTMIN+4, SIGRTMIN+5, SIGRTMIN+6, SIGRTMIN+7, SIGRTMIN+8,
SIGRTMIN+9, SIGRTMIN+10, SIGRTMIN+11, SIGRTMIN+12, SIGRTMIN+13,
SIGRTMIN+14, SIGRTMIN+15, SIGRTMAX-14, SIGRTMAX-13, SIGRTMAX-12,
SIGRTMAX-11, SIGRTMAX-10, SIGRTMAX-9, SIGRTMAX-8, SIGRTMAX-7,
SIGRTMAX-6, SIGRTMAX-5, SIGRTMAX-4, SIGRTMAX-3, SIGRTMAX-2,
SIGRTMAX-1, SIGRTMAX
};
char *sigstr_array[] =
{
"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP",
"SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1",
"SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM",
"SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
"SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ",
"SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR",
"SIGSYS", "SIGRTMIN", "SIGRTMIN+1", "SIGRTMIN+2", "SIGRTMIN+3",
"SIGRTMIN+4", "SIGRTMIN+5", "SIGRTMIN+6", "SIGRTMIN+7", "SIGRTMIN+8",
"SIGRTMIN+9", "SIGRTMIN+10", "SIGRTMIN+11", "SIGRTMIN+12", "SIGRTMIN+13",
"SIGRTMIN+14", "SIGRTMIN+15", "SIGRTMAX-14", "SIGRTMAX-13", "SIGRTMAX-12",
"SIGRTMAX-11", "SIGRTMAX-10", "SIGRTMAX-9", "SIGRTMAX-8", "SIGRTMAX-7",
"SIGRTMAX-6", "SIGRTMAX-5", "SIGRTMAX-4", "SIGRTMAX-3", "SIGRTMAX-2",
"SIGRTMAX-1", "SIGRTMAX"
};
sigset_t mask;
if (sigprocmask(0, NULL, &mask) != 0)
{
printf("sigprocmask error\n");
return;
}
printf("%s\n", str);
printf("sig mask: ");
int i, is_mask = false;
for (i=0; i < sizeof(signo_array)/sizeof(int); ++i)
{
if (sigismember(&mask, signo_array[i]))
{
is_mask = true;
printf("%s ", sigstr_array[i]);
}
}
if (!is_mask)
{
printf("no_signal_masked");
}
printf("\n");
errno = save_errno;
}
void sig_int(int signo)
{
pr_mask("caught SIGINT\n");
}
执行结果:

可以看到,sigsuspend 首先将信号屏蔽字设置为了 waitset,并等待,直到 SIGINT 信号发生,在 SIGINT 信号处理函数中,阻塞了 waitset 中的信号,当处理结束之后,进程的信号屏蔽字恢复到了函数执行之前
sigsuspend 的另一个常用方式是等待在信号处理函数中设置一个全局变量的值,但是需要注意的是,只能够给 sig_atomic_t 类型的变量复制,否则后果是不可预期的
示例 -- 通过 suspend 实现父子进程同步
/*
* author: 龙泉居士
* date: 2014-09-17
* file: main.c
*/
#include <signal.h>
static volatile sig_atomic_t sigflag;
static sigset_t newmask, oldmask, zeromask;
static void sig_usr(int signo)
{
sigflag = 1;
}
void TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
{
printf("signal SIGUSR1 error\n");
exit(0);
}
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
{
printf("signal SIGUSR2 error\n");
exit(0);
}
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) != 0)
{
printf("SIG_BLOCK error\n");
exit(0);
}
}
void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2);
}
void WAIT_PARENT()
{
while (sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)
{
printf("child SIG_SETMASK error\n");
exit(0);
}
}
void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1);
}
void WAIT_CHILD()
{
while (sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)
{
printf("parent SIG_SETMASK error\n");
exit(0);
}
}
这 5 个函数使用了两个用户定义的信号:SIGUSR1 和 SIGUSR2
在单线程环境中,进程只能单纯地等待信号,在多线程环境中,常常会专门安排一个信号处理线程