Skip to content

进程管理

什么是进程

进程表示程序的一次执行过程,它是应用程序的运行实例,是一个动态的过程。
或者可以更简单地描述为:进程是操作系统当前运行的程序。当一个进程开始运行时,就是启动了这个过程。
进程包括动态执行的程序和数据两部分。
现代操作系统支持多进程处理,这些进程可以接受操作系统的调度,所以说每一个进程都是操作系统进行资源调度和分配的一个独立单位。

所有的进程都可能存在 3 种状态:运行态、就绪态、阻塞态。
运行态表示程序当前实际占用着 CPU 等资源;
就绪态是指程序除 CPU 之外的一切运行资源都已经就绪,等待操作系统分配 CPU 资源,只要分配了 CPU 资源,即可立即运行;
而阻塞态是指程序在运行的过程中由于需要请求外部资源(例如 I/O 资源、打印机等低速的或同一时刻只能独享的资源)而当前无法继续执行,从而主动放弃当前 CPU 资源转而等待所请求资源

进程之间又存在互斥和同步的关系。
互斥也就是说进程间不能同时运行,必须等待一个进程运行完毕,另一个进程才能运行,比如说不可能有两个进程同时使用同一部打印机打印文件。
而进程同步指的是进程间通过某种通信机制实现信息交互。
现代计算机使用信号量机制来实现进程间的互斥和同步,它的基本原理是:两个或者多个进程可以通过简单的信号进行合作,一个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。
任何复杂的合作需求都可以通过适当的信号结构得到满足。

查看进程

ps 命令

选项与参数:

  • -A:所有的进程均显示出来,与 -e 具有同样的效果
  • -a:不显示与终端有关的所有进程
  • -u:有效使用者相关的进程
  • x:通常与 a 这个参数一起使用,可列出较完整信息
  • -w: 显示加宽可以显示较多信息

输出格式规划:

  • l:较长、较详细的将该 PID 的信息列出
  • j:任务的格式
  • -f:做一个更为完善的输出

常用:

  • ps aux / ps -ef:查看所有系统运行的进程
  • ps -l:只查看自己 bash 的进程

参数的输出:

1
2
3
4
5
6
7
#USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND        

#USER:进程拥有者    #PID:pid    #%CPU:占用的CPU使用率   
#%MEM:占用的内存使用率    #VSZ:占用的虚拟内存大小    #RSS:占用的内存大小        
#TTY:占用的内存大小    #STAT:进程状态     #D:不可中断    #R:运行中    #S:休眠     #T:暂停    #Z:僵尸进程        
#W:没有足够的内存可分配    #<:高优先级的行程    #N:低优先级的行程     #START:进程开始时间      
#TIME:累计使用CPU的时间    #COMMAND:执行的命令
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(base) ➜  ~ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0  2020 ?        00:01:37 /sbin/init nospectre_v2 nopti noibrs noibpb
root         2     0  0  2020 ?        00:00:00 [kthreadd]
root         4     2  0  2020 ?        00:00:00 [kworker/0:0H]
root         6     2  0  2020 ?        00:00:00 [mm_percpu_wq]
root     17062 16985  0 May18 ?        01:30:32 mongod --bind_ip 127.0.0.1 --pidfilepath /var/snap/rocketchat-server/common/mongod.pid --smallfiles --journal --dbpath=/var/snap/rocketchat-server/
root     17461 16999  7 May18 ?        1-06:11:50 node /snap/rocketchat-server/1453/main.js
www-data 17913 17911  0 May18 ?        00:00:18 nginx: worker process
root     21513 21476  0  2020 pts/1    00:00:07 /root/anaconda3/bin/python /root/anaconda3/bin/pipenv shell
root     17461 16999  7 May18 ?        1-06:11:50 node /snap/rocketchat-server/1453/main.js

执行 ps -ef 后可以看到 UID 之后有两个 id,第一个为 PID,第二个为 PPID:

  • PID是程序被操作系统加载到内存成为进程后动态分配的资源。每次程序执行的时候,操作系统都会重新加载,PID在每次加载的时候都是不同的。
  • PPID(parent process ID): PPID是程序的父进程号

top 命令

动态查看进程的变化

相对于 ps 是选取一个时间点的进程状态,top 则可以持续检测进程运行的状态。使用方式如下:

1
2
Usage:
  top -hv | -bcHiOSs -d secs -n max -u|U user -p pid(s) -o field -w [cols]

-d: 后面可以接秒数,就是整个进程界面更新的秒数,默认是 5 秒
-u: 后面接用户名,表示只显示该用户相关的进程
-b: 以批量的方式执行 top ,还有更多的参数可以使用,通常会搭配数据流重定向来将批量的结果输出为文件
-n: 与 -b 搭配,意义是,需要执行几次 top 的输出结果
-p: 指定某个 PID 来执行查看检测而已

在 top 执行过程中可以使用的按键命令:
?: 显示在 top 当中可以输入的按键命令
P: 以 CPU 的使用排序显示
M: 以 Memory 的使用排序显示
N: 以 PID 来排序
T: 由该进程使用的 CPU 时间累计(TIME+)来排序
k: 给予某个 PID 一个信号(signal)
r: 给予某个 PID 重新制订一个 nice 值
q: 退出 top 的按键

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
top - 22:52:43 up 42 days, 11:17, 71 users,  load average: 69.00, 70.78, 75.38
Tasks: 1586 total,  13 running, 1044 sleeping,   0 stopped,   0 zombie
%Cpu(s): 32.1 us,  0.6 sy, 15.1 ni, 47.8 id,  4.4 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 10567212+total, 29476009+free, 46295036+used, 29901075+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 58679942+avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 13054 zhaoyan+  20   0 67.058g 0.015t 108668 R 101.0  1.6 248:43.94 harvest
 15011 zhaoyan+  20   0 55.736g 8.000g 107800 R 101.0  0.8 149:45.32 harvest
 14042 zhaoyan+  20   0 50.301g 2.573g 109780 R  65.9  0.3 161:57.68 harvest
 36568 zhaoyan+  20   0 2109284 165076  30676 S  16.1  0.0   2:32.15 node
115259 zhaoyan+  20   0   54680   6112   3728 R   3.2  0.0   0:04.60 top
 36767 zhaoyan+  20   0  874584  50948  27372 S   1.0  0.0   2:28.57 node
 22105 zhaoyan+  20   0  111188   9824   1676 S   0.6  0.0 315:29.58 redis-server
 22196 zhaoyan+  20   0  148052  30028   1520 S   0.6  0.0 256:25.54 redis-server
 22300 zhaoyan+  20   0  369236  12088   1612 S   0.6  0.0 291:43.63 redis-server
 22409 zhaoyan+  20   0   69204   4756   1560 S   0.6  0.0 227:34.51 redis-server

top 主要分为两部分界面,上面的界面为整个系统的资源使用状态,基本上总共有六行,显示的内容依序是:

第一行(top...): 这一行显示的信息分别为

  • 目前的时间,即 22:52:43
  • 开机到目前为止所经过的时间,即 up 42 days, 11:17
  • 已经登录系统的用户人数,即 71 users
  • 系统在 1、5、15 分钟的平均任务负载。代表的是 1、5、15 分钟,系统平均要负责运行几个进程(任务)的意思。数值越小代表系统越闲置

第二行(Tasks...): 显示的是目前进程的总量与各别进程在什么状态(running、sleeping、stopped、zombie)。需要注意的是最后的 zombie 那个数值,如果不是 0,赶紧好好看看到底是哪个 process 变成僵尸了吧?

第三行(%Cpus...): 显示的是 CPU 的整体负载,每个项目可使用?(问号)查看。需要特别注意的是 wa 项目,那个项目代表的是 I/O wait,通常你的系统会变慢都是 I/O 产生的问题比较大。因此这里要注意这个项目耗用 CPU 的资源。另外,如果是多内核的设备,可以按下数字键 1 来切换不同 CPU 的负载率

第四行与第五行:表示目前的物理内存与虚拟内存(Mem/Swap)的使用情况。再次重申,要注意的是 swap 的使用量要尽量的少,如果 swap 被用得很多,表示系统的物理内存实在不足。(单位为写在最前面)

第六行:这个是当在 top 进程当中输入命令时,显示状态的地方

至于 top 下半部分的画面,则是每个进程使用的资源情况,需要注意的是:

  • PID: 每个进程的 ID
  • USER: 该进程所属的用户
  • PR: Priority 的缩写,进程的优先执行顺序,越小则越早被执行
  • NI: Nice 的缩写,与 Priority 有关,也是越小则越早被执行
  • %CPU: CPU 的使用率
  • %MEM: 内存的使用率
  • TIME+: CPU 使用时间的累加
  • S: 进程状态(D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)
  • RES: 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA

top 默认使用 CPU 使用率(%CPU) 作为排序的依据。
如果你想要使用内存使用率排序,则可以按下 M,若要恢复则按下 P 即可。如果想要退出 top,则按下 q。

htop

htop 是一个在 centos 上没有,ubuntu 上才有的命令

htop 相比 top 的优点:

  • 在启动上,比top 更快
  • 杀进程时不需要输入进程号
  • 支持鼠标操作

进程的管理

进程之间是可以相互控制的。举例来说,你可以关闭、重新启动服务器软件,服务器软件本身是个进程,你既然可以让它关闭或启动,当然就可以控制该进程。

那么进程是如何互相管理的呢?其实是通过给予该进程一个信号(signal)去告知该进程你想要让它做什么,因此这个信号就很重要

到底有多少信号呢?可以使用kill -lman 7 signal来查询。主要信号的代码、名称及内容如下:

代号 名称 内容
1 SIGHUP 启动被终止的进程,可让该 PID 重新读取自己的配置文件,类似重新启动
2 SIGINT 相当于键盘输入 Ctrl+C 来中断一个进程的运行
9 SIGKILL 代表强制中断一个进程的执行,如果该进程执行到一半,那么尚未完成的部分可能会有【半成品】产生,类似 vim 会有 .filename.swp 保留下来
15 SIGTERM 以正常的方式结束进程来终止该进程。由于是正常的终止,所以后续的操作会将它完成。不过,如果该进程已经发生问题,就是无法使用正常的方法终止时,输入这个信号也是没有用的
19 SIGSTOP 相当于键盘输入 Ctrl+Z 来暂停一个进程的运行

上面仅是常见的信号而已。一般来说,只要记得 1、9、15 这三个号码的意义即可。
那么我们如何发送一个信号给某个进程呢?就通过 kill 或 killall

kill -signal PID

kill 可以帮我们将这个信号传送给某个任务(%jobnumber)或是某个 PID(直接输入数字)。

查看系统资源信息

free: 查看内存使用情况

uname: 查看系统与内核相关信息

uname [-asrmpi]
选项与参数:
-a: 所有系统相关的信息,包括下面的数据都会被列出来
-s: 系统内核名称
-r: 内核的版本
-m: 本系统的硬件架构,例如 i686 或 x86-64 等
-p: CPU 的类型,与 -m 类似,只是显示的是 CPU 的类型
-i: 硬件的平台(x86)

1
2
(base) ➜  ~ uname -a
Linux Mo-14-I60M512 4.15.0-70-generic #79-Ubuntu SMP Tue Nov 12 10:36:11 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
1
2
➜  ~ uname -a
Darwin zhaoyangzhendeMacBook-Pro.local 19.2.0 Darwin Kernel Version 19.2.0: Sat Nov  9 03:47:04 PST 2019; root:xnu-6153.61.1~20/RELEASE_X86_64 x86_64

查询进程打开的文件:lsof

lsof(list open files) 是一个列出当前系统中所有打开文件的工具。
Linux 中一切皆文件,所以在系统中,被打开的文件可以是普通文件、目录、网络文件系统中的文件、字符设备、管道、socket 等。
那么如何知晓现在系统打开的是哪些问价呢,这时 lsof 命令就有用武之地了。
不过这个命令在系统中可能并未默认安装,在 CentOS 下如果可以联网,简单输入 yum install lsof -y即可安装该工具,如果是在 RedHat 下,则需要到原始安装光盘中寻找 lsof 的 rpm 包进行安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
lsof [options filename]       
# 常用的参数列表
lsof filename # 显示打开指定文件的所有进程     
lsof -c string # 显示 COMMAND 列中包含指定字符的进程所有打开的文件
lsof -u username # 显示所属于 user 进程打开的文件
lsof -g gid # 显示归属于 gid 的进程情况    
lsof +d /DIR/ # 同上,但是会搜索目录下的所有目录,时间相对较长
lsof -d FD # 显示指定文件描述符的进程
lsof -n # 不讲 IP 转换为 hostname,默认是不加 -n 参数    
lsof -i # 用以显示符合条件的进程情况
lsof -i:[46][protocol][@hostname|hostaddr][:service|port] # 46指IPv4或IPv6 protocol指TCP或UDP hostname指主机名hostaddr是 IPv4 地址 service 是 /etc/service 中的 service name,port 是端口号
1
2
3
4
➜  ~ lsof -i:80
COMMAND     PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
mkdocs     6426 root    4u  IPv4 164206970      0t0  TCP *:http (LISTEN)
AliYunDun 16143 root   21u  IPv4 161595502      0t0  TCP izuf6cyo1arn732qegfq81z:44008->100.100.30.25:http (ESTABLISHED)
1
2
3
4
5
6
7
➜  /nocilantro git:(master) lsof .
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh      891 root  cwd    DIR  253,1     4096 1572865 .
lsof    1147 root  cwd    DIR  253,1     4096 1572865 .
lsof    1148 root  cwd    DIR  253,1     4096 1572865 .
zsh     6288 root  cwd    DIR  253,1     4096 1572865 .
mkdocs  6426 root  cwd    DIR  253,1     4096 1572865 .

这个命令可以在不加任何参数的情况下直接运行,但是该命令一定需要用 root 账号来执行,因为 lsof 在运行时需要访问很多核心文件,需要的权限很高,其所输出的目前系统中所有打开的文件。

输出的字段有 COMMAND、PID、USER、FD、TYPE、DEVICE、SIZE、NODE、NAME9 列,这 9 个字段的意思如下:

  • COMMAND: 进程的名称
  • PID:进程标识符
  • USER: 进程所有者
  • FD: 文件描述符,应用程序通过文件描述符识别该文件
  • TYPE: 文件类型,如 DIR、REG 等
  • DEVICE: 磁盘的名称
  • SIZE: 文件大小
  • NODE: 索引节点
  • NAME: 打开文件的全路径名称

Linux 系统中有很多日志文件会不断地被写入、更新,/var/log/messages就是其中的一个。
现在来看一下当前有什么进程正在使用该文件(syslogd 是系统中负责写系统日志的进程)

1
2
3
➜  ~ lsof /var/log/messages
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
rsyslogd 3503 root    6w   REG  253,1    82581 262762 /var/log/messages

使用 lsof 还可以查找使用了某个端口的进程,比如说如果系统中运行了 sshd 进程(基本上都是默认运行的),则该进程默认会绑定 22 端口,让我们来确认一下:

1
2
3
4
➜  ~ lsof -i:22
COMMAND   PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
sshd     7942 root    3u  IPv4 175706096      0t0  TCP izuf6cyo1arn732qegfq81z:ssh->115.171.12.149:trivnet2 (ESTABLISHED)
sshd    29961 root    3u  IPv4 164018954      0t0  TCP *:ssh (LISTEN)

如果你发现系统中打开了一些未知的端口,可以使用这个方法来确认具体是什么进程在使用

使用 lsof 命令还有个更实用的功能,就是可以通过其恢复被删除的文件--但这是有条件的,必须是文件正在被某个进程使用,而且该进程未停止(也就是依然拥有打开文件的句柄)。

我们知道,在 Windows 下恢复数据相对来说比较简单,因为 Windows 提供了回收站功能,删除的文件可以在其中简单地被找回。
另外,Windows 系统下的第三方软件也是非常丰富的,即使回收站被清空了,也有可能使用这些第三方软件协助恢复数据的软件。

现假设文件/var/log/messages不小心被删除了,首先来确认一下当前是否有进程正在使用这个文件,如果有则可以继续,如果没有就无法使用该方法继续了。
本例中看到有个 PID 为 2449 的进程正在使用该文件,那么接下来只要找到对应/proc目录下的文件就可以了。
具体看以下的命令演示:

1
2
3
4
5
lsof | grep message
syslogd 2449 root 1w REG 253,0 149423
416767 /var/log/messages
cat /proc/2449/fd/2 > /var/log/messages
Service syslogd restart

进程优先级调整:nice、renice

在学习 top 时,我们看到其输出中有 NI 字段,标记了对应进程的优先级,该字段的取值范围是 -20~19,数值越低代表优先级越高,也就能更多地被操作系统调度运行,如果一个进程在启动时并没有设定 nice 优先级,则默认使用 0.
普通用户也可以给自己的进程设定 nice 优先级,但是范围只限于 0~19.不过 top 中不是还有一个 PR 字段吗,它也是进程的“优先级”,这两个概念怎么理解呢?
实际上,Linux 使用了“动态优先级”的调度算法来确定每一个进程的优先级,一个进程的最终优先级=优先级+nice优先级

nice 命令仅限于在启动一个进程的时候同时赋予其 nice 优先级,比如你自己写了一个脚本 job.sh,你想以比较高的优先级来运行它,就可以这么做:

nice -n -10 ./jon.sh

对于已经启动的进程,可以用 renice 命令进行修改,不过,这需要先查询出该进程的 PID(使用 ps 命令)。
假设现在需要将 PID 为 5555 的进程的 nice 优先级调整为 -10,则可以这么做:

renice -10 -p 5555

除了使用 renice 外,还可以使用 top 提供的功能来修改,前提也是要查到该进程的 PID,然后在 top 界面中按 r 键,在出现的 PID to renice 后输入 PID。
然后在出现的 renice PID***to value 后输入修改后的 nice 优先级即可