第 10 章:信号

信号是软件中断,它提供了一种处理异步事件的手段。

信号的定义

每个信号都以一个 SIG 开头的命令定义,定义位于头文件 <signal.h> 中。

信号都被定义为正整数,不存在编号为 0 的信号,因为发送信号的 kill 函数对 0 有特殊的用途(用于检查进程是否存在)。

产生信号

有很多条件可以产生信号:

  • 用户通过按键,引发终端产生信号:比如 CTRL+C 可以产生中断信号 SIGINT
  • 硬件异常产生信号:除零、无效的内存引用等等。这些异常由硬件检测,并通知内核,内核再为引发问题的进程产生适当的信号。
  • 进程调用 kill(2) 函数可将信号发送给另一个进程或进程组。
  • 用户用 kill(1) 命令将信号发送给其他进程。
  • 当检测到某种软件条件已经发生,也会产生信号来通知进行。

Note

这里使用的 kill 是带有歧义的,它的意思是“向进程发送指定信号”,而不是“杀死某个程序”。

信号处理

信号是异步事件的一个典型示例, 不能通过检测某个变量来判断是否出现了一个信号, 而是必须告诉内核, “当XX信号发生时,请执行以下动作”。

处理信号有以下三种方法:

  • 忽略此信号,不执行任何动作。除了用于终结进程的 SIGKILLSIGSTOP 之外,大部分信号都可以被忽略。
  • 捕捉信号。通知内核,在指定信号发生时,执行一个指定的函数,这个函数称为该信号的信号处理器(handler)。
  • 执行系统默认动作。最常见的默认动作是终结接收到信号的进程。

信号处理器

使用 signal 函数,可以为指定信号管理一个信号处理器:当进程接到给定信号时,它就会执行所指定的信号处理器。

signal 函数的签名定义如下:

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

其中参数 signum 指定要处理的信号; handler 参数指定处理信号的函数, 这个函数接受一个整数值,但不返回任何值。

如果处理器设置成功, signal 函数的返回值是之前为这个信号所设置的信号处理器。如果设置失败,则返回 SIG_ERR

handler 可以是 SIG_IGN 或者 SIG_DFL ,前者表示忽略信号,后者表示执行信号的默认动作。

以下程序执行一个无限循环,直到接受到指定信号才退出:

// 10-signal.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void sig_usr(int);

int
main(void) {

    if (signal(SIGUSR1, sig_usr) == SIG_ERR) {
        printf("set SIGUSR1 handler fail\n");
        return 1;
    }

    for ( ; ; )
        pause();
}

void 
sig_usr(int signo)
{
    printf("receive signal %d \n", SIGUSR1);
    printf("bye bye ~\n");
    exit(0);
}

我们在后台运行这个程序,并调用 kill 命令向它发送指定的信号:

$ ./10-signal.out &
[1] 4016

$ kill -USR1 4016
receive signal 10
bye bye ~

Tip

signal 函数的语义和实现有关,最好使用 sigaction 函数代替 signal 函数。

信号与阻塞调用

如果在进程执行一个阻塞调用的过程中, 系统捕捉到一个信号, 那么这个调用就会被中断, 不再执行, 该系统调用返回出错, 并将 errno 设为 EINTR

以下代码展示了一个可能被中断的读操作如何重新启动:

again:
    if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno = EINTR)
            goto again;
        else
            // handle error
    }

发送信号

有几个函数可以用于发送和等待信号。

kill 和终端的 kill 命令类似,它可以将一个指定的信号发送给指定的进程或者进程组(实际上, kill 命令就由 kill 函数定义),它的函数签名定义如下:

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

raise 可以向调用进程自身发送一个信号:

#include <signal.h>

int raise(int sig);

alarm 可以在规定的时间之后,向调用进程自身发送一个 SIGALRM 信号:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

因为 SIGALRM 的默认操作是终止进程,因此,要有效地利用 alarm ,通常需要为 SIGALRM 信号设置一个信号处理程序。

pause 函数挂起调用进程,直到捕捉到一个信号为止:

#include <unistd.h>

int pause(void);

留言

comments powered by Disqus

Table Of Contents

Previous topic

第 8 章:进程控制

Next topic

第 11 章:线程