第 20 章 信号:基本概念 (Signals: Fundamental Concepts)

      +

      核心结论

      • 信号是软件中断:通知进程某事件已发生;Linux 上标准信号编号 1-31(另有实时信号 32-64,第 22 章);<signal.h> 中以 SIGxxxx 符号名引用。

      • 信号三态:产生(generate)→ 等待(pending)→ 投递(deliver);可被「信号掩码」阻塞——阻塞期间保持 pending,解除阻塞后立即投递。

      • 默认动作五类:ignore(SIGCHLD/SIGURG 等)、terminate(SIGTERM)、core dump(SIGSEGV/SIGABRT/SIGQUIT)、stop(SIGSTOP/SIGTSTP/SIGTTIN/TTOU)、cont(SIGCONT)。

      • 信号处置(disposition):signal() 或 sigaction() 设为 SIG_DFL(默认)、SIG_IGN(忽略)或自定义 handler;sigaction 是 SUSv3 推荐、跨平台可靠的方式。

      • 信号发送四象限kill(pid, sig) 中 pid > 0 单进程;pid == 0 同进程组;pid < -1 进程组 |pid|;pid == -1 同 UID 全部进程;sig=0 为「空信号」用于探测进程是否存在。

      • 标准信号不排队:同一信号在阻塞期间产生多次仅投递一次;高频信号可能丢失——这是「为什么不能用信号计数」的根本原因。

      本章主旨

      本章是信号三章中的第一章——建立「什么是信号、信号从哪来、信号怎么响应」的总体框架。读者应掌握:信号的三态生命周期(generate/pending/deliver)、信号掩码与阻塞机制、六种默认动作、四大类信号来源(硬件异常、终端特殊字符、软件事件、其他进程),kill() 的四种 pid 语义与权限规则、信号集(sigset_t)API、sigprocmask/sigpending/sigsuspend/pause 的用法。本章与第 21 章(handler 设计)、第 22 章(高级特性:实时信号、signalfd、core dump 等)形成完整信号体系。

      一、核心概念

      本章围绕 7 个核心概念展开:从信号本质入手,到信号类型、信号处置、信号发送、信号掩码、pending 信号,最后是进程等待信号的标准范式。

      概念 定义 + 重要性 实现提示

      信号生命周期

      信号三态:generate(事件发生)→ pending(等待投递)→ deliver(已投递);阻塞期间保持 pending;解除阻塞后立即投递(甚至在 sigprocmask 返回前)。

      §20.1;标准信号不排队——多次产生只投递一次;同步信号(如 SIGSEGV)发生在进程执行某指令时,可预测;异步信号(如 SIGTERM)时机不可预测。

      信号类型与默认动作

      Linux 标准信号 1-31,每个有独特语义;默认动作分 5 类:ignore / term / core / stop / cont;SIGKILL 与 SIGSTOP 无法被阻塞、忽略或捕获——保证管理员始终能结束失控进程。

      §20.2/Table 20-1;常用信号:SIGTERM(15,优雅终止)、SIGKILL(9,强制)、SIGHUP(1,挂起/重载配置)、SIGCHLD(17,子进程终止)、SIGSEGV(11,段错误)、SIGPIPE(13,管道破裂)。

      信号处置(Disposition)

      用 signal() 或 sigaction() 把信号处置设为 SIG_DFL(默认)、SIG_IGN(忽略)、自定义 handler;sigaction 是首选(可移植、灵活、可获详细信息)。

      §20.3/§20.13;handler 形式:void handler(int sig),sig 参数是触发 handler 的信号号。

      信号发送(kill/raise/killpg)

      kill(pid, sig) 是核心;pid > 0 单进程;pid == 0 同进程组;pid < -1 进程组 abs(pid);pid == -1 同 UID 全进程(广播);sig=0 为「空信号」用于探测进程存在。

      §20.5;权限:root 可发任何信号;非特权 sender 需 UID/GID 匹配 receiver(real 或 saved set-user-ID);SIGCONT 特殊——同 session 任意 UID 都可发。

      信号集(sigset_t)

      用 sigset_t 数据类型表示多个信号的集合;sigemptyset/sigfillset/sigaddset/sigdelset/sigismember 操作;SUSv3 要求 sigset_t 必须可赋值(不一定实现为位图,但 Linux 是)。

      §20.9;必须用 sigemptyset 或 sigfillset 初始化——不能假设静态变量初值表示空集。

      信号掩码与阻塞

      sigprocmask(how, set, oldset) 操纵进程的信号掩码;how 取 SIG_BLOCK(加)/ SIG_UNBLOCK(减)/ SIG_SETMASK(覆盖);SIGKILL/SIGSTOP 不能被阻塞。

      §20.10;典型模式:sigprocmask(SIG_BLOCK, &set, &prev) → 临界区 → sigprocmask(SIG_SETMASK, &prev, NULL);pending 信号在解除阻塞后立即投递。

      等待信号(pause/sigsuspend)

      pause() 阻塞直到任意信号到达(必返回 -1,errno = EINTR);sigsuspend(mask) 原子地「设置 mask 并 pause」——避免 pause 与 sigprocmask 之间的 race。

      §20.14;pause 简单但有 race;sigsuspend 是正确模式(详见第 22 章)。

      二、详细笔记

      20.1 信号概念与生命周期

      What:信号(signal)是「事件已发生」的通知;类比硬件中断——异步打断进程正常执行。

      Why:信号是 UNIX 异步事件通知的核心机制——Ctrl-C、kill、子进程退出、定时器到期、硬件异常等都用信号表达。

      How:信号三态:

      含义 关键事实

      产生 (generate)

      事件发生,内核/进程「记下」要发某信号

      可由内核(硬件异常、终端、软件事件)或其他进程(kill)触发

      等待 (pending)

      信号已产生但未投递

      进程信号掩码(signal mask)决定信号是否被阻塞;被阻塞的信号在 pending 集中等待

      投递 (deliver)

      信号已送到进程,触发默认动作或 handler

      进程解除阻塞后立即投递;SUSv3 要求至少一个被解除阻塞的 pending 信号在 sigprocmask 返回前投递

      信号来源(§20.1):

      • 硬件异常——非法内存访问、总线错误、除零等;通常同步触发。

      • 终端特殊字符——Ctrl-C(SIGINT)、Ctrl-Z(SIGTSTP)、Ctrl-\(SIGQUIT)。

      • 软件事件——timer 到期、子进程终止、文件描述符就绪、CPU 时间超限等。

      • 进程间发送——kill/raise/killpg;可用作同步原语或简易 IPC。

      When:写信号驱动代码——理解三态才能设计正确的「屏蔽-处理-恢复」流程。

      Example:Ctrl-C 时的信号流程:

      用户按 Ctrl-C
         ↓
      终端驱动发送 SIGINT 给前台进程组
         ↓
      内核把 SIGINT 标记为 pending(如果未阻塞)
         ↓
      下次调度时内核投递 SIGINT
         ↓
      若进程有 handler → 调用 handler;否则默认终止

      20.2 信号类型与默认动作

      What:Linux 标准信号 1-31;每个有独特的名字、编号与默认动作;其中两个「sure」信号(SIGKILL/SIGSTOP)行为固定。

      Why:信号数量众多,各有语义;理解默认动作是「为什么我的程序被杀了」「为什么 Ctrl-Z 让它停了」的前提。

      How:常见信号速查(§20.2 + Table 20-1):

      信号 默认 来源与语义

      SIGABRT(6)

      core

      abort() 触发;产生 core dump

      SIGALRM(14)

      term

      alarm/setitimer 实时 timer 到期

      SIGBUS(7)

      core

      总线错误(如 mmap 越界)

      SIGCHLD(17)

      ignore

      子进程终止/停止/恢复

      SIGCONT(18)

      cont

      恢复被 stop 的进程

      SIGFPE(8)

      core

      算术异常(除零)

      SIGHUP(1)

      term

      终端挂起;daemon 用它重载配置

      SIGILL(4)

      core

      非法指令

      SIGINT(2)

      term

      终端中断字符(Ctrl-C)

      SIGKILL(9)

      term

      sure kill——不能阻塞/忽略/捕获

      SIGPIPE(13)

      term

      管道/FIFO/socket 写时无读端

      SIGQUIT(3)

      core

      终端 quit 字符(Ctrl-\),产生 core

      SIGSEGV(11)

      core

      段错误——无效内存引用

      SIGSTOP(19)

      stop

      sure stop——不能阻塞/忽略/捕获

      SIGSYS(31)

      core

      非法系统调用号

      SIGTERM(15)

      term

      标准终止信号;kill/killall 默认发它

      SIGTSTP(20)

      stop

      终端停止字符(Ctrl-Z)

      SIGUSR1(10)/SIGUSR2(12)

      term

      用户自定义信号

      SIGWINCH(28)

      ignore

      终端窗口大小变化

      SIGXCPU(24)/SIGXFSZ(25)

      core

      CPU/文件大小超 RLIMIT

      When:进程间约定通信——SIGUSR1/SIGUSR2 是「应用自定义」的专用通道;daemon 用 SIGHUP 重载配置;父进程用 SIGCHLD 感知子进程退出。

      Example:标准 kill 流程——kill <pid> 默认发 SIGTERM;kill -9 <pid> 发 SIGKILL。优雅终止应先用 SIGTERM 让程序清理资源,最后才用 SIGKILL。

      20.3 signal() 与 20.13 sigaction()

      Whatsignal() 是旧 API,sigaction() 是 POSIX/SUSv3 推荐 API;后者支持阻塞集、flags、获取旧 disposition 等。

      Whysignal() 在不同 UNIX 上语义差异大;sigaction() 行为一致——写可移植代码必须用 sigaction。

      How:sigaction 用法(§20.13):

      // 摘自《The Linux Programming Interface》第 20 章
      #include <signal.h>
      
      struct sigaction {
          void   (*sa_handler)(int);     /* handler 地址 / SIG_IGN / SIG_DFL */
          sigset_t sa_mask;              /* handler 调用期间额外阻塞的信号 */
          int      sa_flags;             /* 标志位 */
          void   (*sa_restorer)(void);   /* 内部使用,应用不要碰 */
      };
      
      int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);

      典型用法:

      // 摘自《The Linux Programming Interface》第 20 章
      struct sigaction sa;
      sa.sa_handler = my_handler;
      sigemptyset(&sa.sa_mask);          /* handler 期间不额外阻塞 */
      sa.sa_flags = 0;                   /* 默认:阻塞触发信号、自动恢复 */
      sigaction(SIGINT, &sa, NULL);

      When

      • 一次性 handler——sa_flags = SA_RESETHAND(捕获后恢复默认)。

      • handler 中允许同信号再次打断——sa_flags |= SA_NODEFER

      • 自动重启被中断的系统调用——sa_flags |= SA_RESTART(第 21 章详述)。

      • handler 需要更多上下文(发送者 PID、信号来源)——sa_flags |= SA_SIGINFO(第 21 章)。

      Example:完整 sigaction 安装:

      struct sigaction sa = {0};
      sa.sa_handler = my_handler;
      sigemptyset(&sa.sa_mask);
      sa.sa_flags = SA_RESTART;
      if (sigaction(SIGINT, &sa, &old_sa) == -1) errExit("sigaction");
      /* old_sa 含旧 disposition,可在适当时候恢复 */

      20.4 信号处理器(Handler)基础

      What:handler 是「信号投递时被自动调用的用户函数」;形式为 void handler(int sig)

      Why:handler 是进程对信号「主动响应」的方式——比默认动作更灵活。

      How:第 20 章 Listing 20-1 ouch.c——最简 handler 示例:

      // 摘自《The Linux Programming Interface》第 20 章(Listing 20-1)
      #include <signal.h>
      static void sigHandler(int sig) {
          printf("Ouch!\n");          /* UNSAFE: stdio 非 async-signal-safe */
      }
      int main(int argc, char *argv[]) {
          if (signal(SIGINT, sigHandler) == SIG_ERR) errExit("signal");
          for (;;) { printf("%d\n", j++); sleep(3); }
      }

      handler 设计的两大模式:

      • 设置全局 flag——主程序周期性检查;适合简单场景。

      • 清理后终止或 nonlocal goto——适合错误处理路径。

      When:handler 应尽量简单——避免调用非 async-signal-safe 函数(printf、malloc、exit 等);详细见第 21 章。

      Exampleintquit.c —— 同一 handler 处理 SIGINT 与 SIGQUIT:

      static void sigHandler(int sig) {
          if (sig == SIGINT) { count++; printf("Caught SIGINT (%d)\n", count); return; }
          /* SIGQUIT */
          printf("Caught SIGQUIT - that's all folks!\n");
          exit(EXIT_SUCCESS);
      }

      20.5 发送信号:kill/raise/killpg

      Whatkill(pid, sig) 是核心发送 API;raise(sig) 是「给自己发信号」的便捷封装;killpg(pgrp, sig) 发到进程组。

      Why:进程间最常用的同步/控制手段;kill 命名虽然叫 kill,但实际可发任何信号。

      How:kill 的 pid 语义:

      pid 取值 含义 备注

      > 0

      发到 PID = pid 的进程

      需权限

      == 0

      发到同进程组所有进程

      包括调用者

      < -1

      发到进程组 ID = abs(pid) 的所有进程

      适合 shell job control

      == -1

      发到同 UID 所有进程(除 init 与调用者)

      广播信号;root 可发给所有进程

      权限规则(§20.5):

      • 特权进程(CAP_KILL)可发任何信号给任何进程。

      • init(PID 1)只能被它安装过 handler 的信号发。

      • 非特权 sender 需「sender real/effective UID == receiver real/saved set-user-ID」。

      • SIGCONT 特殊:同 session 任意 UID 都可发。

      sig = 0(空信号):不发任何信号,仅检查权限——kill(pid, 0) 返回 0 表示「可发信号」、ESRCH 表示「进程不存在」、EPERM 表示「存在但无权」。

      When:检查进程是否存在——kill(pid, 0);shell job control——kill(-pgid, sig);进程间事件通知——kill(pid, SIGUSR1)

      Example:第 20 章 Listing 20-3 t_kill.c —— 演示 kill 与空信号:

      kill(getLong(argv[1], 0, "pid"), sig);
      if (sig == 0) {
          if (s == 0)       printf("Process exists and we can send it a signal\n");
          else if (errno == EPERM) printf("Process exists, but no permission\n");
          else if (errno == ESRCH) printf("Process does not exist\n");
      }

      20.9 信号集(sigset_t)API

      Whatsigset_t 表示一组信号;五个操作函数:sigemptyset(清空)、sigfillset(填满)、sigaddset(加)、sigdelset(删)、sigismember(测)。

      Why:很多信号相关 API(如 sigprocmask、sigaction)需要表示一组信号——sigset_t 是统一抽象。

      How

      sigset_t set;
      sigemptyset(&set);              /* 必须在使用前显式初始化 */
      sigaddset(&set, SIGINT);
      sigaddset(&set, SIGTERM);
      if (sigismember(&set, SIGINT)) { /* SIGINT 在 set 中 */ }
      
      sigfillset(&set);              /* 包含所有信号 */
      sigdelset(&set, SIGKILL);       /* 移除(其实 SIGKILL 本来也加不进去) */

      必须用 sigemptyset/sigfillset 初始化——SUSv3 不保证 sigset_t 是位图;不能用 memset(0) 模拟空集。

      GNU 扩展:sigandsetsigorsetsigisemptyset

      When:阻塞前初始化 sigset_t → 用 sigaddset 加要阻塞的信号 → 传给 sigprocmask。

      Example:printSigset 函数(Listing 20-4)—— 遍历 NSIG 个信号,sigismember 测试,打印所有属于 set 的信号名。

      20.10 信号掩码与 sigprocmask

      Whatsigprocmask(how, set, oldset) 操纵进程的信号掩码(已阻塞信号集);how 取 SIG_BLOCK(加)/ SIG_UNBLOCK(减)/ SIG_SETMASK(覆盖)。

      Why:保证临界区不被信号打断;保存/恢复信号掩码实现「临时屏蔽」。

      How:典型「屏蔽-恢复」模式(§20.10 Listing 20-5):

      sigset_t blockSet, prevMask;
      sigemptyset(&blockSet);
      sigaddset(&blockSet, SIGINT);              /* 准备阻塞 SIGINT */
      
      /* 阻塞 SIGINT,保存旧掩码 */
      if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
          errExit("sigprocmask1");
      
      /* 临界区代码 —— 不会被 SIGINT 打断 */
      /* SIGINT 若在此期间产生,进入 pending 集 */
      
      if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
          errExit("sigprocmask2");
      /* 解除阻塞 → pending 的 SIGINT 立即被投递(可能在 sigprocmask 返回前) */

      关键事实:

      • SIGKILL 与 SIGSTOP 不能被阻塞——sigprocmask 静默忽略此请求,不报错。

      • SUSv3 要求「解除阻塞的 pending 信号在 sigprocmask 返回前至少投递一个」。

      • sigfillset(&set); sigprocmask(SIG_BLOCK, &set, NULL) 阻塞「除 SIGKILL/SIGSTOP 外全部信号」——原子性把握状态的好方式。

      When:更新全局数据结构的临界区;事务性操作(创建临时文件 + 写内容 + 改名)应屏蔽信号防止半完成。

      Example:第 20 章 Listing 20-6/20-7 sig_sender + sig_receiver——演示「阻塞期间发送 100 万次同一信号,解阻后只投递一次」。

      20.11/20.12 pending 信号与「信号不排队」

      Whatsigpending(&set) 返回当前进程的 pending 信号集;标准信号不排队——多次产生只投递一次。

      Why:理解这一点才能解释「为什么 SIGCHLD 多次产生但 wait 只回收一次」「为什么 SIGUSR1 不能用于计数」。

      How

      sigset_t pending;
      sigpending(&pending);
      /* 检查 pending 中是否有某信号 */
      if (sigismember(&pending, SIGINT)) { /* SIGINT pending */ }

      利用「信号不排队」的特性:

      • 改变 pending 信号的 disposition 为 SIG_IGN(或默认 ignore)——该信号从 pending 集移除,不再投递。

      • 适合「在信号产生后、handler 调用前取消信号处理」的场景。

      When:调试「信号丢失」——可能是发送频率太高、pending 集已满、或 handler 期间重复触发(后者正常,只投递一次)。

      Example:第 20 章 Listing 20-7 sig_receiver.c —— 阻塞期间收 100 万 SIGUSR1,解阻后 handler 仅调用 1 次。

      20.14 pause() 等待信号

      Whatpause() 阻塞进程直到任意信号被投递;总是返回 -1,errno = EINTR(如果是被 handler 捕获的信号)。

      Why:最简单的「阻塞到信号到达」调用——但与 sigprocmask 配合时有 race(详见第 22 章 sigsuspend)。

      How

      /* 经典模式 */
      sigset_t mask, oldmask;
      sigemptyset(&mask);
      sigprocmask(SIG_BLOCK, &mask, &oldmask);  /* 此处无意义,只是示意 */
      for (;;) {
          pause();
          /* 此处只会被信号唤醒 */
      }

      When:简单的「等待一个事件」循环;正确的并发等待应用 sigsuspend(第 22 章)。

      Example:第 20 章 Listing 20-2 intquit.c

      for (;;) pause();   /* 阻塞,直到 SIGINT 或 SIGQUIT */

      三、关键图表

      (本章无独立编号图表)

      常见信号速查
      信号 默认 典型用途

      SIGHUP(1)

      term

      终端挂起;daemon 重载配置

      SIGINT(2)

      term

      Ctrl-C 中断

      SIGQUIT(3)

      core

      Ctrl-\ 退出 + core

      SIGKILL(9)

      term

      sure kill

      SIGUSR1(10)/USR2(12)

      term

      用户自定义

      SIGPIPE(13)

      term

      管道破裂

      SIGALRM(14)

      term

      alarm timer 到期

      SIGTERM(15)

      term

      标准终止

      SIGCHLD(17)

      ignore

      子进程状态变化

      SIGCONT(18)

      cont

      恢复被 stop 进程

      SIGSTOP(19)

      stop

      sure stop

      SIGTSTP(20)

      stop

      Ctrl-Z 挂起

      SIGSEGV(11)

      core

      段错误

      信号处置与生命周期
      步骤 描述

      generate

      事件发生(硬件、终端、其他进程、软件)

      pending

      信号已产生但被 mask 阻塞;在进程 pending 集等待

      deliver

      mask 解除后投递——调用 handler 或执行默认动作

      关键事实

      标准信号不排队;SIGKILL/SIGSTOP 不可阻塞/忽略/捕获

      四、思维导图

      mindmap
        root((第 20 章 信号 基本概念))
          信号生命周期
            generate 产生
            pending 等待
            deliver 投递
            阻塞与解除
            不排队
          信号类型
            1 31 标准信号
            32 64 实时信号
            SIGKILL SIGSTOP 不可阻挡
            默认动作 5 类
            ignore term core stop cont
          信号处置
            signal 旧 API
            sigaction 推荐 API
            SIG DFL SIG IGN handler
            sa mask sa flags
          信号发送
            kill pid sig
            pid 4 象限
            sig 0 空信号
            raise killpg
            权限检查
          信号集
            sigset t 类型
            sigemptyset sigfillset
            sigaddset sigdelset
            sigismember
            5 个操作函数
          信号掩码
            sigprocmask
            SIG BLOCK UNBLOCK SETMASK
            阻塞 SIGKILL 静默忽略
            解阻立即投递
          pending 信号
            sigpending 查询
            改变 disposition 丢弃
            多次产生只投递一次
          等待信号
            pause 阻塞 EINTR
            sigsuspend 原子组合
            race 与正确模式

      五、重点与易错点

      1. 「信号是软件中断」——类比硬件中断,可异步打断主程序执行;handler 返回后程序从被打断处继续(除非 handler 用 longjmp)。

      2. 信号三态:generate → pending → deliver——阻塞期间保持 pending;解阻后立即投递;SUSv3 要求至少一个 pending 信号在 sigprocmask 返回前投递。

      3. 标准信号不排队——同一信号在阻塞期间产生多次只投递一次;高频信号可能丢失;不能用信号计数;这是实时信号(第 22 章)出现的原因之一。

      4. SIGKILL 与 SIGSTOP 不可阻挡——不能被捕获、阻塞、忽略;保证管理员始终能 kill/stop 失控进程;signal(SIGKILL, …​) 返回错误。

      5. 默认动作 5 类:ignore(SIGCHLD/SIGURG)、term(SIGTERM)、core(SIGSEGV/SIGQUIT)、stop(SIGSTOP/SIGTSTP)、cont(SIGCONT);core 类信号便于调试——ulimit -c unlimited 后用 gdb 看 core 文件。

      6. kill 的 pid 4 象限——>0 单进程;==0 同进程组;←1 进程组 |pid|;==-1 同 UID 全部(除 init 与自身);shell 用 kill -pgid 实现 job control。

      7. 空信号(sig=0)用于进程探测——kill(pid, 0) 不发信号,只检查权限;返回 0 = 可发;ESRCH = 不存在;EPERM = 存在但无权。

      8. 权限规则——root 可发任何信号;非特权 sender 需 UID 匹配(real/effective == receiver real/saved-set-user-ID);SIGCONT 特殊:同 session 任意 UID 可发。

      9. sigaction 是首选 API——signal() 跨 UNIX 行为差异大;sigaction 是 SUSv3 标准、可移植、支持 flags(SA_RESTART/SA_NODEFER/SA_RESETHAND/SA_SIGINFO)与 sa_mask。

      10. sa_mask 含义——handler 调用期间额外阻塞的信号集合;handler 触发时信号本身也会被自动阻塞(除非 SA_NODEFER);handler 返回后自动解除。

      11. sigset_t 必须显式初始化——SUSv3 不保证它是位图;不能用 memset(0) 模拟空集;用 sigemptyset/sigfillset

      12. pause 与 sigprocmask 配合有 race——两调用之间可能错过信号;正确模式是 sigsuspend(第 22 章)。

      13. 终端生成信号的 disposition 约定——若 exec 时发现 SIGHUP/SIGINT/SIGQUIT/SIGTTIN/SIGTTOU/SIGTSTP 被设为 SIG_IGN,新程序应保留 SIG_IGN;不要改回默认——否则后台作业会被意外中断(详见第 34 章)。

      14. 「信号来自硬件还是软件」决定是否能安全处理——硬件异常 SIGSEGV/SIGBUS/SIGFPE/SIGILL 返回时通常会再次触发;正确处理是不正常返回(exit/siglongjmp),详见第 22 章。

      15. 跨章衔接:第 21 章深入 handler 设计(reentrancy、async-signal-safe、longjmp、SA_SIGINFO);第 22 章深入高级特性(实时信号、sigsuspend、signalfd、core dump);第 26 章 SIGCHLD 与 wait;第 34 章 SIGHUP/SIGTSTP 与 job control;第 9 章 UID/GID 与信号权限。