在 Linux 系统中,特别是没有 GUI 的发行版本里,与进程打交道是一件家常便饭的事情,能不能把进程管理玩溜了,完全可以决定你 Linux 系统的使用体验。今天,我们来看一看常见的进程管理工具和技巧。

使用 ps 命令查看进程

ps 命令是 process status 的简称,用于显示当前运行的进程的信息。

在不使用任何标识的情况下,ps 会显示所有被当前用户启动的进程,比如:

1
2
3
4
$ ps
PID TTY TIME CMD
20094 ttys000 0:00.11 -bash
3086 ttys002 0:00.68 -bash

其中每一列代表:

  • PID:进程的 ID 号
  • TTY:命令执行时所使用的终端 ID
  • TIME:进程已经运行的时长
  • CMD:所执行的命令名称

ps 一个十分常见的用法是 ps aux,可以用来显示系统中运行的所有进程,同时也可以提供更多的信息,比如哪个用户启动了该进程、多少 CPU 和内存被占用了等等。

ps auxgrep 命令通过管道连接来查找特定的进程也是非常常见的用法,例如如果我们想要看见 SSH 服务 sshd 是否正在运行,我们可以使用下面的命令:

1
2
$ ps aux | grep sshd
root 2848 ... /usr/sbin/sshd -D

... 省略了一些信息来来简化上面的输出,其中应该包含了内存占用运行时间等等,root 是执行进程的用户,2848 是进程的 ID。

进程树

每一个进程都是被别的进程启动的,或者说是复刻(Fork)的。当系统刚刚启动的时候,有一个非常特别的根进程 init ,它就是是直接被操作系统内核启动的。

这样一来,这个系统中运行的所有进程集合就构成了一颗以 init 进程为根节点的进程树,所有的进程都有一个父进程,也有可能有多个子进程。

比方说,每次我们在 bash 命令行提示符下执行一个命令的时候,bash复刻一个进的进程来执行这个命令,这时这个进程就变成了 bash 的子进程了。

相似地,当我们看见一个「登录」提示符时,这其实是 login 命令在运行着。如果我们成功的登录了,login 命令会复刻一个新的进程来执行登录用户选择的 shell。

ps 命令展示了一个扁平化的进程列表,但是我们可以使用 pstree 命令来查看树形结构的进程列表,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ pstree
init─┬─atd
├─cron
├─dbus-daemon
├─dhclient
├─getty
├─mysqld_safe───mysql
├─nginx───nginx
├─ntpd
├─rsyslogd───3*[{rsyslogd}]
├─ruby─┬─4*[ruby───{ruby}]
│ └─{ruby}
├─sshd─┬─sshd───bash
│ └─bash───pstree
├─systemd-logind
├─systemd-udevd
├─upstart-file-br
├─upstart-socket-
└─upstart-udev-br

我们还可以通过 ps auxf 来查看树形结构的显示,只是效果上可能不那么用户友好罢了。

进程归属权

每一个进程都归属于某个特定的用户,归属于该用户的进程有权限像该用户的直接登录了一样执行所有该用户可以执行的所有命令。

比方说,假如有一个进程归 kchen 用户所有,那么这个进程就可以做所有 kchen 用户能做的事情了:编辑 kchen 用户 home 目录下的文件,启动一个归属于 kchen 用户的新进程,等等。

系统进程比如 initlogin 归属于 root 用户,而且当一个根进程复刻一个新进程的时候,它可以改变这个子进程的归属。

所以,当我们登录后, login 命令会复刻一个新的进程我运行我们的 shell,但是新的进程是所属于成功登陆的那个用户名的。接下来所有的后续命令都会以该用户的名义执行,所启动的进程都归属于他。

默认情况下,只有 root 进程可以像这样改变归属权。

什么是 Init System

操作系统内核初始化进程中所做的最后一件事情就是启动「init system」,也就是执行 /sbin/init 命令。「init system」有很多种,但它们都有相同的职责:

  1. 控制哪些服务在系统启动时跟随启动
  2. 提供可以开启、停止服务的工具,并且给出服务状态信息总览
  3. 提供一个可以编写新的服务的框架

这里的服务涵盖了从 web 服务器到用来管理登录的系统级服务器在内的所有服务。基本上,一个「init system」的工作就是让所有面向用户(即非内核)的程序和服务运行起来。

(1)-(3) 中设计的特定命令和工具会因不同的「init system」而各有不同。Linux 系统历史上最通用的一个「init system」叫做「System V Init」,它是以极具影响力的 UNIX SYSTEM V 来命名的。在现在 Linux 系统中,同时被 CentOS、RedHad、Debian、Ubuntu 等等主流发行版本所采用的「init system」叫做「systemd init system」。

有两点需要铭记:

  1. 不同的 Linux 发行版本可以使用不同的「init system」
  2. 同一 Linux 发型版本的不同版本号可以使用不同的「init system」

例如,Ubuntu 在 v15.04 中开始使用 systemd 作为默认的「init system」。

终止进程

很多时候,我们会想要终止那些占用了过多资源或者未响应的进程,两个常见的终止进程的命令是 killkillall

kill 命令使用进程号 PID 来终止进程,我们可以通过 ps 命令来获取进程号killall 命令通过给出进程名来终止进程,该命令会终止该进程名下的所有进程。

如果我们的 ps 命令有如下输出:

1
2
3
4
$ ps
PID TTY TIME CMD
20735 ttys000 0:00.10 -bash
3086 ttys002 0:00.70 -bash

现在有两个 bash 进程正在运行,一个的 PID 是 20735,另一个的 PID 是 3086。执行 kill 3086 会促使操作系统发送「graceful shutdown」信号给这个 bash 进程,但是如果执行 killall bash 命令,则会让操作系统终止所有 bash 进程。

发送不同的信号

默认情况下,killkillall 命令会发送 TERM 信号给特定的进程。TERM 信号是一个「优雅」的终止信号,进程收到这个信号时会以合适的方式处理和结束进程。比如,被终止的进程可能想要在终止之前完成当前的任务、或者是清理可能会残留在系统中的临时文件等等。

如果一个进程有漏洞导致它已经不能响应 TERM 信号了,这种情况下我们就只能发送另一个比较激进的信号了,KILL。有两种方法可以发送这个信号:

  1. kill -KILL 3086
  2. kill -9 3086

所有的信号都有一个数字编号,KILL 就是 9

kill -9 或者 killall -9 指令都是非常激进了,粗略地等同于直接拔掉计算机的电源。像这样来终止进程可能会留下一堆麻烦,只不过如果进程真的不响应了,也没啥别的办法。

所以,在使用 kill -9 PID 之前,一定要先尽量尝试使用 kill PID 才是。

使用 top 来查看资源占用

ps 命令会给出一个当前运行进程的快照,而 top 命令则是给出一个持续更新可排序的进程列表。top 命令常常被用来查看那个进程占用了最多的 CPU 或者内存。

下面是一些根据内存占用排序的 top 输出,用 ... 来做了部分省略:

1
2
3
4
PID   USER   ...  %CPU %MEM    TIME+ COMMAND
14944 kchen ... 0.0 9.5 17:03.55 ruby
14947 kchen ... 0.0 9.2 17:03.19 ruby
3164 mysql ... 0.3 2.5 13:31.83 mysqld

Linux 有这写通用的命令来操作 top 的输出结果:

  1. P (大写)可以按照 CPU 占用来排序
  2. M (大写)可以按照内存占用来排序
  3. < 将排序列左移一栏
  4. > 将排序列右移一栏
  5. q 退出 top

你可以通过阅读 man top 来获取更多关于如何配置 top 的显示,也可以使用 htop 这个程序来实现更为强大的显示和功能效果。