1、进程状态

top命令的S(Status)列表示进程状态
- R(Running、Runnable):表示进程在CPU的就绪队列中,正在运行或正在等待运行
- D(Disk Sleep):不可中断睡眠状态(Uninterruptible Sleep),表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断
- Z(Zombie):僵尸进程,表示进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID等)
- S(Interruptible Sleep):可中断状态睡眠,表示进程因为等到某个事件而被系统挂起。当进程等待的事件发生时,他会被唤醒并进入R状态
- I(Idle):空闲状态,用在不可中断睡眠的内核线程上。其中,硬件交互导致的不可中断进程用D表示,但对某些内核线程来说,他们有可能实际上并没有任何负载,用 Idle 来区分这种情况。注意,D状态的进程会导致平均负载升高, I 状态的进程不会。
除了上面5种状态,进程还包括下面两种状态:
- T、t(Stopped或Traced):表示进程处于暂停或者跟踪状态。
向一个进程发送 SIGSTOP 信号,他就会因响应这个信号变成暂停状态(Stopped);再向他发生 SIGCONT 信号,进程又会恢复运行。
用调试器(如gdb)调试一个进程时,在使用断点中断进程后,进程就会变为跟踪状态。
- X(Dead):进程已经消亡
2、中断进程和僵尸进程
1、如果系统或硬件发生了故障,进程可能会在不可中断进程状态保持很久,甚至导致系统中出现大量不可中断进程,这时需要注意,系统是不是出现了 I/O 等性能问题。
2、针对僵尸进程,在多进程应用中很容易碰到。正常情况,当一个进程创建了子进程后,他会用过系统调用wait() 或者 waitpid() 等待子进程结束,回收子进程的资源。而子进程结束时,会向父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
通常,僵尸进程持续时间都较短,父进程回收他的资源后就会消亡;或者父进程退出后,有init进程回收后消亡。3、Demo
1、准备
```bash apt install dstat
docker run —privileged —name=app -itd feisky/app:iowait
<a name="FREis"></a>### 2、初步分析<br />可以看到多个app的进程,分别有 Ss+ 和 D+ ,其中S表示可中断睡眠状态,D表示不可中断睡眠状态,s 表示这个进程使一个会话的领导进程,而 + 表示前台进程组。- **进程组**:表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员- **会话**:共享同一个控制终端的一个或多个进程组比如,登录服务器时ssh的终端,就对应一个会话。终端中运行的命令以及他的子进程,就构成了一个个的进程组。其中,在后台运行的命令,构成后台进程组;在前台运行的命令,构成前台进程组。<br /><br />通过top可以看到,- 过去 1分钟 的平均负载有较大升高,说明系统很可能存在性能瓶颈- tasks任务行,有2个正在运行的进程,但僵尸进程较多,并且不断增加,说明子进程在退出时没有被清理- 用户CPU和系统CPU都不高,而iowait分别是 70.5% 和 52.3% ,不太正常<a name="Gw1dm"></a>### 3、iowait分析<a name="PEoJI"></a>#### 1、dstat + pidstat使用 `dstat`,可以同时查看CPU和I/O两种资源的使用情况。```bash# 每隔1秒输出10组数据dstat 1 10

可以看到,没当 iowait(wai)升高时,磁盘的读请求(read)都很大,说明 iowait的升高跟磁盘的读请求有关。
再通过top观察,发现有很多 app 进程的僵尸进程,通过 pidstat查看进程的情况
# -d 展示 I/O 统计数据,每隔1秒输出20组数据
pidstat -d 1 20

kB_rd 表示每秒读的 KB 数,kB_wr 表示每秒写的 KB数,iodelay 表示 I/O 的延迟(单位是时钟周期)。
可以看到确实是 app 进程在进行磁盘读。
2、strace
如果知道pid的话,可以通过 strace来进行 跟踪进程系统调用。
3、perf
perf record -g
perf report

图中 swapper 是内核中的调度进程,它只在系统初始化时创建 init 进程,之后,他就成了一个最低优先级的空闲任务。也就是,在CPU上没有其他任务执行时,就会执行 sqapper。又称为“空闲任务”。
可以发现,app进程中,通过系统调用 sys_read() 读取数据,并且从 new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高了。
4、定位问题
查看 app.c 可以发现,查看 app.c 发现使用了O_DIRECT选项打开了磁盘,于是绕过了系统缓存,直接进行磁盘读写。造成了上面的问题。
open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)
4、僵尸进程
僵尸进程是因为父进程没有回收子进程的资源而出现的,所以我们先 top 发现进程的pid,再找到他的父进程
# -a 表示输出命令行选项
# p 表示 pid
# s 表示指定进程的父进程
pstree -aps 6217
可以发现 6217 进程的父进程是 6163 。
下来需要查看app的源代码,查看子进程是否正确结束,比如有没有调用 wait() 或 waitpid() ,或者有没有注册 SIGCHLD 信号的处理函数。
int status = 0;
for (;;) {
for (int i = 0; i < 2; i++) {
if(fork()== 0) {
sub_process();
}
}
sleep(5);
}
while(wait(&status)>0);
可以发现,等待子进程结束的逻辑放在了for循环的外面,因此并没有调用 wait() 函数。放到 for 循环里面即可。
