Bootstrap

Linux 进程 | 进程状态:获取状态、僵尸进程、孤儿进程

进程状态

前言

本文详细讲解了进程的状态,包括进程状态的查看方式和僵尸进程、孤儿进程两种特别的状态。如要了解进程的概念、机制等,可以参考进程概念

3、进程状态

在程序运行的时候,如果遇到一个scanf等语句,进程会暂停知道输入相应的数据,才继续运行,由此可见进程需要有不同的状态(例如运行、阻塞、挂起等),不然进程无法按照预期正常执行。

3.1状态

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))

  • D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。

  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。

  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

*运行队列

​ 进程需要执行的时候,会被加入到运行队列中,并由调度器对队列进行调度,在CPU中执行运行的进程,无论是在运行中的还是在运行队列中的进程都是在R运行状态。示意图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


3.2进程状态查看

  • ps -l 列出与本次登录有关的进程信息;
  • ps -aux 查询内存中进程信息;
  • ps -aux | grep + 程序名字 查询该程序进程的详细信息;
  • top 查看内存中进程的动态信息;
  • kill -9 + pid 杀死进程。

举例如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中,在使用ps -l命令时,注意到几个信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID: 代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI: 代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

3.3僵尸进程(Z状态)

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

下面是一个僵尸进程例子:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
    pid_t ret = fork();
    if(ret == 0){ 
        printf("child process exit\n");
        exit(0);
    }   
    else{
        while(1){
        }   
    }   
    return 0;

可以复制一个当前会话便于观察进程信息,下图一为上面代码运行效果,下图二为运行中的进程信息,可以看到由于子进程代码中有exit(0)而提前退出,而父进程一直等待子进程的反馈未果,因而子进程处于z状态。想要结束程序可以使用Ctrl + c 退出或使用kill命令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

进程一般退出的时候,如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z状态,进程的相关资源尤其是task_struct结构体不能被释放

僵尸进程的危害

  • 进程的退出状态必须被维持下去。父进程如果一直不读取,那子进程就一直处于Z状态
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护
  • 那一个父进程创建了很多子进程,就是不回收,会造成内存资源的浪费。因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间的,不会受会造成内存泄漏

3.4孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号systemd进程”领养“,当然要有systemd进程回收。

下面是一个孤儿进程的例子。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
    pid_t ret = fork();
    if(ret == 0){ 
        for(int i=0;i<60;i++){  
            printf("chile process %d \n",getpid());
            sleep(1);
        }   
    }   
    else{
        for(int i=0;i<8;i++){
            printf("father process %d\n",getpid());
            sleep(1);
        }   
        exit(0);
    }   
    return 0;
}

可以看到父进程提前退出,子进程继续执行,如果观察进程信息会发现子进程在父进程提前退出后它的PPID变成了1。使用ps ajx | grep systemd会发现PID是1,即1号进程就是操作系统本身。我们把这种子进程称为孤儿进程。

为什么孤儿进程的PPID会变成1?

因为子进程将来需要被释放,原来的父进程提前退出,因此子进程被系统进程”领养“,在结束后进程后释放掉子进程。

;