1、进程状态

image.png
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种状态,进程还包括下面两种状态:

  1. T、t(Stopped或Traced):表示进程处于暂停或者跟踪状态。

向一个进程发送 SIGSTOP 信号,他就会因响应这个信号变成暂停状态(Stopped);再向他发生 SIGCONT 信号,进程又会恢复运行。
用调试器(如gdb)调试一个进程时,在使用断点中断进程后,进程就会变为跟踪状态。

  1. 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

  1. <a name="FREis"></a>
  2. ### 2、初步分析
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22097252/1645620016966-124719ed-88b5-4a2a-89bf-549d6746b8b6.png#clientId=uba5bfe3d-1a28-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=201&id=u431d32ac&margin=%5Bobject%20Object%5D&name=image.png&originHeight=251&originWidth=1164&originalType=binary&ratio=1&rotation=0&showTitle=false&size=47340&status=done&style=none&taskId=u323fe52b-fc00-48af-9af3-d44e4495f8c&title=&width=931.2)<br />可以看到多个app的进程,分别有 Ss+ 和 D+ ,其中S表示可中断睡眠状态,D表示不可中断睡眠状态,s 表示这个进程使一个会话的领导进程,而 + 表示前台进程组。
  4. - **进程组**:表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员
  5. - **会话**:共享同一个控制终端的一个或多个进程组
  6. 比如,登录服务器时ssh的终端,就对应一个会话。终端中运行的命令以及他的子进程,就构成了一个个的进程组。其中,在后台运行的命令,构成后台进程组;在前台运行的命令,构成前台进程组。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22097252/1645668662038-55228517-85df-4c83-9245-c68f7b64cdfa.png#clientId=uba5bfe3d-1a28-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=148&id=u16ed4726&margin=%5Bobject%20Object%5D&name=image.png&originHeight=185&originWidth=1058&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33666&status=done&style=none&taskId=u6ff61342-e275-423d-93b3-0a3fb12d1b2&title=&width=846.4)<br />通过top可以看到,
  7. - 过去 1分钟 的平均负载有较大升高,说明系统很可能存在性能瓶颈
  8. - tasks任务行,有2个正在运行的进程,但僵尸进程较多,并且不断增加,说明子进程在退出时没有被清理
  9. - 用户CPU和系统CPU都不高,而iowait分别是 70.5% 和 52.3% ,不太正常
  10. <a name="Gw1dm"></a>
  11. ### 3、iowait分析
  12. <a name="PEoJI"></a>
  13. #### 1、dstat + pidstat
  14. 使用 `dstat`,可以同时查看CPU和I/O两种资源的使用情况。
  15. ```bash
  16. # 每隔1秒输出10组数据
  17. dstat 1 10

image.png
可以看到,没当 iowait(wai)升高时,磁盘的读请求(read)都很大,说明 iowait的升高跟磁盘的读请求有关。
image.png
再通过top观察,发现有很多 app 进程的僵尸进程,通过 pidstat查看进程的情况

# -d 展示 I/O 统计数据,每隔1秒输出20组数据
pidstat -d 1 20

image.png
kB_rd 表示每秒读的 KB 数,kB_wr 表示每秒写的 KB数,iodelay 表示 I/O 的延迟(单位是时钟周期)。
可以看到确实是 app 进程在进行磁盘读。

2、strace

如果知道pid的话,可以通过 strace来进行 跟踪进程系统调用。
image.png

3、perf

perf record -g
perf report

image.png
图中 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

image.png可以发现 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 循环里面即可。