# 过程控制，第 1 部分：使用信号等待宏

> 原文：<https://github.com/angrave/SystemProgramming/wiki/Process-Control%2C-Part-1%3A-Wait-macros%2C-using-signals>

## 等待宏

## 我可以找出孩子的退出价值吗？

您可以找到子项退出值的最低 8 位（`main()`的返回值或`exit()`中包含的值）：使用“等待宏” - 通常您将使用“WIFEXITED”和“WEXITSTATUS”。有关更多信息，请参见`wait` / `waitpid`手册页。

```c
int status;
pid_t child = fork();
if (child == -1) return 1; //Failed
if (child > 0) { /* I am the parent - wait for the child to finish */
  pid_t pid = waitpid(child, &status, 0);
  if (pid != -1 && WIFEXITED(status)) {
     int low8bits = WEXITSTATUS(status);
     printf("Process %d returned %d" , pid, low8bits);
  }
} else { /* I am the child */
 // do something interesting
  execl("/bin/ls", "/bin/ls", ".", (char *) NULL); // "ls ."
}
```

一个进程只能有 256 个返回值，其余的位是信息性的。

## 位移

请注意，无需记住这一点，这只是对状态变量中信息存储方式的高级概述

[Android 源代码](https://android.googlesource.com/platform/prebuilts/gcc/linuxx86/host/i686-linux-glibc2.7-%0A4.6/+/tools_r20/sysroot/usr/include/bits/waitstatus.h)

/ *如果是 WIFEXITED（STATUS），则为低位 8 位的状态。 * /

#define __WEXITSTATUS（status）（（（状态）＆amp; 0xff00）＆gt;＆gt; 8）

/ *如果 WIFSIGNALED（STATUS），终止信号。 * /

#define __WTERMSIG（status）（（status）＆amp; 0x7f）

/ *如果 WIFSTOPPED（STATUS），停止孩子的信号。 * /

#define __WSTOPSIG（status）__ WEXITSTATUS（status）

/ *非零，如果 STATUS 表示正常终止。 * /

#define __WIFEXITED（status）（__ WTERMSIG（status）== 0）

内核具有跟踪信号，退出或停止的内部方式。该 API 是抽象的，以便内核开发人员可以随意更改。

## 小心。

请记住，只有满足前提条件时，宏才有意义。这意味着如果发出进程信号，则不会定义进程的退出状态。宏不会为你做检查，所以由编程来确保逻辑检出。

## 信号

## 什么是信号？

信号是内核提供给我们的构造。它允许一个进程异步地向另一个进程发送信号（思考消息）。如果该过程想要接受该信号，则它可以，然后，对于大多数信号，可以决定如何处理该信号。这是一个简短的列表（非全面的）信号。

| 名称 | 默认操作 | 常用用例 |
| --- | --- | --- |
| SIGINT | 终止进程（可以捕获） | 告诉过程好好停止 |
| SIGQUIT | Terminate Process (Can be caught) | 告诉过程严厉阻止 |
| SIGSTOP | 停止进程（无法捕获） | 停止继续进程 |
| SIGCONT | 继续一个过程 | 继续运行进程 |
| SIGKILL | 终止进程（不能忽略） | 你希望你的过程消失了 |

## 我可以暂停我的孩子吗？

是的！您可以通过发送 SIGSTOP 信号暂时暂停正在运行的进程。如果成功，它将冻结一个过程;即，该进程将不再分配 CPU 时间。

要允许进程恢复执行，请发送 SIGCONT 信号。

例如，这是一个程序，每秒慢慢打印一个点，最多 59 点。

```c
#include <unistd.h>
#include <stdio.h>
int main() {
  printf("My pid is %d\n", getpid() );
  int i = 60;
  while(--i) { 
    write(1, ".",1);
    sleep(1);
  }
  write(1, "Done!",5);
  return 0;
}
```

我们将首先在后台启动该过程（注意＆amp; at end）。然后使用 kill 命令从 shell 进程发送一个信号。

```
>./program &
My pid is 403
...
>kill -SIGSTOP 403
>kill -SIGCONT 403 
```

## 如何从 C 中杀死/停止/暂停我的孩子？

在 C 中，使用`kill` POSIX 调用向孩子发送信号，

```c
kill(child, SIGUSR1); // Send a user-defined signal
kill(child, SIGSTOP); // Stop the child process (the child cannot prevent this)
kill(child, SIGTERM); // Terminate the child process (the child can prevent this)
kill(child, SIGINT); // Equivalent to CTRL-C (by default closes the process)
```

如上所述，shell 中还有一个 kill 命令，例如获取正在运行的进程列表，然后终止进程 45 和进程 46

```
ps
kill -l 
kill -9 45
kill -s TERM 46 
```

## 如何检测“CTRL-C”并正常清理？

我们稍后会回到信号 - 这只是一个简短的介绍。在 Linux 系统上，如果您有兴趣了解更多内容，请参阅`man -s7 signal`（例如，异步信号安全的系统和库调用列表。

信号处理程序中的可执行代码有严格的限制。大多数库和系统调用都不是“异步信号安全” - 它们可能不会在信号处理程序中使用，因为它们不是可重入的安全。在单线程程序中，信号处理会暂时中断程序执行，以执行信号处理程序代码。假设您的原始程序在执行`malloc`的库代码时被中断; malloc 使用的内存结构不会处于一致状态。调用`printf`（使用`malloc`）作为信号处理程序的一部分是不安全的，并且将导致“未定义的行为”，即它不再是有用的，可预测的程序。在实践中，您的程序可能会崩溃，计算或生成不正确的结果或停止运行（“死锁”），具体取决于您的程序在执行信号处理程序代码时被中断时的确切执行情况。

信号处理程序的一个常见用途是设置一个布尔标志，偶尔轮询（读取）作为程序正常运行的一部分。例如，

```c
int pleaseStop ; // See notes on why "volatile sig_atomic_t" is better

void handle_sigint(int signal) {
  pleaseStop = 1;
}

int main() {
  signal(SIGINT, handle_sigint);
  pleaseStop = 0;
  while ( ! pleaseStop) { 
     /* application logic here */ 
   }
  /* cleanup code here */
}
```

上面的代码似乎在纸上是正确的。但是，我们需要为编译器和将执行`main()`循环的 CPU 内核提供提示。我们需要阻止编译器优化：表达式`! pleaseStop`似乎是一个循环不变量，即永远为真，因此可以简化为`true`。其次，我们需要确保`pleaseStop`的值不使用 CPU 寄存器进行高速缓存，而是始终读取和写入主存储器。 `sig_atomic_t`类型意味着变量的所有位都可以作为“原子操作”读取或修改 - 单个不间断操作。不可能读取由一些新位值和旧位值组成的值。

通过使用正确的类型`volatile sig_atomic_t`指定`pleaseStop`，我们可以编写可移植代码，其中主循环将在信号处理程序返回后退出。在大多数现代平台上，`sig_atomic_t`类型可以与`int`一样大，但在嵌入式系统上可以与`char`一样小，并且只能表示（-127 到 127）值。

```c
volatile sig_atomic_t pleaseStop;
```

这种模式的两个例子可以在“COMP”基于终端的 1Hz 4bit 计算机中找到（ [https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121](https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121) ）。使用两个布尔标志。一个标记`SIGINT`（CTRL-C）的传送，并正常关闭程序，另一个标记`SIGWINCH`信号以检测终端调整大小并重绘整个显示。