Linux性能利器Htop:完胜top、strace(中)
长久以来,我只知Linux有个神器htop,却不知道htop的各项指标的内涵。
比如,2核的服务器 CPU利用率为 50%,那为啥load average 却显示 1.0?那接下来开始捋捋。。。
- Process state
接下来看下htop中进程状态列,其用字母 S 表示。
下面是进程列的可能取值:
R | 运行状态(running)或者运行队列中的就绪状态(runnable) |
---|---|
S | 中断睡眠(等待事件完成) |
D | 非中断睡眠(常为IO) |
Z | 僵尸进程,无效进程但是未被父进程回收 |
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是在执行指令,换句话说,处理数字。
S - 中断睡眠
这意味着该进程的指令不能在CPU上立即执行。相反地,该进程等待某个事件或者条件发生。当事件发生,系统内核设置进程状态为运行状态。
本例是GNU的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
。让我们发送
INT
(也称作,SIGINT
或者2
或者 Terminal interrupt
)中断睡眠。$ kill -INT 10089[1]+ Interrupt sleep 1000
当你在键盘上敲击
CTRL
+C
也会产生上面同样的效果。 bash
将发送前台进程 SIGINT
信号。顺便提一下,在
bash
中,虽然大部分操作系统都有 /bin/kill
,但是 kill
是一个内建命令。这是为什么呢?如果你创建的进程达到限制条件,它允许进程被kill。实现相同功能的命令:
kill -INT 10089
kill -2 10089
/bin/kill -2 10089
另外一个有用的信号是
SIGKILL
(也被称作 9
)。你可以使用该信号kill掉不响应的进程,省的你发狂的按 CTRL
+C
键盘。当编写程序时,你能设置信号handler函数,该函数将在进程收到信号时被调用。换句话说,你能捕获信号,然后做点什么事。例如,清理或者优雅的关闭进程。所以发送
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 -92658[1]+ Killed sleep 1000
D - 非中断睡眠
不像中止睡眠进程那么简单,你不能用信号唤醒该进程。这就是为什么许多人喊怕看到这个状态。你不能kill该进程,因为kill意味着通过发送
SIGKILL
信号给该进程。如果进程必须等待不中断或者事件被期望快速发生,那这个状态被使用,比如读写磁盘。但是这仅仅发生一秒分之一。
引用自StackOverflow
不中断进程经常等到I/O出现页缺失(page fault)。进程/任务不能在这种状态下中断,因为它不能处理任何信号;如果中断了,另外一个页缺失将会发生,会返回到原始位置。
换句话说,如果你在使用NFS(网络文件系统)时出现中断,那得好久才恢复。
或者,以我的经验看,意味着进程正在交换许多小内存。
让我们试着一个进程进入不中断睡眠。
8.8.8.8
是Google提供的公共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
!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
选项的 mount
命令来中断: sudo mount 8.8.8.8:/tmp /tmp -o intr
。Z - 僵尸进程,无效进程但是未被父进程回收
当一个进程以
exit
退出时,它还有子进程,此时子进程变成了僵尸进程。- 如果僵尸进程存在一小会,那相当正常;
- 僵尸进程存在很长时间可能导致程序bug;
- 僵尸进程不消耗内存,仅仅是一个进程ID;
- 僵尸进程不能被
kill
; - 发生
SIGCHLD
信号能让父进程回收僵尸进程; kill
僵尸进程的父进程能摆脱父进程和其僵尸进程
下面写个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"); }
return0; }
安装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
如果用
while (true) ;
代替 sleep(20)
,僵尸进程将正常退出。使用
exit
时,该进程所有的内存和资源被释放,其它的进程可以继续使用。为什么要保留僵尸进程存在呢?
父进程使用
wait
系统调用找出其子进程退出码(信号 handler)。如果一个进程睡眠,它需要等待唤醒。为什么不简单的强制进程唤醒和kill掉?当你厌倦小孩时,你不会把他扔垃圾桶。这里的原因相同。坏事情总会发生的。
T - 被控制信号停止
打开两个终端窗口,使用
ps u
能查看到用户的进程:$ ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ubuntu 13170.00.9214204992 pts/0 Ss+ Jun07 0:00 -bash ubuntu 35141.51.0214205196 pts/1 Ss 07:280:00 -bash ubuntu 35280.00.6360843316 pts/1 R+ 07:280: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 35401030.16168688 pts/1 R+ 07:290: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 354086.80.16168688 pts/1 T 07:290:15 cat /dev/urandom
该进程的状态现在为
T
。在第一个终端运行
fg
可以重新恢复该进程。另外一种停止进程的方法是用
kill
发送 STOP
信号给进程。然后使用 CONT
信号可让进程恢复执行。t - 跟踪时被调试者停止
首先,安装GNU调试器(gdb):
sudo apt install -y gdb
监听网络端口1234的入网连接:
$ nc -l1234 & [1] 3905
状态显示睡眠状态,那意味着该进程在等待网络传入数据。
$ ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ubuntu 39050.00.19184896 pts/0 S 07:410:00 nc -l1234
运行调试器,并与进程ID为3905的进程关联:
sudo gdb -p 3905
你会发现这个进程的状态变为
t
,这意味着调试器正在跟踪该进程。$ ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ubuntu 39050.00.19184896 pts/0 t 07:410:00 nc -l1234
- Process time
Linux是一个多任务的操作系统。这意味着,即使机器只有一个PCU,也能在同一个时间点运行多个进程。你可以通过SSH连接服务器查看 htop 输出,同时你的web服务也在通过网络传输博客内容给读者。
那系统是如何做到在单个CPU上一个时间点只执行一个指令呢?答案是时间共享。
一个进程运行“一点时间”,然后挂起;这时另外一个等待的进程运行“一点时间”。进程运行的这“一点时间”称为时间片(time slice)。
时间片通常是几毫秒。所以只要服务器系统的负载不高,你是注意不到的。
这也就可以解释为什么平均负载(load average)是运行进程的平均数了。如果你的服务器只有一个核,平均负载是1.0,那CPU的利用率达到100%。如果平均负载高于1.0,那意味着等待运行的进程数大于CPU能承载运行的进程数。所以会发现服务器宕机或者延迟。如果负载小雨1.0,那意味着CPU有时会处于空闲状态,不做任何工作。
这也给你一个提示:为什么一个运行了10秒的进程的运行时间有时会高于或者低于准确的10秒。
- Process niceness and priority
当运行的task数比可用的CPU核数要多时,你必须找个方法决定接下来哪个task运行哪个task保持等待。这其实是 task scheduler的职责。
Linux内核的scheduler负责选择运行进程队列中哪个进程接下来运行,依赖于内核使用的scheduler算法。
一般你不能影响scheduler,但是让它知道哪个进程更重要。
Nice值(
NI
) 是表示用户空间进程优先级的数值,其代表静态优先级。Nice值的范围是-20~+19,拥有Nice值越大的进程的实际优先级越小(即Nice值为+19的进程优先级最小,为-20的进程优先级最大),默认的Nice值是0。Nice值增加1,降低进程10%的CPU时间。priority(优先级,
PRI
)是Linux内核级的优先级,其代表动态优先级。该优先级范围从0到139,0到99表示实时进程,100到139表示用户空间进程优先级。你可以改变Nice值让内核考虑该进程优先级,但是不能改变priority。
Nice值和priority之间的关系如下:
PR = 20 + NI
所以
PR = 20 + (-20 to +19)
的值是0到39,映射为100到139。在启动进程前设置该进程的Nice值:
nice -n niceness program
当程序已经正在运行,可用
renice
改变其Nice值:renice -n niceness -p PID
下面是CPU使用颜色代表的意义:
蓝色:低优先级线程(nice > 0)绿色:常规优先级线程红色:内核线程
- 内存使用 - VIRT/RES/SHR/MEM
进程给人的假象是只有一个进程使用内存,其实这是通过虚拟内存实现的。
进程没有直接访问物理内存,而是拥有虚拟地址空间,Linux内核将虚拟内存地址转换成物理内存或者映射到磁盘。这就是为什么看起来进程能够使用的内存比电脑真实的内存要多。
这里说明的是,想准确计算一个进程占用多少内存并不是那么直观。你也想计算共享内存或者磁盘映射内存吗?
htop
显示的一些信息能帮助你估计内存使用量。内存使用颜色代表的意义:
绿色:Used memory蓝色:Buffers橘黄色:Cache
VIRT/VSZ - 虚拟内存
task使用的虚拟内存总量。它包含代码、数据和共享内存(加上调出内存到磁盘的分页和已映射但未使用的分页)。
VIRT
是虚拟内存使用量。它包括所有的内存,含内存映射文件。如果应用请求1GB内存,但是内存只有1MB,那
VIRT
显示1GB。如果 mmap
映射的是一个1GB 文件, VIRT
也显示1GB。大部分情况下,
VIRT
并不是一个太有用的数字。RES/RSS - 常驻内存大小
task使用的非交换的物理内存。
RES
是常驻内存使用量。RES
相比于 VIRT
,能更好的表征进程的内存使用量:不包含交换出的内存;不包含共享内存
如果一个进程使用1GB内存,并调用
fork()
函数,fork的结果是两个进程的 RES
都是1GB,但是实际上只使用了1GB内存。因为Linux采用的是copy-on-write机制。SHR - 共享内存大
task使用的共享内存总量。简单的反应进程间共享的内存。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("Started\n"); sleep(10); size_t memory = 10 * 1024 * 1024; // 10 MBchar* 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);
return0; }
fallocate -l10G gcc -std=c99 mem.c -o mem ./mem
Process Message VIRT RES SHR main Started 4200680604
main Allocated 10M 14444680604
main Used 5M 1444461681116
main Forked 1444461681116
child Forked 1444452160
main Child used extra 2M 82521116
child Child used extra 2M 52160
MEM% - 内存使用量占比
task当前使用的内存占比。
该值为
RES
除以RAM总量。如果
RES
是400M,你有8GB的RAM,MEM%
是 400/8192*100
= 4.88%
。未完待续。。。
Enjoy!
侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。
若发现以上文章有任何不妥,请联系我。
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。