当前位置: 移动技术网 > IT编程>数据库>Mysql > Linux系统进程状态、僵尸进程及孤儿进程讲解

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

2017年12月27日  | 移动技术网IT编程  | 我要评论

一、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可查看当前所有进程:

这里写图片描述

二、僵尸进程

<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的方式来获取子进程的退出信息,此时子进程就会出现僵尸进程的现象。

这里写图片描述

这里写图片描述

<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进程所接管。

这里写图片描述

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网