欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Linux系统进程状态、僵尸进程及孤儿进程讲解

程序员文章站 2022-04-08 10:25:22
一、Linux进程状态 在我们的操作系统中,可以同时运行多个程序,而程序在内存中则是一个个的进程,在windows下我们打开任务管理器就可查看对应进程的状态。Linux下可以通过...

一、Linux进程状态

在我们的操作系统中,可以同时运行多个程序,而程序在内存中则是一个个的进程,在windows下我们打开任务管理器就可查看对应进程的状态。Linux下可以通过ps命令查看,Linux上进程主要有以下几种状态:

<1>运行状态 R(TASK_RUNNING)

当进程正在被CPU执行,或已经准备就绪随时可被调度执行,则称该进程为处于运行状态(running)。所谓就绪状态就是该进程已经具有执行所需的条件,随时可被调度执行,但是还在就绪队列中尚未被调度。

<2>可中断睡眠状态 S(TASK_INTERRUPTIBLE)

由于cpu同时可执行的进程有限,所以我们查看进程状态时会有大多数的进程都处于此状态。当进程处于可中断睡眠状态时,系统不会调度该进程执行。只有当系统产生一个中断或者释放了该进程正在等待的资源,或者该进程收到一个信号,就可以唤醒该进程并将状态转换到运行状态(R)。

<3>不可中断睡眠状态 D(TASK_UNINTERRUPTIBLE)

与可中断睡眠状态类似,但处于该状态的进程不响应异步信号,此状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。

<4>暂停状态 T(TASK_STOPPED)

当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。

<5>僵死状态 Z(TASK_ZOMBIE)

当子进程已停止运行,但其父进程还在运行并且没有询问其状态时,则称该子进程处于僵死状态。僵死进程会以终止状态保持在进程表中,直到等待父进程读取退出状态码。

<6>退出状态 X ( TASK_DEAD - EXIT_DEAD)

进程将被置于EXIT_DEAD退出状态,这意味着接下来的代码立即就会将该进程彻底释放。该状态是非常短暂的,几乎不可能通过ps命令捕捉到。

补充:STAT状态位常见的状态字符

D 无法中断的休眠状态(通常 IO 的进程);

R 正在运行可中在队列中可过行的;

S 处于休眠状态;

T 停止或被追踪;

W 进入内存交换 (从内核2.6开始无效);

X 死掉的进程 (基本很少見);

Z 僵尸进程;

< 优先级高的进程

N 优先级较低的进程

L 有些页被锁进内存;

s 进程的领导者(在它之下有子进程);

l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);

+ 位于后台的进程组;

使用ps a可查看当前所有进程:

Linux系统进程状态、僵尸进程及孤儿进程讲解

二、僵尸进程

<1>先来构造一个僵尸进程的例子:我们在前面已经了解到一个子进程在其父进程没有调用wait()或waitpid()的情况下退出,如果其父进程还存在而一直不调用wait(),则该僵尸进程将无法回收,等到其父进程退出后该进程将被init回收。在这个过程中子进程已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,这个时候该子进程称为一个僵尸进程。这个子进程就是僵尸进程。例:

  1 #include 
  2 #include 
  3 #include 
  4 
  5 
  6 int main()
  7 {
  8     pid_t id = fork();
  9     if(id < 0){
 10         perror("fork");
 11         return 1;
 12     }else if(id > 0){
 13         printf("parent[%d] is sleeping\n",getpid());
 14         sleep(30);
 15     }else{
 16         printf("child[%d] is begin\n",getpid());
 17         sleep(5);
 18         exit(1);
 19     }
 20 
 21     return 0;
 22 }

我们在shell下同时开启两个终端,一个下运行以上程序,另外一个在执行过程中使用grep抓取stat状态为zZ进程,通过结果的显示我们可以看出来在该进程执行时父进程状态为Z,成为了僵尸进程

ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]'

<2>结果分别如下:

我们通过fork()创建一个子进程,父进程执行30s,子进程执行5s后就退出,并且父进程没有使用wait或waitpid的方式来获取子进程的退出信息,此时子进程就会出现僵尸进程的现象。

Linux系统进程状态、僵尸进程及孤儿进程讲解

Linux系统进程状态、僵尸进程及孤儿进程讲解

<3>避免僵尸进程的方法:

⒈父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起,相关用法可以通过man命令查看。

⒉ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。

⒊ 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。

⒋ 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。

<4>僵尸进程的危害:

假设这样一个情景,我们有一个父进程在不断的创建子进程,每个子进程的存活时间都很短,父进程对子进程的终止状态都不管不顾,任由发展下去,子子孙孙,系统中就会存在许多的僵尸进程,更重要的是每一个僵尸进程都还没占着对应的进程列表,进程列表可是临界资源是有限的,时间一长内存中就没有多余的地方再让我们创建进程了。

三、孤儿进程

<1>同样通过构造一个例子来理解什么叫孤儿进程,此场景就和名字一样通俗易懂,所谓的“孤儿”,就是失去了“父亲”,也就是说在子进程还在执行时,父进程就已经退出了。看例子:

 #include 
 #include 

 int main()
 {
      pid_t id  = fork();

      if(id < 0){
          perror("for");
          return 1;
      }else if(id == 0){
          printf("start:im child[%d],parent is [%d]\n",getpid(),getppid());
          sleep(10);
          printf("finish:im child[%d],parent is [%d]\n",getpid(),getppid());
      }else{
          printf("im father[%d]\n",getpid());
          sleep(2);
      }   

      return 0;
  }

<2>运行结果及分析:

我们可以看出本来子进程的父进程Pid为2420,但是我们让父进程sleep两秒后就结束了,此时子进程依然还在运行,等子进程sleep完成后,此时父进程已经不在了,新的父亲的pid为1,也就是系统中的init进程,也可以说该子进程被init进程所接管。

Linux系统进程状态、僵尸进程及孤儿进程讲解