给进程排个队 - Linux中ps命令的使用
在Linux中,ps命令是很常用的,可是这个命令虽然看起来简单,但往往需要配合各种参数才能得到有用的信息,而ps的参数系统可以说是混乱至极,它要称自己第二,估计没有其他的命令敢称第一。首先,很多参数的效果是差不多的,有相当多重叠的部分,再次,很多参数是不能配合在一起使用的。
造成这种混乱很大的原因是现在Linux主流发行版的ps命令是同时兼容System V和BSD的,关于这一块的详细分析请看 史上最全 Linux ps 命令详解 这篇文章,一般我看到什么“史上最全”之类的标题都会嗤之以鼻,但这篇文章,可能真的是国内讲解ps命令最全的文章了。
作为运维工程师,需要对ps命令掌握的更全面一些,但是作为开发工程师,在这个命令的使用过程中,侧重点可能会稍有不同。因此本文主要是结合开发的实际需要,从中提取总结了一些比较实用的参数,并结合procrank工具和top命令,进行了一定程度的扩展。
进程占用内存
在讲解ps命令的参数之前,有必要先介绍一下Linux对进程占用内存的计算依据。因为Linux中的进程是使用 虚拟地址 的,这些进程通过malloc()或者 mmap() 向内存申请内存之后(这部分内存大小称为 VSS - Virtual Set Size),内核并不会立刻为其分配实际的物理内存。等到进程真正使用到内存时(比如调用了memset()函数),内核才会为这个进程分配物理内存,并建立虚拟地址和物理地址之间的映射(参考 这篇文章 )。
计算这段分配的物理内存的大小并不难,可是Linux中的进程是广泛使用共享的动态链接库的(后缀名为so - shared object),而动态链接库本身也是要占据物理内存的,那这部分内存应该算在哪个进程头上呢?
为此就出现了三种不同的计算方法(统计口径),一种是只计算进程自身占用的物理内存,完全不包含共享库所占用内存的 USS (Unique Set Size),一种是把共享库占用的内存直接加到每个进程头上的 RSS (Resident Set Size)。
USS和RSS的统计方法虽然简单,但显得不是那么合理,更公平一点的做法是把一个共享库占用的内存,分摊到使用了这个共享库的各个进程头上,称为 PSS (Proportional Set Size),类似于小区里面的“公摊面积”。
之所以说“类似”,是因为两者还是存在一定的区别,公摊面积通常是按照房屋的实际大小乘以一个百分比获得,也就是说房屋面积越大,公摊面积也越大,而PSS虽然是叫"Proportional",但这名字其实是具有误导性的,它实际的做法是平分。比如一个共享库占用了3MiB的物理内存,有3个进程使用了这个库,那么摊派到每个进程头上的都是1MiB。房屋建好后大小就固定了,但进程自身占用的内存是动态变化的,按比例分摊就太麻烦了。
假设现在这3个进程中有一个被kill掉了,那么共享库这3MiB的内存就要分摊到另外2个还存活的进程上,此时这个被kill的进程所释放的内存大小就不是它的PSS,而是它的USS。
ps命令的参数
介绍完了进程占用内存的计算依据,接下来就可以结合各个参数,来查看ps命令输出的各种信息了。要显示所有的用户进程和内核线程,加上参数"-e"即可:
如果只想查看用户进程,则加上参数"u"或"l"。"u"和"l"都可以显示VSZ(即VSS),RSS和执行时间统计,此外"u"还可以显示CPU和memory占用率:
而"l"可以显示优先级、nice值和PPID:
单独使用"-e"虽然可以查看所有进程/线程,但关于每个进程/线程的信息有限,而且甚至都无法分别其中的一项到底是用户进程还是内核线程,因此"-e"参数通常应该配合"u"或"l"使用:
用"[]"包起来的就是内核线程,我们可以利用这一特性来实现只查看内核线程,像这样:
ps -e u | grep '\[.*\]'
内核线程所占用的内存大小不通过VSZ和RSS表达,所以在ps命令的输出中,内核线程对应的VSZ和RSS的值都为0。
由于用户进程之间存在继承关系,可以通过加"f"参数来查看这种关系,输出结果以树形排列的形式呈现:
单独使用"f"跟单独使用"-e"一样,得到的关于每一个进程/线程的信息有限,而"f"也同"-e"一样,可以联合"u"或"l"使用以获得更全面的进程信息。
既然"u"和"l"可以显示进程中各个特性的详细信息,那我们肯定希望必要的时候,可以按照其中的某个特性来排序,比如按RSS排序以查看哪个进程消耗的内存最大(虽然并不是那么准确,但还是可以看个大概嘛),这就要用到"k"参数。
"k"后面跟上某个特性的名称,就可以按照这个特性排序,例如"k rss"即是按rss升序,"k -rss"则是按rss降序,其他常用的包括按CPU占用率排序的"%cpu",按执行时间排序的"time"等。因为有些进程在启动时的名称很长(比如qemu),可以再加上一个"c",让输出排列的比较整齐。
在多核系统中,有时我们可能还有兴趣看看这个进程/线程现在是哪个核上运行啊,"-P"参数可以帮助我们查看这个信息。此外,一个用户进程往往是由多个线程组成的,可以通过加上"-L"参数来显示线程。
如果我们把"-P"和"-L"结合起来查看启动 QEMU虚拟机 的"qemu-system-aarch64"进程的线程运行情况,可以看到它一共包含6个线程。
在启动这个QEMU虚拟机时,我使用的是"-smp 4",也就是4个CPU, 我们可以进入QEMU的命令行模式(通过"ctrl-a + c"),输入"info cpus"来查看这4个CPU的信息。
可以看到,这4个CPU使用了4个线程,而这4个线程的PID就是包含在我们之前用ps命令看到的6个线程中的,而且这4个线程是运行在同一个CPU上。QEMU中guest使用的是 虚拟CPU ,每个虚拟CPU由host的一个线程来模拟,这4个虚拟CPU共享一个host上的一个物理CPU。
procrank工具
在Android上面做过开发的同学可能知道,Android提供一个procrank工具,可以很方便的查看各进程的内存使用情况,排序依据可以是VSS, RSS, PSS或者USS。
而我们完全可以把procrank工具移植到Linux系统上来,只需下载 这个repo ,根据自己运行Linux的架构,交叉编译一下就可以了。
其实啊,如果你理解了ps命令的那些参数,虽然略微繁琐了点,但完全可以实现和procrank类似的输出,虽然不像procrank那么完备,可以一并显示VSS, RSS, PSS和USS的信息,但作为初步调试基本够用了。毕竟,当你遇到一台出现问题的Linux机器需要调试时,那台机器上大多没有安装procrank,有时临时移植一个也不现实,而用ps命令加参数的方式就可以不受这种限制。
top命令
Linux中另一个常用的命令是top,它通常用于查看CPU的占用率,但同时也能显示VSS和RSS的信息,只不过在这里叫法不一样,分别被称为了"VIRT"和"RES"。top命令的输出默认是按CPU占用率排序的,但同样可以实现按照其他规则排序,比如在top命令的运行过程中,使用"shift+m"就是按RES/内存排序,使用"shift+t"就是按CPU执行时间排序,"shift+p"则可切回按照CPU占用率排序。
看来ps命令和top命令的输出也很多重叠的部分(毕竟它们都是从"/proc/$(pidof process)/stat"里提取的信息),也许两者最大的区别就是top命令可以一直动态刷新显示。好处当然是可以实时观察变化,但之前的数据会被新的数据覆盖掉。其实用ps指令配合很简单的shell脚本,像这样
while true; do ps; sleep 3; done
也可以实现实时地输出数据:
参考:
http://jackjia.cc/linux-command-top
原创文章,转载请注明出处。