UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字

时间:2021-05-10 21:56:39

lienhua34
2014-11-03

1 信号传递过程

信号源为目标进程产生了一个信号,然后由内核来决定是否要将该信号传递给目标进程。从信号产生到传递给目标进程的流程图如图 1 所示,

UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字

图 1: 信号产生、传递到处理的流程图

进程可以阻塞信号的传递。当信号源为目标进程产生了一个信号之后,内核会执行依次执行下面操作,

1. 如果目标进程设置了忽略该信号,则内核直接将该信号丢弃。

2. 如果目标进程没有阻塞该信号,则内核将该信号传递给目标进程,由目标进程执行相对应操作。

3. 如果目标进程设置阻塞该信号,则内核将该信号放到目标进程的阻塞信号列表中,等待目标进程对该类型信号的下一步设置。若目标进程后续设置忽略该信号,则内核将该信号从目标进程的阻塞信号列表中移除并丢弃。若目标进程对该信号解除了阻塞,内核将该信号传递给目标进程进行相对应的操作。

在信号产生到信号传递给目标进程之间的时间间隔内,我们称该信号为未决的(pending)。

每个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞传递给该进程的信号集。对于每种可能的信号,信号屏蔽字中都有一位与之对应。

2 信号集及其操作

POSIX.1 定义了一个数据类型sigset_t,用于表示信号集。另外,头文件 signal.h 提供了下列五个处理信号集的函数。

函数 sigemptyset 初始化由 set 指向的信号集,清除其中所有信号。

int sigemptyset(sigset_t *set);

返回值:若成功则返回0,若出错则返回-1

函数 sigfillset 初始化由 set 指向的信号集,使其包含所有信号。

int sigfillset(sigset_t *set);
返回值:若成功则返回0,若出错则返回-1

函数 sigaddset 将一个信号 signo 添加到现有信号集 set 中。

int sigaddset(sigset_t *set, int signo);
返回值:若成功则返回0,若出错则返回-1

函数 sigdelset 将一个信号 signo 从信号集 set 中删除。

int sigdelset(sigset_t *set, int signo);
返回值:若成功则返回0,若出错则返回-1

函数 sigismember 判断指定信号 signo 是否在信号集 set 中。

int sigismember(const sigset_t *set, int signo);
返回值:若真则返回1,若假则返回0,若出错则返回-1

3. sigprocmask 检 或设置进程的信号屏蔽字

调用 sigprocmask 函数可以检测或者设置进程的信号屏蔽字。

#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
返回值:若成功则返回0,若出错则返回-1

若 oset 参数是一个非空指针,则进程的当前信号屏蔽字将通过 oset 返回。若 set 参数是一个非空指针,则参数 how 将指示如何修改当前信号屏蔽字。how 的可选值如表 1 所示,

表 1: sigprocmask 函数 how 参数可选值
how 说明
SIG_BLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集。
SIG_UNBLOCK 该进程的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集补给的交集。set 包含了我们希望解除阻塞的信号。
SIG_SETMASK 该进程新的信号屏蔽字设置为 set 所指向的信号集。

下面我们来看一个例子。在下面的程序文件中先调用 sigprocmask 设置阻塞信号 SIGALRM,然后调用 alarm(2) 设置一个两秒钟的闹钟(两秒钟之后将向当前进程产生一个 SIGALRM 信号)。在睡眠 4 秒钟之后(此时应该已经产生了 SIGALRM 信号),调用 sigprocmask 函数解除对信号SIGALRM 的阻塞。

UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

static void
sig_alrm(int signo)
{
  printf("received SIGALRM\n");
}

int
main(void)
{
  sigset_t sigset;

  sigemptyset(&sigset);
  sigaddset(&sigset, SIGALRM);
  if (sigprocmask(SIG_BLOCK, &sigset, NULL) < 0) {
    printf("sigprocmask error: %s\n", strerror(errno));
    exit(-1);
  }

  if (signal(SIGALRM, sig_alrm) < 0) {
    printf("signal error: %s\n", strerror(errno));
    exit(-1);
  }

  alarm(2);
  sleep(4);
  printf("before unblock sigprocmask\n");
  if (sigprocmask(SIG_UNBLOCK, &sigset, NULL) < 0) {
    printf("sigprocmask SIG_UNBLOCK error: %s\n", strerror(errno));
    exit(-1);
  }
  printf("before exit\n");
  exit(0);
}
sigprocmaskdemo.c

编译该程序文件 sigprocmaskdemo.c,生成并执行文件 sigprocmaskdemo,

lienhua34:demo$ gcc -o sigprocmaskdemo sigprocmaskdemo.c
lienhua34:demo$ ./sigprocmaskdemo
before unblock sigprocmask
received SIGALRM
before exit

从上面的执行输出,我们看到信号 SIGALRM 是在调用 sigprocmask函 数 执 行 unblock 之 后 才 被 传 递 给 当 前 进 程 进 行 处 理 的。 如 果 我 们 将sigprocmaskdemo.c 中的sigprocemask(SIG_BLOCK, &sigset, NULL) 注释掉,编译执行,生成如下结果,

lienhua34:demo$ ./sigprocmaskdemo
received SIGALRM
before unblock sigprocmask
before exit

4 sigpending 获取进程未决的信号集

函数 sigpending 获取当前进程所有未决的信号。通过其 set 参数返回未决的信号集。

#include <signal.h>
int sigpending(sigset_t *set);
返回值:若成功则返回0,若出错则返回-1

下面我们来看一个例子,

UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字UNIX环境编程学习笔记(24)——信号处理进阶学习之信号集和进程信号屏蔽字
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

void
alrm_is_pending(const char *str)
{
  sigset_t pendingsigset;

  printf("%s: ", str);
  if (sigpending(&pendingsigset) < 0) {
    printf("sigpending error: %s\n", strerror(errno));
    exit(-1);
  }
  if (sigismember(&pendingsigset, SIGALRM)) {
    printf("SIGALRM is pending\n");
  } else {
    printf("SIGALRM is not pending\n");
  }
}

int
main(void)
{
  sigset_t sigset;

  sigemptyset(&sigset);
  sigaddset(&sigset, SIGALRM);
  if (sigprocmask(SIG_BLOCK, &sigset, NULL) < 0) {
    printf("sigprocmask error: %s\n", strerror(errno));
    exit(-1);
  }
  alrm_is_pending("before alarm");
  alarm(2);
  sleep(4);
  alrm_is_pending("after alarm");
  exit(0);
}
sigpendingdemo.c

编译该程序 sigpendingdemo.c,生成并执行文件 sigpendingdemo。从下面的运行结果,我们看到调用 alarm 函数产生信号 SIGALRM 之后,该信号在 sigpending 函数的 set 参数指向的信号集中。

lienhua34:demo$ gcc -o sigpendingdemo sigpendingdemo.c
lienhua34:demo$ ./sigpendingdemo
before alarm: SIGALRM is not pending
after alarm: SIGALRM is pending

(done)