1. 信号处理函数的定义

信号处理函数.png

预定义的信号处理常量:

  • SIG_ERR:用于注册信号处理函数sigaction()的返回值,表示发生了错误
  • SIG_DFL:指定使用默认动作来处理信号
  • SIG_IGN:忽略这个信号。

2. 信号处理程序的安装

信号处理函数2.png

sigaction函数的返回值: 0 成功; -1 失败,并设置errno

有两个信号无法被捕获: SIGKILL 和 SIGSTOP

sa_mask:阻塞信号的集合。当前信号正在被处理的时候,哪些信号会被阻塞。

sa_flags:可以修改信号的行为:

  • SA_NOCLDSTOP:子进程被stop或者resume时,不发出 SIGCHLD 信号。
  • SA_NOCLDWAIT:当子进程被terminate时,不会转变成zombie进程
  • SA_NODEFER:正在处理当前信号时,不阻塞其他信号。优先级比sa_mask

注:信号的阻塞和系统调用的阻塞:

  • 信号的阻塞是指:在处理某个信号时,不允许被阻塞的信号被递交( deliver
  • 系统调用的阻塞:阻塞的系统调用会在资源不足时让进程睡眠;非阻塞的系统调用在资源不足时会立刻返回,以便进程再度进行系统调用

3. SIGCLD 信号

当一个子进程 终止停止 时,父进程会收到这个信号。

它的默认处置为忽略。

可以这样处理子进程:

sig_chld的信号处理.png

上述的子进程处理程序不能应对多个子进程的情形。

因为:在处理某个SIGCHLD信号时,此时可能有另一个SIGCHLD信号到来。UNIX中信号不会排队,所以这些后来的SIGCHLD信号就被丢掉了。这些终结的子进程没有被wait,变成了僵死进程。

对于多个子进程,应该这样:

  1. int sig_chld(int signo) {
  2. pid_t pid;
  3. int stat;
  4. while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
  5. printf("child %d terminated\n", pid);
  6. }

其中,waitpid函数的第一个参数-1表示第一个终结的子进程;第三个参数WNOHANG表示非阻塞。

不能在while使用wait函数。因为wait是阻塞的。不能得到while循环的退出条件;同时信号处理程序可能会永远阻塞下去。

4. 信号处理

  • 慢系统调用:可能会永远阻塞的系统调用。比如accept,服务器在没有接收到连接时,这个调用就不会返回。
  • 如果父进程正阻塞与某个慢系统调用,比如accept, read;这时来了另一个系统调用,并且被父进程处理了,如SIGCHLD,那么原来的慢系统调用就会出错,并设置错误码为EINTR,即Error Interrupt。
  • 对于上述情况,某些系统实现的signal函数可以将被中断的系统调用重启,而某些系统无法,我们应该手动这样做。(signal函数是ANSI C的标准,而不是POSIX标准。)
  • 对POSIX的SA_RESTART支持,意味着某些系统调用可以重启。(可以通过#ifdef SA_RESTART判断系统是否支持这项特性。)
  • 手动重启被中断的系统调用:
  1. for (;;) {
  2. client = sizeof(cliaddr);
  3. if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {
  4. if (errno == EINTR) // accept系统调用被中断了
  5. continue; // 失败重试
  6. else // 其他错误
  7. err_sys("accept error");
  8. }
  9. }

kill 命令

向指定进程发送信号。默认发送SIGTERM信号。

SIGINT,SIGTERM,SIGKILL

SIGKILL 可以通过 kill -9 发送,这个信号无法被捕获,进程会强制终止运行
SIGINT 通过键盘 ctrl-c 发送,可以被捕获
SIGTERM 是 kill 命令发送的默认信号,可以被捕获