Linux htop/top 各项指标详解
目录
Ubuntu Server 16.04 x64 上的 htop
下面这张 htop 截图,就是我要讲解的对象:

运行时间(Uptime)
运行时间显示系统已持续运行的时长。你也可以通过执行 uptime 命令查看相同信息:
$ uptime
12:17:58 up 111 days, 31 min, 1 user, load average: 0.00, 0.01, 0.05
uptime 程序是怎么获取这些信息的?它会读取 /proc/uptime 文件的内容:
9592411.58 9566042.33
第一个数字是系统累计运行的秒数,第二个数字是系统处于空闲状态的总秒数。在多核系统中,第二个数值可能大于系统总运行时间,因为它是所有核心空闲时间的总和。
我是怎么知道这些的?我查看了 uptime 运行时会打开哪些文件,用到的工具是 strace:
strace uptime
命令会输出大量内容,我们可以用 grep 过滤出 open 系统调用,但因为 strace 的输出默认发往标准错误流(stderr),直接过滤不会生效,需要先把 stderr 重定向到标准输出流(stdout),用 2>&1 实现:
$ strace uptime 2>&1 | grep open
...
open("/proc/uptime", O_RDONLY) = 3
open("/var/run/utmp", O_RDONLY|O_CLOEXEC) = 4
open("/proc/loadavg", O_RDONLY) = 4
结果里就包含了我提到的 /proc/uptime 文件。其实也可以直接用 strace -e open uptime,不用再额外过滤。
既然直接读取文件就能拿到数据,为什么还需要 uptime 程序?因为 uptime 会把数据格式化为人类易读的形式,而原始的秒数更适合在脚本或程序中使用。
负载平均值(Load average)
除了运行时间,uptime 还会输出三个代表负载平均值的数字:
$ uptime
12:59:09 up 32 min, 1 user, load average: 0.00, 0.01, 0.03
这些数据来自 /proc/loadavg 文件,从刚才的 strace 输出里也能看到程序打开了这个文件:
$ cat /proc/loadavg
0.00 0.01 0.03 1/120 1500
前三个数字分别代表系统过去1分钟、5分钟、15分钟的平均负载;第四列是当前处于运行状态的进程数和系统总进程数;最后一列是最近一次分配的进程ID。
先从最后一个数字说起:每次启动新进程,系统都会给它分配一个递增的ID,除非ID被耗尽才会复用。ID为1的进程是 /sbin/init,它在系统启动时就会运行。
我们再看一次 /proc/loadavg 的内容,然后在后台启动 sleep 命令,此时会显示它的进程ID:
$ cat /proc/loadavg
0.00 0.01 0.03 1/123 1566
$ sleep 10 &
[1] 1567
这里的 1/123 表示当前有1个进程正在运行或等待运行,系统总共有123个进程。当你运行 htop 时看到只有1个运行中进程,那通常就是 htop 自身。
如果运行 sleep 30 再打开 htop,你会发现运行中进程还是只有1个——因为 sleep 并没有在运行,它处于休眠状态,也就是在等待某个事件触发。所谓“运行中进程”,指的是正在CPU上执行,或者正在排队等待CPU资源的进程。
如果执行 cat /dev/urandom > /dev/null(持续生成随机字节并写入一个不会被读取的特殊文件),你会看到运行中进程变成了2个:
$ cat /dev/urandom > /dev/null &
[1] 1639
$ cat /proc/loadavg
1.00 0.69 0.35 2/124 1679
现在有两个运行中进程(一个是生成随机数的进程,另一个是读取 /proc/loadavg 的 cat 进程),同时负载平均值也上升了。
负载平均值代表一段时间内的系统平均负载,它的计算对象是**运行中进程(正在执行或等待CPU)和不可中断状态进程(等待磁盘或网络IO)**的总数。
那负载平均值就是这三类进程在过去1、5、15分钟内的简单平均数吗?其实没这么简单。
负载平均值是负载数的指数衰减移动平均值。根据维基百科的解释:
从数学角度看,三个负载平均值实际上包含了系统启动以来的所有负载数据,只是衰减速率不同。1分钟负载平均值会纳入过去1分钟内63%的负载,再加上系统启动后除最后1分钟外37%的历史负载。所以严格来说,它并非只包含最近60秒的活动,但大部分权重还是落在最近1分钟。
是不是和你之前想的不一样?回到刚才生成随机数的例子:
$ cat /proc/loadavg
1.00 0.69 0.35 2/124 1679
虽然技术上不准确,但我习惯这样简化理解:这个生成随机数的进程是CPU密集型的,所以过去1分钟的负载平均值为1.00,意味着平均有1个运行中进程。
如果我的系统只有1个CPU核心,那CPU利用率就是100%——因为单核心同一时间只能运行一个进程;如果是双核心,CPU利用率就是50%,此时要达到100%的CPU利用率,负载平均值需要达到2.00。
你可以在 htop 左上角看到核心数,也可以执行 nproc 命令查看。
不过,负载数还包含不可中断状态的进程,这类进程对CPU利用率影响很小,所以像刚才那样直接用负载平均值推断CPU利用率并不完全准确。这也解释了为什么有时候负载平均值很高,但CPU却没什么压力。如果想查看实时CPU利用率,可以用 mpstat 工具:
$ sudo apt install sysstat -y
$ mpstat 1
Linux 4.4.0-47-generic (hostname) 12/03/2016 _x86_64_ (1 CPU)
10:16:20 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:16:21 PM all 0.00 0.00 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
10:16:22 PM all 0.00 0.00 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
10:16:23 PM all 0.00 0.00 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
# ...
# 终止 cat /dev/urandom 进程
# ...
10:17:00 PM all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
10:17:01 PM all 1.00 0.00 0.00 2.00 0.00 0.00 0.00 0.00 0.00 97.00
10:17:02 PM all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
那我们为什么还要用负载平均值呢?看看Linux内核源码里的这段注释就懂了:
$ curl -s https://raw.githubusercontent.com/torvalds/linux/v4.8/kernel/sched/loadavg.c | head -n 7
/*
* kernel/sched/loadavg.c
*
* This file contains the magic bits required to compute the global loadavg
* figure. Its a silly number but people think its important. We go through
* great pains to make it work on big machines and tickless kernels.
*/
进程(Processes)
htop 右上角会显示总进程数和正在运行的进程数,但它用的是“Tasks”而不是“Processes”,为什么?
“进程”的另一个称呼就是“任务”,Linux内核内部就把进程称为任务。htop 用“Tasks”可能只是因为更短,能节省屏幕空间。
你还可以在 htop 中查看线程,按 Shift+H 可以切换线程显示状态。如果看到“Tasks: 23, 10 thr”,就表示线程处于显示状态;按 Shift+K 可以查看内核线程,显示时会显示“Tasks: 23, 40 kthr”。
进程ID(PID)
每次启动新进程,系统都会给它分配一个唯一的标识号,即进程ID(Process ID,简称PID)。
如果在 bash 中后台运行程序(加 &),会显示方括号里的作业号和PID:
$ sleep 1000 &
[1] 12503
如果没看到,也可以用 bash 的 $! 变量,它会展开为最近一个后台进程的PID:
$ echo $!
12503
进程ID非常有用,可以用来查看进程详情,也可以控制进程。
procfs 是一个伪文件系统,用户态程序可以通过读取其中的文件获取内核信息。它通常挂载在 /proc/ 目录下,看起来和普通目录一样,可以用 ls 和 cd 浏览。
所有和某个进程相关的信息都存放在 /proc/<pid>/ 目录下:
$ ls /proc/12503
attr coredump_filter fdinfo maps ns personality smaps task
auxv cpuset gid_map mem numa_maps projid_map stack uid_map
cgroup cwd io mountinfo oom_adj root stat wchan
clear_refs environ limits mounts oom_score schedstat statm
cmdline exe loginuid mountstats oom_score_adj sessionid status
comm fd map_files net pagemap setgroups syscall
比如,/proc/<pid>/cmdline 会显示启动该进程的命令:
$ cat /proc/12503/cmdline
sleep1000$
看起来不太对?因为命令的各个参数之间是用 \0 字节分隔的,我们可以用 od 命令查看:
$ od -c /proc/12503/cmdline
0000000 s l e e p \0 1 0 0 0 \0
0000013
也可以把 \0 替换成空格或换行符:
$ tr '\0' '\n' < /proc/12503/cmdline
sleep
1000
$ strings /proc/12503/cmdline
sleep
1000
进程目录里还包含一些链接,比如 cwd 指向进程的当前工作目录,exe 指向进程对应的可执行文件:
$ ls -l /proc/12503/{cwd,exe}
lrwxrwxrwx 1 ubuntu ubuntu 0 Jul 6 10:10 /proc/12503/cwd -> /home/ubuntu
lrwxrwxrwx 1 ubuntu ubuntu 0 Jul 6 10:10 /proc/12503/exe -> /bin/sleep
htop、top、ps 等诊断工具,就是通过读取 /proc/<pid>/<file> 这类文件来获取进程详情的。
进程树
启动新进程时,发起启动操作的进程称为父进程,新进程则是它的子进程,这种关系会形成树状结构。
在 htop 中按 F5 就能查看进程层级关系,也可以用 ps 命令加 -f 参数:
$ ps f
PID TTY STAT TIME COMMAND
12472 pts/0 Ss 0:00 -bash
12684 pts/0 R+ 0:00 \_ ps f
或者用 pstree 命令:
$ pstree -a
init
├─atd
├─cron
├─sshd -D
│ └─sshd
│ └─sshd
│ └─bash
│ └─pstree -a
...
你可能好奇为什么很多进程的父进程是 bash 或 sshd,拿在 bash 里运行 date 举例:
bash通过fork系统调用创建一个自身的副本;- 再通过
exec系统调用把/bin/date程序加载到内存中; - 作为父进程的
bash会等待子进程执行完毕。
系统启动时会运行ID为1的 /sbin/init,它会启动SSH守护进程 sshd;当你通过SSH连接服务器时,sshd 会为会话创建一个进程,这个进程再启动 bash shell。
我喜欢在 htop 中同时打开进程树视图和线程显示,这样能更清晰地看到进程关系。
进程所属用户
每个进程都属于某个用户,用户用数字ID标识:
$ sleep 1000 &
[1] 2045
$ grep Uid /proc/2045/status
Uid: 1000 1000 1000 1000
可以用 id 命令根据数字ID查找用户名:
$ id 1000
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)
id 命令其实是从 /etc/passwd 和 /etc/group 文件中获取这些信息的,用 strace 就能看到:
$ strace -e open id 1000
...
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libnss_compat.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
open("/etc/group", O_RDONLY|O_CLOEXEC) = 3
...
这是因为名称服务切换(Name Service Switch,NSS)配置文件 /etc/nsswitch.conf 指定了用这些文件来解析名称:
$ head -n 9 /etc/nsswitch.conf
# ...
passwd: compat
group: compat
shadow: compat
compat(兼容模式)和 files 类似,但允许一些特殊条目;files 表示用户数据存储在文件中,由 libnss_files.so 加载。当然,用户数据也可以存储在其他数据库或服务中,比如轻量级目录访问协议(LDAP)。
/etc/passwd 和 /etc/group 是纯文本文件,负责将数字ID映射为人类可读的名称:
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
$ cat /etc/group
root:x:0:
adm:x:4:syslog,ubuntu
ubuntu:x:1000:
passwd 文件里怎么没有密码?其实密码存在 /etc/shadow 文件中:
$ sudo cat /etc/shadow
root:$6$mS9o0QBw$P1ojPSTexV2PQ.Z./rqzYex.k7TJE2nVeIVL0dql/:17126:0:99999:7:::
daemon:*:17109:0:99999:7:::
ubuntu:$6$GIfdqlb/$ms9ZoxfrUq455K6UbmHyOfz7DVf7TWaveyHcp.:17126:0:99999:7:::
这些乱码是什么意思?
$6$表示使用的密码哈希算法是sha512;- 后面跟着随机生成的盐(salt),用于防范彩虹表攻击;
- 最后是密码加盐后的哈希值。
你运行程序时,进程会以你的用户身份执行,即使可执行文件不属于你。如果想以 root 或其他用户身份运行程序,就需要用到 sudo:
$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)
$ sudo id
uid=0(root) gid=0(root) groups=0(root)
$ sudo -u ubuntu id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm)
$ sudo -u daemon id
uid=1(daemon) gid=1(daemon) groups=1(daemon)
如果想切换到其他用户执行多个命令,可以用 sudo bash 或 sudo -u user bash,这样就能以该用户身份使用shell了。
如果不想每次用 sudo 都输密码,可以把你的用户添加到 /etc/sudoers 文件中。试试执行:
$ echo "$USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-bash: /etc/sudoers: Permission denied
没错,只有root用户才能修改这个文件。那试试用 sudo:
$ sudo echo "$USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-bash: /etc/sudoers: Permission denied
怎么还是不行?因为这里只有 echo 命令是以root身份执行的,而追加内容到 /etc/sudoers 的操作还是以你的用户身份进行的。
通常有两种解决方法:
echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoerssudo bash -c "echo '$USER ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"
第一种方法中,tee -a 会把标准输入的内容追加到文件中,而我们是以root身份执行 tee 命令;第二种方法是直接以root身份运行bash,让它执行整个命令。注意这里的引号嵌套,它决定了 $USER 变量何时被展开。
查看 /etc/sudoers 文件开头,你会看到这样的提示:
$ sudo head -n 3 /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
这是个很重要的警告:编辑这个文件应该用 sudo visudo 命令,它会在保存前验证文件内容,防止你出错。如果不用 visudo 而改错了文件,可能会导致你无法使用 sudo,到时候连错误都没法修正!
再说说修改密码的事,用 passwd 命令就能修改,它会把密码保存到 /etc/shadow 文件中。这个文件很敏感,只有root用户有写入权限:
$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1122 Nov 27 18:52 /etc/shadow
那普通用户运行的 passwd 程序,怎么能写入这个受保护的文件呢?
我之前说过,启动进程时,进程会以你的用户身份运行,即使可执行文件的所有者是其他人。但通过修改文件权限,可以改变这个行为。看看 passwd 的权限:
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 54256 Mar 29 2016 /usr/bin/passwd
注意到那个 s 了吗?它是通过 sudo chmod u+s /usr/bin/passwd 设置的,表示执行这个程序时,会以文件所有者(这里是root)的身份启动。
可以用 find /bin -user root -perm -u+s 找出所有这类 setuid 可执行文件。同样,也可以对组设置类似的权限(g+s)。
进程状态
接下来看看 htop 中用字母 S 标识的进程状态列,它可能的取值有:
R 运行或可运行(在运行队列中)
S 可中断睡眠(等待事件完成)
D 不可中断睡眠(通常是等待IO)
Z 僵死(zombie)进程,已终止但未被父进程回收
T 被作业控制信号停止
t 被调试器跟踪时停止
X 死亡(通常不会看到)
我是按自己日常见到的频率排序的。注意用 ps 命令时,还会显示一些子状态,比如 Ss、R+、Ss+ 等:
$ ps x
PID TTY STAT TIME COMMAND
1688 ? Ss 0:00 /lib/systemd/systemd --user
1689 ? S 0:00 (sd-pam)
1724 ? S 0:01 sshd: vagrant@pts/0
1725 pts/0 Ss 0:00 -bash
2628 pts/0 R+ 0:00 ps x
R - 运行或可运行(在运行队列中)
处于这个状态的进程,要么正在CPU上执行,要么在运行队列中等待CPU资源。
什么是“运行”?你写的程序编译后会生成CPU能执行的机器码,保存为可执行文件;启动程序时,这些代码会被加载到内存,然后CPU执行这些指令——简单说就是CPU正在“ crunch numbers”(处理数据)。
S - 可中断睡眠(等待事件完成)
处于这个状态的进程,其代码没有在CPU上执行,而是在等待某个事件或条件发生。当事件触发时,内核会把它的状态改为运行。
比如coreutils里的 sleep 工具,它会休眠指定的秒数(近似值):
$ sleep 1000 &
[1] 10089
$ ps f
PID TTY STAT TIME COMMAND
3514 pts/1 Ss 0:00 -bash
10089 pts/1 S 0:00 \_ sleep 1000
10094 pts/1 R+ 0:00 \_ ps f
这是“可中断”睡眠,怎么中断它?可以给进程发信号。
在 htop 中按 F9,然后从左侧菜单选择一个信号即可。发送信号也叫“kill”,因为 kill 是一个能给进程发信号的系统调用;系统上还有 /bin/kill 程序,它可以在用户态调用这个系统调用,默认发送的是 TERM 信号,用于请求进程终止。
信号本质是数字,为了好记才给它们起了名字,通常是大写,有时会加 SIG 前缀。常用的信号有 INT、KILL、STOP、CONT、HUP。
我们给 sleep 进程发送 INT(即 SIGINT,信号2,终端中断信号)来中断它:
$ kill -INT 10089
[1]+ Interrupt sleep 1000
这和你按 CTRL+C 的效果一样——bash 会给前台进程发送 SIGINT 信号,就像我们刚才手动做的那样。
顺便说一下,bash 里的 kill 是内置命令,虽然大多数系统上也有 /bin/kill。为什么要做内置命令?因为当进程数达到上限时,内置命令依然能用来终止进程。
下面这些命令效果是一样的:
kill -INT 10089kill -2 10089/bin/kill -2 10089
另一个有用的信号是 SIGKILL(即信号9),你可能用它终止过那些对 CTRL+C 无响应的进程。
编写程序时,你可以设置信号处理函数,当进程收到信号时就会调用这些函数——也就是说,你可以捕获信号并做一些处理,比如清理资源后优雅退出。所以发送 SIGINT(用户想中断进程)或 SIGTERM(用户想终止进程),并不一定能让进程终止。
运行Python脚本时,你可能见过这个异常:
$ python -c 'import sys; sys.stdin.read()'
^C
Traceback (most recent call last):
File "<string>", line 1, in <module>
KeyboardInterrupt
如果想强制终止进程,不给它响应的机会,可以发送 KILL 信号:
$ sleep 1000 &
[1] 2658
$ kill -9 2658
[1]+ Killed sleep 1000
D - 不可中断睡眠(通常是等待IO)
和可中断睡眠不同,这类进程不能被信号唤醒,这也是很多人害怕看到这个状态的原因——你无法杀死它们,因为“杀死”本质是给进程发 SIGKILL 信号,而它们根本不会响应。
这个状态通常用于进程必须无中断等待,或者等待的事件很快就会发生的场景,比如读写磁盘,正常情况下只会持续几分之一秒。
StackOverflow上有个很好的解释[http://stackoverflow.com/questions/223644/what-is-an-uninterruptable-process]:
不可中断进程通常是在缺页后等待IO。进程处于这个状态时不能被中断,因为它无法处理任何信号——如果中断了,会再次触发缺页,回到之前的状态。
换句话说,当你使用网络文件系统(NFS)且读写耗时较长时,可能会出现这种状态;根据我的经验,进程大量交换内存(swap)时也会出现,这通常意味着可用内存不足。
我们来试试让进程进入不可中断睡眠状态:8.8.8.8 是谷歌提供的公共DNS服务器,上面没有开放NFS服务,但我们可以尝试挂载它:
$ sudo mount 8.8.8.8:/tmp /tmp &
[1] 12646
$ sudo ps x | grep mount.nfs
12648 pts/1 D 0:00 /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw
怎么知道它在等什么?用 strace!我们跟踪上面 ps 输出中的命令:
$ sudo strace /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw
...
mount("8.8.8.8:/tmp", "/tmp", "nfs", 0, ...
可以看到是 mount 系统调用阻塞了进程。如果你想让它变成可中断的,可以加 intr 参数:sudo mount 8.8.8.8:/tmp /tmp -o intr。
Z - 僵死(zombie)进程,已终止但未被父进程回收
当进程通过 exit 终止,但它的父进程还在运行时,子进程就会变成僵死进程。
- 僵死进程短时间存在是完全正常的;
- 长时间存在的僵死进程可能意味着程序有bug;
- 僵死进程不占用内存,只占用一个进程ID;
- 你无法“杀死”僵死进程;
- 可以给父进程发送
SIGCHLD信号,请求它回收僵死进程; - 杀死僵死进程的父进程,就能连带清除它的僵死子进程。
我写一段C代码来演示这个过程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("Running\n");
int pid = fork();
if (pid == 0) {
printf("I am the child process\n");
printf("The child process is exiting now\n");
exit(0);
} else {
printf("I am the parent process\n");
printf("The parent process is sleeping now\n");
sleep(20);
printf("The parent process is finished\n");
}
return 0;
}
先安装GNU C编译器(GCC):
sudo apt install -y gcc
编译并运行:
gcc zombie.c -o zombie
./zombie
查看进程树:
$ ps f
PID TTY STAT TIME COMMAND
3514 pts/1 Ss 0:00 -bash
7911 pts/1 S+ 0:00 \_ ./zombie
7912 pts/1 Z+ 0:00 \_ [zombie] <defunct>
1317 pts/0 Ss 0:00 -bash
7913 pts/0 R+ 0:00 \_ ps f
我们得到了一个僵死进程!当父进程运行完毕后,僵死进程就会消失:
$ ps f
PID TTY STAT TIME COMMAND
3514 pts/1 Ss+ 0:00 -bash
1317 pts/0 Ss 0:00 -bash
7914 pts/0 R+ 0:00 \_ ps f
如果把代码中的 sleep(20) 换成 while (true) ;,僵死进程会立刻消失。
进程调用 exit 后,它占用的内存和资源都会被释放,供其他进程使用。那为什么还要保留僵死进程呢?因为父进程可以通过 wait 系统调用(在信号处理函数中)获取子进程的退出码。如果父进程处于睡眠状态,就需要等它醒来才能处理。
为什么不直接强制唤醒父进程并清除僵死进程?就像你不会因为烦了就把孩子扔了一样,强制操作可能会导致不良后果。
T - 被作业控制信号停止
我打开两个终端窗口,用 ps u 查看我的用户进程:
$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 1317 0.0 0.9 21420 4992 pts/0 Ss+ Jun07 0:00 -bash
ubuntu 3514 1.5 1.0 21420 5196 pts/1 Ss 07:28 0:00 -bash
ubuntu 3528 0.0 0.6 36084 3316 pts/1 R+ 07:28 0:00 ps u
下面的输出我会省略 -bash 和 ps u 进程。
在一个终端运行 cat /dev/urandom > /dev/null,它的状态是 R+,表示正在运行:
$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3540 103 0.1 6168 688 pts/1 R+ 07:29 0:04 cat /dev/urandom
按 CTRL+Z 停止这个进程:
$ # 按 CTRL+Z
[1]+ Stopped cat /dev/urandom > /dev/null
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3540 86.8 0.1 6168 688 pts/1 T 07:29 0:15 cat /dev/urandom
它的状态变成了 T。在第一个终端运行 fg 就能恢复它的运行。
也可以用 kill 给进程发送 STOP 信号来停止它,发送 CONT 信号来恢复执行。
t - 被调试器跟踪时停止
先安装GNU调试器(gdb):
sudo apt install -y gdb
运行一个监听1234端口的程序:
$ nc -l 1234 &
[1] 3905
它处于睡眠状态,正在等待网络数据:
$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3905 0.0 0.1 9184 896 pts/0 S 07:41 0:00 nc -l 1234
启动调试器并附加到ID为3905的进程:
sudo gdb -p 3905
此时查看进程状态,会显示为 t,表示该进程正在被调试器跟踪:
$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3905 0.0 0.1 9184 896 pts/0 t 07:41 0:00 nc -l 1234
进程时间
Linux是多任务操作系统,即使只有一个CPU,也能同时运行多个进程——比如你可以通过SSH连接服务器查看 htop,同时你的Web服务器正在给读者提供博客内容。
单个CPU同一时间只能执行一条指令,这怎么实现多任务?答案是时间分片。
一个进程运行一小段时间后会被暂停,让等待的进程轮流运行,这段时间称为时间片(time slice)。时间片通常只有几毫秒,所以系统负载不高时你几乎察觉不到切换。(真想知道Linux中时间片通常是多长。)
这也能解释为什么负载平均值是运行中进程的平均数:如果是单核心,负载平均值为1.0意味着CPU利用率100%;如果负载平均值高于1.0,说明想要运行的进程数超过了CPU的处理能力,系统可能会变慢或出现延迟;如果低于1.0,说明CPU有时处于空闲状态。
这也能解释为什么一个运行了10秒的进程,其实际运行时间可能多于或少于10秒。
进程优先级(Niceness和Priority)
当需要运行的任务数超过可用CPU核心数时,就得决定先运行哪个任务、让哪个任务等待,这就是任务调度器的职责。
Linux内核的调度器负责从运行队列中选择下一个要执行的进程,具体逻辑取决于内核使用的调度算法。
你通常无法直接影响调度器,但可以告诉它哪些进程对你更重要,调度器可能会将其纳入考量。
**Niceness(NI,友好值)**是用户态的进程优先级,范围从-20(最高优先级)到19(最低优先级)。这个概念有点绕,你可以理解为:越“友好”的进程,越会主动让给其他进程资源——友好值越高,让出资源的意愿越强。
根据我在StackOverflow和其他网站查到的信息,友好值每增加1,进程获得的CPU时间会减少约10%。
**Priority(PRI,优先级)**是内核态使用的优先级,范围从0到139,其中0-99是实时优先级,100-139是用户态优先级。
你可以修改进程的友好值,内核会据此调整优先级,但你无法直接修改优先级。友好值和优先级的关系是:
PR = 20 + NI
所以当NI在-20到19之间时,PR的范围是0到39,对应内核态的100到139。
你可以在启动进程时设置友好值:
nice -n 友好值 程序名
也可以在进程运行时用 renice 修改:
renice -n 友好值 -p PID
CPU使用率的颜色含义:
- 蓝色:低优先级线程(nice > 0)
- 绿色:普通优先级线程
- 红色:内核线程
内存使用 - VIRT/RES/SHR/MEM
进程会产生一种“独占内存”的错觉,这是通过虚拟内存实现的。
进程不能直接访问物理内存,而是拥有自己的虚拟地址空间,内核会将虚拟内存地址转换为物理内存地址,或者将部分内存映射到磁盘。这就是为什么进程的内存占用看起来可能超过你实际安装的内存。
这里要说明的是,计算进程实际占用的内存并不简单——要不要算共享库?要不要算映射到磁盘的内存?不过内核提供了一些信息,htop 也会显示出来,帮助你估算内存使用情况。
内存使用的颜色含义:
- 绿色:已用内存
- 蓝色:缓冲区(Buffers)
- 橙色:缓存(Cache)
VIRT/VSZ - 虚拟内存大小
任务使用的虚拟内存总量,包括所有代码、数据、共享库,以及已交换到磁盘的页面和已映射但未使用的页面。
VIRT 是虚拟内存使用量,包含所有内容,包括内存映射文件。比如一个程序请求1GB内存但只使用了1MB,VIRT 会显示1GB;如果它用 mmap 映射了一个1GB的文件但从未使用,VIRT 也会显示1GB。大多数时候,这个数字没什么用。
RES/RSS - 常驻内存大小
任务使用的未交换到磁盘的物理内存。
RES 是常驻内存使用量,即当前实际在物理内存中的部分。虽然它比 VIRT 更能反映进程的内存使用情况,但要注意:
- 它不包括已交换到磁盘的内存;
- 部分内存可能和其他进程共享。
比如一个进程占用1GB内存,调用 fork() 后会生成两个进程,它们的 RES 都会显示1GB,但实际只占用1GB物理内存——因为Linux使用写时复制(copy-on-write)机制,只有当其中一个进程修改内存时,才会真正复制数据。
SHR - 共享内存大小
任务使用的共享内存量,反映了可能与其他进程共享的内存。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("Started\n");
sleep(10);
size_t memory = 10 * 1024 * 1024; // 10 MB
char* buffer = malloc(memory);
printf("Allocated 10M\n");
sleep(10);
for (size_t i = 0; i < memory/2; i++)
buffer[i] = 42;
printf("Used 5M\n");
sleep(10);
int pid = fork();
printf("Forked\n");
sleep(10);
if (pid != 0) {
for (size_t i = memory/2; i < memory/2 + memory/5; i++)
buffer[i] = 42;
printf("Child used extra 2M\n");
}
sleep(10);
return 0;
}
fallocate -l 10G
gcc -std=c99 mem.c -o mem
./mem
进程 消息 VIRT RES SHR
main Started 4200 680 604
main Allocated 10M 14444 680 604
main Used 5M 14444 6168 1116
main Forked 14444 6168 1116
child Forked 14444 5216 0
main Child used extra 2M 8252 1116
child Child used extra 2M 5216 0
TODO:我会完善这部分内容。
MEM% - 内存使用率
任务当前占用的物理内存占总可用物理内存的比例,计算方式是 RES 除以总内存。比如 RES 是400MB,总内存是8GB,那么 MEM% 就是 400/8192*100 = 4.88%。
系统进程详解
我们来看看htop截图中的进程列表,这些进程真的都需要吗?以下是我对Digital Ocean上全新Ubuntu Server 16.04.1 LTS x64 droplet启动进程的研究笔记。
初始状态

/sbin/init
/sbin/init(也叫init)负责协调系统启动的后续流程,为用户配置环境。init启动后,会成为所有自动启动进程的父进程或祖进程。
它是systemd吗?
$ dpkg -S /sbin/init
systemd-sysv: /sbin/init
是的,它就是systemd。如果杀死它会怎么样?什么都不会发生。
- https://wiki.ubuntu.com/SystemdForUpstartUsers
- https://www.centos.org/docs/5/html/5.1/Installation_Guide/s2-boot-init-shutdown-init.html
/lib/systemd/systemd-journald
/lib/systemd/systemd-journald是一个系统服务,负责收集和存储日志数据。它会创建并维护结构化、带索引的日志,数据源多种多样。
简单说:journald的主要变化是用一种专门优化的日志格式替代了纯文本日志文件,让系统管理员能更高效地查找相关日志,同时把集中式数据库日志的部分功能带到了单台机器上。
你应该用 journalctl 命令查询日志:
journalctl _COMM=sshd:查看sshd的日志journalctl _COMM=sshd -o json-pretty:以JSON格式查看sshd日志journalctl --since "2015-01-10" --until "2015-01-11 03:00":查看指定时间段的日志journalctl --since 09:00 --until "1 hour ago":查看从9点到1小时前的日志journalctl --since yesterday:查看昨天的日志journalctl -b:查看系统启动后的日志journalctl -f:实时跟踪日志journalctl --disk-usage:查看日志占用磁盘空间journalctl --vacuum-size=1G:清理日志,保留1GB以内的内容
功能相当强大。看起来无法移除或禁用这个服务,只能关闭日志功能。
- https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
- https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs
- https://www.loggly.com/blog/why-journald/
- https://ask.fedoraproject.org/en/question/63985/how-to-correctly-disable-journald/
/sbin/lvmetad -f
/sbin/lvmetad -f是LVM元数据缓存守护进程,让LVM命令无需扫描磁盘就能读取元数据。元数据缓存的优势在于,扫描磁盘既耗时又可能干扰系统和磁盘的正常工作。
那什么是LVM(逻辑卷管理)?你可以把LVM理解为“动态分区”,它允许你在系统运行时通过命令行创建、调整大小或删除LVM“分区”(LVM术语中叫“逻辑卷”),无需重启系统让内核识别新分区或调整后的分区。
如果你在用LVM,就应该保留这个进程。
$ lvscan
$ sudo apt remove lvm2 -y --purge
- http://manpages.ubuntu.com/manpages/xenial/man8/lvmetad.8.html
- http://askubuntu.com/questions/3596/what-is-lvm-and-what-is-it-used-for
/lib/systemd/udevd
/lib/systemd/udevd监听内核的uevent事件,对每个事件执行udev规则中指定的匹配操作。udev是Linux内核的设备管理器,作为devfsd和hotplug的继任者,主要负责管理 /dev 目录下的设备节点。
简单说,这个服务负责管理 /dev 目录。我不确定虚拟服务器是否需要它运行。
- https://www.freedesktop.org/software/systemd/man/systemd-udevd.service.html
- https://wiki.archlinux.org/index.php/udev
/lib/systemd/timesyncd
/lib/systemd/timesyncd是一个系统服务,用于将本地系统时钟与远程网络时间协议(NTP)服务器同步,它取代了 ntpd。
$ timedatectl status
Local time: Fri 2016-08-26 11:38:21 UTC
Universal time: Fri 2016-08-26 11:38:21 UTC
RTC time: Fri 2016-08-26 11:38:20
Time zone: Etc/UTC (UTC, +0000)
Network time on: yes
NTP synchronized: yes
RTC in local TZ: no
看看服务器的开放端口:
$ sudo netstat -nlput
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 2178/sshd
tcp6 0 0 :::22 :::* LISTEN 2178/sshd
很干净!而在Ubuntu 14.04中是这样的:
$ sudo apt-get install ntp -y
$ sudo netstat -nlput
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1380/sshd
tcp6 0 0 :::22 :::* LISTEN 1380/sshd
udp 0 0 10.19.0.6:123 0.0.0.0:* 2377/ntpd
udp 0 0 139.59.256.256:123 0.0.0.0:* 2377/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 2377/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 2377/ntpd
udp6 0 0 fe80::601:6aff:fxxx:123 :::* 2377/ntpd
udp6 0 0 ::1:123 :::* 2377/ntpd
udp6 0 0 :::123 :::* 2377/ntpd
对比之下,timesyncd简洁多了。
- https://www.freedesktop.org/software/systemd/man/systemd-timesyncd.service.html
- https://wiki.archlinux.org/index.php/systemd-timesyncd
/usr/sbin/atd -f
/usr/sbin/atd -f负责运行用 at 命令排队的任务,at 和 batch 从标准输入或指定文件读取命令,在指定时间执行。
和定期执行任务的cron不同,at 只在指定时间执行一次任务。
$ echo "touch /tmp/yolo.txt" | at now + 1 minute
job 1 at Fri Aug 26 10:44:00 2016
$ atq
1 Fri Aug 26 10:44:00 2016 a root
$ sleep 60 && ls /tmp/yolo.txt
/tmp/yolo.txt
我之前从未用过它,可以移除:
sudo apt remove at -y --purge
- http://manpages.ubuntu.com/manpages/xenial/man8/atd.8.html
- http://manpages.ubuntu.com/manpages/xenial/man1/at.1.html
- http://askubuntu.com/questions/162439/why-does-ubuntu-server-run-both-cron-and-atd
/usr/lib/snapd/snapd
Snappy Ubuntu Core是Ubuntu的一个新版本,支持事务性更新——它是一个最小化服务器镜像,使用的库和当前Ubuntu相同,但应用通过更简单的机制提供。
具体来说,来自多个Linux发行版和公司的开发者宣布合作推出“snap”通用Linux包格式,让单个二进制包能在任何Linux桌面、服务器、云或设备上完美且安全地运行。
显然,它是一种简化的deb包,你可以把所有依赖打包到一个snap包中分发。我从未在服务器上用snappy部署或分发应用,可以移除:
sudo apt remove snapd -y --purge
- https://developer.ubuntu.com/en/snappy/
- https://insights.ubuntu.com/2016/06/14/universal-snap-packages-launch-on-multiple-linux-distros/
/usr/bin/dbus-daemon
D-Bus(或DBus)是一种进程间通信(IPC)和远程过程调用(RPC)机制,允许同一台机器上同时运行的多个程序之间通信。
我的理解是,桌面环境需要它,但运行Web应用的服务器呢?
sudo apt remove dbus -y --purge
移除后我想看看时间是否还在通过NTP同步:
$ timedatectl status
Failed to create bus connection: No such file or directory
糟糕,看来还是得保留它。
/lib/systemd/systemd-logind
/lib/systemd/systemd-logind是管理用户登录的系统服务。
/usr/sbin/cron -f
/usr/sbin/cron -f是执行定时命令的守护进程(Vixie Cron),-f 参数表示保持前台运行,不转为守护进程。
你可以用cron定期执行任务,用 crontab -e 编辑当前用户的定时任务,在Ubuntu上我也常用 /etc/cron.hourly、/etc/cron.daily 等目录。
查看cron日志的方式:
grep cron /var/log/syslogjournalctl _COMM=cronjournalctl _COMM=cron --since="日期" --until="日期"
你可能想保留cron,但如果不想用,应该先停止并禁用服务:
sudo systemctl stop cron
sudo systemctl disable cron
否则用 apt remove cron 移除时,它会尝试安装postfix!
$ sudo apt remove cron
The following packages will be REMOVED:
cron
The following NEW packages will be installed:
anacron bcron bcron-run fgetty libbg1 libbg1-doc postfix runit ssl-cert ucspi-unix
这是因为cron需要邮件传输代理(MTA)来发送邮件:
$ apt show cron
Package: cron
Version: 3.0pl1-128ubuntu2
...
Suggests: anacron (>= 2.0-1), logrotate, checksecurity, exim4 | postfix | mail-transport-agent
$ apt depends cron
cron
...
Suggests: anacron (>= 2.0-1)
Suggests: logrotate
Suggests: checksecurity
|Suggests: exim4
|Suggests: postfix
Suggests: <mail-transport-agent>
...
exim4-daemon-heavy
postfix
- https://help.ubuntu.com/community/CronHowto
- https://www.digitalocean.com/community/tutorials/how-to-use-cron-to-automate-tasks-on-a-vps
- http://unix.stackexchange.com/questions/212355/where-is-my-logfile-of-crontab
/usr/sbin/rsyslogd -n
/usr/sbin/rsyslogd -n是提供日志记录支持的系统工具,负责填充 /var/log/ 目录下的日志文件,比如记录SSH登录等认证信息的 /var/log/auth.log。
它的配置文件在 /etc/rsyslog.d 目录下,你还可以配置rsyslogd将日志发送到远程服务器,实现集中式日志管理。
在后台脚本(比如启动脚本)中,可以用 logger 命令将日志写入 /var/log/syslog:
#!/bin/bash
logger Starting doing something
# 执行NFS操作、获取IP等
logger Done doing something
不过我们已经在运行 systemd-journald 了,还需要 rsyslogd 吗?
系统上的两个日志应用rsyslog和journal各有特点,适用于不同场景。很多时候结合两者的功能会很有用,比如创建结构化消息并存储到文件数据库中,它们之间的通信接口由rsyslog的输入输出模块和journal的通信套接字提供。
所以可能还是需要保留它,以防万一。
- http://manpages.ubuntu.com/manpages/xenial/man8/rsyslogd.8.html
- http://manpages.ubuntu.com/manpages/xenial/man1/logger.1.html
- https://wiki.archlinux.org/index.php/rsyslog
- https://www.digitalocean.com/community/tutorials/how-to-centralize-logs-with-rsyslog-logstash-and-elasticsearch-on-ubuntu-14-04
- https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/s1-interaction_of_rsyslog_and_journal.html
/usr/sbin/acpid
/usr/sbin/acpid是高级配置与电源接口(ACPI)事件守护进程,负责将ACPI事件通知给用户态程序。默认情况下,它会在系统启动时启动并在后台运行。
ACPI是一个开放标准,操作系统可以用它发现和配置计算机硬件组件、进行电源管理(比如让闲置组件休眠)以及监控状态。
但我用的是虚拟服务器,不打算暂停或恢复它,出于好奇我移除了它:
sudo apt remove acpid -y --purge
我成功重启了droplet,但执行 halt 后Digital Ocean显示服务器还在运行,我不得不通过网页界面手动关机。看来还是应该保留它。
- http://manpages.ubuntu.com/manpages/xenial/man8/acpid.8.html
- https://en.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface
/usr/bin/lxcfs /var/lib/lxcfs/
/usr/bin/lxcfs /var/lib/lxcfs/是一个FUSE文件系统,主要为LXC容器设计。在Ubuntu 15.04系统中,它默认用于提供两个功能:一是虚拟化部分 /proc 文件的视图,二是提供对主机cgroup文件系统的过滤访问。
简单说,在15.04主机上,你可以像往常一样创建容器(lxc-create ...),生成的容器中运行 uptime、top 等命令时会显示“正确”的结果。它本质是一个用户态 workaround,解决了一些认为不适合在内核中修改的问题,让容器感觉更像独立的系统。
如果不用LXC容器,可以移除它:
sudo apt remove lxcfs -y --purge
- https://insights.ubuntu.com/2015/03/02/introducing-lxcfs/
- https://www.stgraber.org/2016/03/31/lxcfs-2-0-has-been-released/
/usr/lib/accountservice/accounts-daemon
/usr/lib/accountservice/accounts-daemon提供了一组D-Bus接口,用于查询和修改用户账户信息,其实现基于 usermod(8)、useradd(8) 和 userdel(8) 命令。
之前移除DBus导致 timedatectl 失效,我想知道移除这个服务会破坏什么:
sudo apt remove accountsservice -y --purge
拭目以待。
/sbin/mdadm
/sbin/mdadm是用于管理和监控软件RAID设备的Linux工具,名称来源于它管理的md(multiple device,多设备)节点,取代了之前的mdctl工具,最初叫“Mirror Disk”,后来随着功能扩展改了名。
RAID是一种将多个硬盘组合使用的方法,主要有两个用途:1. 扩展存储容量:比如RAID 0,两个500GB硬盘组合后总容量为1TB;2. 防止硬盘故障导致数据丢失:比如RAID 1、RAID 5、RAID 6和RAID 10。
如果不用RAID,可以移除:
sudo apt remove mdadm -y --purge
- https://en.wikipedia.org/wiki/Mdadm
- https://help.ubuntu.com/community/Installation/SoftwareRAID
- http://manpages.ubuntu.com/manpages/xenial/man8/mdadm.8.html
/usr/lib/policykit-1/polkitd --no-debug
/usr/lib/policykit-1/polkitd --no-debug是PolicyKit守护进程,PolicyKit是一个授权框架。
我的理解是,它就像细粒度的sudo,可以允许非特权用户以root身份执行某些操作,比如在桌面Linux上重启电脑。但我用的是服务器,可以移除:
sudo apt remove policykit-1 -y --purge
还是不确定会不会破坏什么。
- http://manpages.ubuntu.com/manpages/xenial/man8/polkitd.8.html
- http://manpages.ubuntu.com/manpages/xenial/man8/polkit.8.html
- http://www.admin-magazine.com/Articles/Assigning-Privileges-with-sudo-and-PolicyKit
- https://wiki.archlinux.org/index.php/Polkit#Configuration
/usr/sbin/sshd -D
/usr/sbin/sshd -D是OpenSSH守护进程,-D 参数表示不脱离终端,不转为守护进程,方便监控。
/sbin/iscsid
/sbin/iscsid是在后台运行的守护进程,负责处理iSCSI配置并管理连接。根据它的手册:iscsid实现了iSCSI协议的控制路径,还提供一些管理功能,比如可以根据持久化iSCSI数据库的内容,在启动时自动重新发现设备。
我之前从未听说过iSCSI:它是Internet小型计算机系统接口的缩写,是一种基于IP的存储网络标准,用于连接数据存储设施。通过在IP网络上传输SCSI命令,iSCSI可以在局域网、广域网或互联网上传输数据,实现独立于位置的数据存储和检索。
该协议允许客户端(称为发起方)向远程服务器上的SCSI存储设备(称为目标方)发送SCSI命令(CDB),是一种存储区域网络(SAN)协议,允许企业将存储整合到数据中心的存储阵列中,同时让主机(比如数据库和Web服务器)感觉像是连接了本地磁盘。
如果不用iSCSI,可以移除:
sudo apt remove open-iscsi -y --purge
/sbin/agetty --noclear tty1 linux
/sbin/agetty --noclear tty1 linux是Linux的替代getty程序,getty是“get tty”的缩写,是运行在主机上的Unix程序,负责管理物理或虚拟终端(TTY)。当检测到连接时,它会提示输入用户名并运行 login 程序进行认证。
最初,在传统Unix系统中,getty处理连接到主机的串行终端(通常是电传打字机)。名称中的tty原本指电传打字机,但现在泛指任何文本终端。
它允许你在物理上接触服务器时登录,在Digital Ocean中,你可以点击droplet详情中的“Console”,通过浏览器与这个终端交互(我认为是VNC连接)。
过去,系统启动时会启动多个tty(配置在 /etc/inittab 中),但现在由systemd按需启动。
出于好奇,我删除了启动 agetty 的配置文件:
sudo rm /etc/systemd/system/getty.target.wants/[email protected]
sudo rm /lib/systemd/system/getty@.service
重启服务器后,我仍然可以通过SSH连接,但无法通过Digital Ocean网页控制台登录了。

- http://manpages.ubuntu.com/manpages/xenial/man8/getty.8.html
- https://en.wikipedia.org/wiki/Getty_(Unix
- http://0pointer.de/blog/projects/serial-console.html
- http://unix.stackexchange.com/questions/56531/how-to-get-fewer-ttys-with-systemd
sshd: root@pts/0 & -bash & htop
sshd: root@pts/0 表示用户 root 通过SSH建立了一个会话,使用的是第0个伪终端(pts),伪终端用于模拟真实的文本终端。
bash 是我正在使用的shell,为什么前面有个短横线?Reddit用户hirnbrot给出了很有用的解释:
前面的短横线表示它是一个登录shell。登录shell指的是参数零的第一个字符是 -,或者用 --login 选项启动的shell,这种shell会读取不同的配置文件。
htop 就是截图中正在运行的交互式进程查看工具。
精简后状态
执行以下命令移除不需要的进程:
sudo apt remove lvm2 -y --purge
sudo apt remove at -y --purge
sudo apt remove snapd -y --purge
sudo apt remove lxcfs -y --purge
sudo apt remove mdadm -y --purge
sudo apt remove open-iscsi -y --purge
sudo apt remove accountsservice -y --purge
sudo apt remove policykit-1 -y --purge
精简后的htop截图:

极致精简版
再执行以下命令:
sudo apt remove dbus -y --purge
sudo apt remove rsyslog -y --purge
sudo apt remove acpid -y --purge
sudo systemctl stop cron && sudo systemctl disable cron
sudo rm /etc/systemd/system/getty.target.wants/[email protected]
sudo rm /lib/systemd/system/getty@.service
极致精简后的截图:

我按照博客文章《Ubuntu Server上无人值守安装WordPress》中的步骤操作,系统依然能正常运行,nginx、PHP7和MySQL都工作正常:

附录
查看源代码
有时候只用 strace 还不够,另一种了解程序功能的方法是查看源代码。首先得知道从哪里找:
$ which uptime
/usr/bin/uptime
$ dpkg -S /usr/bin/uptime
procps: /usr/bin/uptime
我们发现 uptime 位于 /usr/bin/uptime,在Ubuntu中它属于 procps 包。你可以去packages.ubuntu.com搜索这个包,找到 procps 的页面:http://packages.ubuntu.com/source/xenial/procps。
滚动到页面底部,就能看到源代码仓库的链接:
- Debian包源码仓库:git://git.debian.org/collab-maint/procps.git
- 可浏览的Debian包源码仓库:https://anonscm.debian.org/cgit/collab-maint/procps.git/
文件描述符与重定向
把标准错误(stderr)重定向到标准输出(stdout),应该写 2&>1 还是 2>&1?
你可以这样记:echo something > file 会把 something 写入文件 file,这和 echo something 1> file 是一样的;echo something 2> file 则是把stderr输出写入文件。如果写 echo something 2>1,意思是把stderr重定向到名为 1 的文件,加个空格更清楚:echo something 2> 1。如果在 1 前面加 &,就表示 1 不是文件名而是流ID,所以正确写法