8.5.2 发送信号

8.5.2 发送信号

Unix 系统提供了大量向进程发送信号的机制。所有这些机制都是基于进程组(process group)这个概念的。

1. 进程组

每个进程都只属于一个进程组,进程组是由一个正整数进程组 ID 来标识的。getpgrp 函数返回当前进程的进程组 ID:

#include <unistd.h>

pid_t getpgrp(void);

返回: 调用进程的进程组 ID。

默认地,一个子进程和它的父进程同属于一个进程组。一个进程可以通过使用 setpgid 函数来改变自己或者其他进程的进程组:

#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);

返回: 若成功则为 0,若错误则为 -1。

setpgid 函数将进程 pid 的进程组改为 pgid。如果 pid 是 0,那么就使用当前进程的 PID。如果 pgid 是 0,那么就用 pid 指定的进程的 PID 作为进程组 ID。例如,如果进程 15213 是调用进程,那么

setpgid(0, 0);

会创建一个新的进程组,其进程组 ID 是 15213,并且把进程 15213 加入到这个新的进程组中。

2. 用 /bin/kill 程序发送信号

/bin/kill 程序可以向另外的进程发送任意的信号。比如,命令

linux> /bin/kill -9 15213

发送信号 9(SIGKILL)给进程 15213。一个为负的 PID 会导致信号被发送到进程组 PID 中的每个进程。比如,命令

linux> /bin/kill -9 -15213

发送一个 SIGKILL 信号给进程组 15213 中的每个进程。注意,在此我们使用完整路径 /bin/kill,因为有些 Unix shell 有自己内置的 kill 命令。

3. 从键盘发送信号

Unix shell 使用作业(job)这个抽象概念来表示为对一条命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和 0 个或多个后台作业。比如,键入

linux> ls | sort

会创建一个由两个进程组成的前台作业,这两个进程是通过 Unix 管道连接起来的:一个进程运行 ls 程序,另一个运行 sort 程序。shell 为每个作业创建一个独立的进程组。进程组 ID 通常取自作业中父进程中的一个。比如,图 8-28 展示了有一个前台作业和两个后台作业的 shell。前台作业中的父进程 PID 为 20,进程组 ID 也为 20。父进程创建两个子进程,每个也都是进程组 20 的成员。

前台和后台进程组

图 8-28 前台和后台进程组

在键盘上输入 Ctrl+C 会导致内核发送一个 SIGINT 信号到前台进程组中的每个进程。默认情况下,结果是终止前台作业。类似地,输入 Ctrl+Z 会发送一个 SIGTSTP 信号到前台进程组中的每个进程。默认情况下,结果是停止(挂起)前台作业。

4. 用 kill 函数发送信号

进程通过调用 kill 函数发送信号给其他进程(包括它们自己)。

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

int kill(pid_t pid, int sig);

返回: 若成功则为 0,若错误则为 -1。

如果 pid 大于零,那么 kill 函数发送信号号码 sig 给进程 pid。如果 pid 等于零,那么 kill 发送信号 sig 给调用进程所在进程组中的每个进程,包括调用进程自己。如果 pid 小于零,kill 发送信号 sig 给进程组 |pid|(pid 的绝对值)中的每个进程。图 8-29 展示了一个示例,父进程用 kill 函数发送 SIGKILL 信号给它的子进程。

code/ecf/kill.c

#include "csapp.h"

int main()
{
    pid_t pid;

    /* Child sleeps until SIGKILL signal received, then dies */
    if ((pid = Fork()) == 0) {
        pause(); /* Wait for a signal to arrive */
        printf("control should never reach here!\n");
        exit(0);
    }

    /* Parent sends a SIGKILL signal to a child */
    Kill(pid, SIGKILL);
    exit(0);
}

图 8-29 使用 kill 函数发送信号给子进程

5. 用 alarm 函数发送信号

进程可以通过调用 alarm 函数向它自己发送 SIGALRM 信号。

#include <unistd.h>

unsigned int alarm(unsigned int secs);

返回: 前一次闹钟剩余的秒数,若以前没有设定闹钟,则为 0。

alarm 函数安排内核在 secs 秒后发送一个 SIGALRM 信号给调用进程。如果 secs 是零,那么不会调度安排新的闹钟(alarm)。在任何情况下,对 alarm 的调用都将取消任何待处理的(pending)闹钟,并且返回任何待处理的闹钟在被发送前还剩下的秒数(如果这次对 alarm 的调用没有取消它的话);如果没有任何待处理的闹钟,就返回零。