# 练习 16：处理进程，`ps`，`kill`

> 原文：[Exercise 16. Processes: working with proccesses, ps, kill](https://archive.fo/CSqm9)

> 译者：[飞龙](https://github.com/wizardforcel)

> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

> 自豪地采用[谷歌翻译](https://translate.google.cn/)

最简单的程序是硬盘上的文件，它包含中央处理器执行的指令。当你启动它的时候，它被复制到内存，控制权传递给它。被执行的程序称为进程。在例如 Linux 的多任务操作系统中，你可以启动程序的许多实例，因此可以从一个程序启动许多进程，所有程序将同时运行（执行）。

这是执行`ls`时发生的事情的概述：

```
你
   把 ls 和它的参数输入到你的终端模拟器，然后按 <ENTER> 
   控制权现在传递给 Bash 
Bash 
   在你的硬盘上查找 ls 
   将自身派生到 Bash 克隆体，也就是将自己克隆到内存中的新位置
   成为 Bash 克隆体的父进程
   控制权传给了传递给 Bash 克隆体
Bash 克隆体
   成为 Bash 的子进程
   保存 Bash 父进程的环境
   知道它是一个克隆体并且做出相应的反应
       使用 ls 覆盖自身
       控制权现在传递给 ls 
ls 
   为你打印一个目录列表或返回错误
   返回退出代码
   控制权现在传递给 Bash 
Bash 
   将 ls 退出代码赋给 ? 变量
   等待你的输入
你
   可以再次输入内容
```

一些进程不像`ls`那样交互，只是在后台静静地工作，就像`ssh`一样。进程有许多可能的状态，并且有许多操作，你可以通过信号机制对它们执行。

首先让我们谈论状态。如果你键入`ps ax -forest`，它将打印出所有进程，你会得到这样的东西（跳过一些与硬件有关的进程）：

```
user1@vm1:/etc$ ps --forest ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:16 init [2]
  297 ?        S<s    0:00 udevd --daemon
  392 ?        S<     0:00  \_ udevd --daemon
  399 ?        S<     0:00  \_ udevd --daemon
  691 ?        Ss     0:00 /sbin/portmap
  703 ?        Ss     0:00 /sbin/rpc.statd
  862 ?        Sl     0:00 /usr/sbin/rsyslogd -c4
  886 ?        Ss     0:00 /usr/sbin/atd
  971 ?        Ss     0:00 /usr/sbin/acpid
  978 ?        Ss     0:01 /usr/sbin/cron
 1177 ?        Ss     0:00 /usr/sbin/sshd
 6671 ?        Ss     0:00  \_ sshd: user1 [priv]
 6675 ?        S      0:00      \_ sshd: user1@pts/0
 6676 pts/0    Ss     0:00          \_ -bash
 7932 pts/0    R+     0:00              \_ ps --forest ax
 1191 ?        Ss     0:00 /usr/sbin/exim4 -bd -q30m
 1210 tty2     Ss+    0:00 /sbin/getty 38400 tty2
 1211 tty3     Ss+    0:00 /sbin/getty 38400 tty3
 1212 tty4     Ss+    0:00 /sbin/getty 38400 tty4
 1213 tty5     Ss+    0:00 /sbin/getty 38400 tty5
 1214 tty6     Ss+    0:00 /sbin/getty 38400 tty6
 6216 tty1     Ss+    0:00 /sbin/getty 38400 tty1
```

让我们浏览这个列表：

`PID` - 进程 ID。每个进程都有与之相关联的唯一编号，用于唯一标识它。这意味着没有两个进程可以拥有相同的 PID。
`TTY` - 与进程相关联的电传模拟器，允许进程与你交换信息。
`STAT` - 当前进程状态。这将在下面详细描述。
`TIME` - 这是在 CPU 上执行此进程的时间（以分钟为单位）。
`COMMAND` - 这是带有参数的程序名称。请注意`/usr/sbin/sshd`是`sshd: user1`的父进程，而`sshd: user1`又是`sshd: user1@pts/0`的父进程，而`sshd: user1@pts/0`又是`bash`的父进程，而`bash`又是`ps –forest ax`的父进程。你需要更深入一些！

现在让我们讨论可能的进程状态。你可以参考`man ps`的 PROCESS STATE CODES。

| 状态 | 描述 |
| --- | --- |
| D | 不中断睡眠（通常为 IO）。进程繁忙或挂起，不响应信号，例如硬盘已经崩溃，读操作无法完成。 |
| R | 运行或可运行（在运行队列中）。进程正在执行中。 |
| S | 中断睡眠（等待事件完成）。例如，终端进程和 Bash 通常处于此状态，等待你键入某些内容。 |
| T | 停止，由任务控制信号或由于被追踪。 |
| W | 分页（从 2.6.xx 内核起无效，所以不用担心）。 |
| X | 死亡（不应该看到）。 |
| Z | 已停止（“僵尸”）进程，已终止，但未被父项收回。这种情况发生在错误终止的进程上。 |
| < | 高优先级（对其他用户不好） |
| N | 低优先级（对其他用户很好） |
| L | 将页面锁定到内存中（用于实时和自定义 IO） |
| s | 是会话领导。Linux 中的相关进程被视为一个单元，并具有共享会话 ID（SID）。如果进程 ID（PID）= 会话 ID（SID），则此进程将是会话领导。 |
| L | 是多线程的（使用 CLONE_THREAD，例如 NPTL pthreads） |
| + | 位于前台进程组。这样的处理器允许输入和输出到电传模拟器，tty。 |

现在让我们讨论，与所有这些进程沟通的方式。你可以通过发送信号来实现它。信号可能是一种鞭策进程方式，所以进程会听你的话，并做出你想要的行为。这是可能的进程的缩略列表，我从`man kill`的`SIGNALS`部分获得：

| 信号 | 编号 | 行为 | 描述 |
| 0 | 0 | N/A | 退出代码表示是否可以发送信号 |
| HUP | 1 | 退出 | 控制终端挂起或父进程死亡 |
| INT | 2 | 退出 | 来自键盘的中断 |
| QUIT | 3 | 内核 | 来自键盘的退出 |
| ILL | 4 | 内核 | 非法指令 |
| TRAP | 5 | 内核 | 跟踪/断点捕获 |
| ABRT | 6 | 内核 | 来自`abort(3)`的中止信号 |
| FPE | 8 | 内核 | 浮点异常 |
| KILL | 9 | 退出 | 不可捕获，不可忽视的杀死 |
| SEGV | 11 | 内核 | 内存引用无效 |
| PIPE | 13 | 退出 | 损坏的管道：写入没有读者的管道 |
| ALRM | 14 | 退出 | 来自`alarm(2)`的定时器信号 |
| TERM | 15 | 退出 | 终止进程 |

同样，不要因为不理解而害怕，因为现在你只需要了解`HUP`，`TERM`和`KILL`。甚至有一首关于`KILL`信号的歌曲 ，命名为[《Kill dash nine》](http://www.monzy.com/intro/killdashnine_lyrics.html)。另外，注意到`abort(3)`和`alarm(2)`了么？这意味着你可以通过键入`man 3 abort`和`man 2 alarm`来阅读相应的手册页。

现在，你将学习如何列出正在运行的进程并向其发送信号。

## 这样做

```
 1: ps x
 2: ps a
 3: ps ax
 4: ps axue --forest
 5: dd if=/dev/zero of=~/test.img bs=1 count=$((1024*1024*1024)) &
 6: kill -s USR1 $!
 7: <ENTER>
 8: kill -s USR1 $!
 9: <ENTER>
10: kill -s TERM $!
11: <ENTER>
```

## 你会看到什么

```
user1@vm1:/etc$ ps x
  PID TTY      STAT   TIME COMMAND
 6675 ?        S      0:00 sshd: user1@pts/0
 6676 pts/0    Ss     0:00 -bash
 8193 pts/0    R+     0:00 ps x
user1@vm1:/etc$ ps a
  PID TTY      STAT   TIME COMMAND
 1210 tty2     Ss+    0:00 /sbin/getty 38400 tty2
 1211 tty3     Ss+    0:00 /sbin/getty 38400 tty3
 1212 tty4     Ss+    0:00 /sbin/getty 38400 tty4
 1213 tty5     Ss+    0:00 /sbin/getty 38400 tty5
 1214 tty6     Ss+    0:00 /sbin/getty 38400 tty6
 6216 tty1     Ss+    0:00 /sbin/getty 38400 tty1
 6676 pts/0    Ss     0:00 -bash
 8194 pts/0    R+     0:00 ps a
user1@vm1:/etc$ ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:16 init [2]
--- skipped --- skipped --- skipped ---
  691 ?        Ss     0:00 /sbin/portmap
  703 ?        Ss     0:00 /sbin/rpc.statd
  862 ?        Sl     0:00 /usr/sbin/rsyslogd -c4
  886 ?        Ss     0:00 /usr/sbin/atd
  971 ?        Ss     0:00 /usr/sbin/acpid
  978 ?        Ss     0:01 /usr/sbin/cron
 1177 ?        Ss     0:00 /usr/sbin/sshd
 1191 ?        Ss     0:00 /usr/sbin/exim4 -bd -q30m
 1210 tty2     Ss+    0:00 /sbin/getty 38400 tty2
 1211 tty3     Ss+    0:00 /sbin/getty 38400 tty3
 1212 tty4     Ss+    0:00 /sbin/getty 38400 tty4
 1213 tty5     Ss+    0:00 /sbin/getty 38400 tty5
 1214 tty6     Ss+    0:00 /sbin/getty 38400 tty6
 6216 tty1     Ss+    0:00 /sbin/getty 38400 tty1
 6671 ?        Ss     0:00 sshd: user1 [priv]
 6675 ?        S      0:00 sshd: user1@pts/0
 6676 pts/0    Ss     0:00 -bash
 8198 pts/0    R+     0:00 ps ax
user1@vm1:/etc$ ps axue --forest
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
--- skipped --- skipped --- skipped ---
root         1  0.0  0.0   8356   820 ?        Ss   Jun06   0:16 init [2]
root       297  0.0  0.0  16976  1000 ?        S<s  Jun06   0:00 udevd --daemon
root       392  0.0  0.0  16872   840 ?        S<   Jun06   0:00  \_ udevd --daemon
root       399  0.0  0.0  16872   836 ?        S<   Jun06   0:00  \_ udevd --daemon
daemon     691  0.0  0.0   8096   532 ?        Ss   Jun06   0:00 /sbin/portmap
statd      703  0.0  0.0  14384   868 ?        Ss   Jun06   0:00 /sbin/rpc.statd
root       862  0.0  0.1  54296  1664 ?        Sl   Jun06   0:00 /usr/sbin/rsyslogd -c4
daemon     886  0.0  0.0  18716   436 ?        Ss   Jun06   0:00 /usr/sbin/atd
root       971  0.0  0.0   3920   644 ?        Ss   Jun06   0:00 /usr/sbin/acpid
root       978  0.0  0.0  22400   880 ?        Ss   Jun06   0:01 /usr/sbin/cron
root      1177  0.0  0.1  49176  1136 ?        Ss   Jun06   0:00 /usr/sbin/sshd
root      6671  0.0  0.3  70496  3284 ?        Ss   Jun26   0:00  \_ sshd: user1 [priv]
user1     6675  0.0  0.1  70496  1584 ?        S    Jun26   0:00      \_ sshd: user1@pts/0
user1     6676  0.0  0.6  23644  6536 pts/0    Ss   Jun26   0:00          \_ -bash LANG=en_US.UTF-8 USER=user1 LOGNAME=user1 HOM
user1     8199  0.0  0.1  16312  1088 pts/0    R+   17:07   0:00              \_ ps axue --forest TERM=screen-bce SHELL=/bin/bas
101       1191  0.0  0.1  44148  1076 ?        Ss   Jun06   0:00 /usr/sbin/exim4 -bd -q30m
root      1210  0.0  0.0   5932   616 tty2     Ss+  Jun06   0:00 /sbin/getty 38400 tty2
root      1211  0.0  0.0   5932   612 tty3     Ss+  Jun06   0:00 /sbin/getty 38400 tty3
root      1212  0.0  0.0   5932   612 tty4     Ss+  Jun06   0:00 /sbin/getty 38400 tty4
root      1213  0.0  0.0   5932   612 tty5     Ss+  Jun06   0:00 /sbin/getty 38400 tty5
root      1214  0.0  0.0   5932   616 tty6     Ss+  Jun06   0:00 /sbin/getty 38400 tty6
root      6216  0.0  0.0   5932   612 tty1     Ss+  Jun14   0:00 /sbin/getty 38400 tty1
user1@vm1:/etc$ dd if=/dev/zero of=~/test.img bs=1 count=$((1024*1024*1024)) &
[1] 8200
user1@vm1:/etc$ kill -s USR1 $!
user1@vm1:/etc$ 1455424+0 records in
1455424+0 records out
1455424 bytes (1.5 MB) copied, 1.76646 s, 824 kB/s

user1@vm1:/etc$ kill -s USR1 $!
user1@vm1:/etc$ 3263060+0 records in
3263060+0 records out
3263060 bytes (3.3 MB) copied, 3.94237 s, 828 kB/s

user1@vm1:/etc$ kill -s TERM $!
user1@vm1:/etc$
[1]+  Terminated              dd if=/dev/zero of=~/test.img bs=1 count=$((1024*1024*1024))
user1@vm1:/etc$
```

## 解释

1.  打印你拥有（启动）的进程。
1.  仅打印与终端（`tty`）相关的进程和你拥有（启动）的进程。
1.  打印所有正在运行的进程。
1.  以树形式打印所有正在运行的进程，并包含附加信息，例如可用的相关用户名和环境。请注意，此信息仅适用于你拥有（启动）的进程。为了查看所有进程的环境信息，请输入`sudo ps axue -forest`。
1.  开始创建一个零填充文件（填充为空字符，现在不要纠结），并通过在末尾指定`&`发送到后台。
1.  查询`dd`的状态。
1.  因为 bash 只能打印一些东西来回应你的输入，你需要按`<ENTER>`（发出空指令）。
1.  再次查询`dd`的状态。
1.  同样，你需要按`<ENTER>`来查看输出。
1.  向`dd`发送终止信号，所以`dd`退出了。
1.  为了看到它确实发生了，你需要再次按`<ENTER>`键 。

## 附加题

+   阅读`man ps`，`man kill`。
+   阅读[进程的生命周期](http://www.linux-tutorial.info/modules.php?name=MContent&pageid=84)，并研究这张图片：[进程的工作流](http://www.linux-tutorial.info/Linux_Tutorial/The_Operating_System/The_Kernel/Processes/procflowa.gif)。
+   打印并填写[信号表](https://nixsrv.com/llthw/ex16/signals)。你可以使用 [kernel.org 中的文档](http://www.kernel.org/doc/man-pages/online/pages/man7/signal.7.html%23DESCRIPTION)。
