1. 信号的基本概念

为了理解信号,先从我们最熟悉的场景说起:

  1. 用户输入命令,在Shell下启动一个前台进程。

  2. 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。

  3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。

  4. 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。

  5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

注意,Ctrl-C产生的信号只能发给前台进程。在第 3.3 节 “wait和waitpid函数”中我们看到一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl-C这种控制键产生的信号。前台进程在运行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

kill -l命令可以察看系统定义的信号列表:

  1. $ kill -l
  2. 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
  3. 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
  4. 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
  5. 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
  6. 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  7. 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
  8. 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
  9. 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
  10. 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
  11. ...

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定义#define SIGINT 2。编号34以上的是实时信号,本章只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:

  1. Signal Value Action Comment

SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Interrupt from keyboard SIGQUIT 3 Core Quit from keyboard SIGILL 4 Core Illegal Instruction …

上表中第一列是各信号的宏定义名称,第二列是各信号的编号,第三列是默认处理动作,Term表示终止当前进程,Core表示终止当前进程并且Core Dump(下一节详细介绍什么是Core Dump),Ign表示忽略该信号,Stop表示停止当前进程,Cont表示继续执行先前停止的进程,表中最后一列是简要介绍,说明什么条件下产生该信号。

产生信号的条件主要有:

  • 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号(可使前台进程停止,这个信号将在第 34 章 终端、作业控制与守护进程详细解释)。

  • 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

  • 一个进程调用kill(2)函数可以发送信号给另一个进程。

  • 可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。

  • 当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如何处理某种信号(sigaction函数稍后详细介绍),可选的处理动作有以下三种:

  1. 忽略此信号。

  2. 执行该信号的默认处理动作。

  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。