走进Linux——进程(二)进程状态

        进程状态是进程的重要属性之一,也是Linux下task_struct中的重要属性,可以便于操作系统较为均衡的调度每个进程。本文将着重介绍Linux下的进程状态,并解释进程状态与操作系统调度进程的关系。

目录

基本概念

Linux下的进程状态

一、R状态

二、S状态

三、D状态

四、T状态

五、t状态

六、x状态

七、Z状态

        僵尸进程的危害

孤儿进程


基本概念

        Linux下,进程的状态信息存储在task_struct中。有了进程状态,可以方便操作系统快速判断进程,完成特定的功能。

        在操作系统的相关书籍中,我们会看到类似下面的图片:

走进Linux——进程(二)进程状态_第1张图片

        这是一个笼统的概念,因为操作系统书籍需要对各种操作系统进行兼容,做到“放之四海而皆准”。

        运行态:进程正在执行。

        就绪态:进程做好了准备,只要有机会就开始执行。

        挂起/等待/阻塞态:进程在某些事件发生前不能执行,如I/O操作完成。

        初始态:刚刚创建的进程,操作系统还未把它加入可执行进程组,通常是进程控制块已经创建但还未加载到内存中的新进程。

        结束:操作系统从可执行进程组中释放出的进程,要么它自身已停止,要么它因为某种原因被取消。

        以上是操作系统书籍中的解释。本篇文章将继续从Linux的角度阐述各种进程状态。

Linux下的进程状态

        Linux下共有如下几种状态:R(运行状态)、S(睡眠状态)、D(磁盘休眠状态)、T(停止状态)、t(追踪状态)、X(死亡状态)、Z(僵尸状态)。

一、R状态

        R状态并不意味着进程一定在运行中,一定占有CPU资源,它表明进程要么在运行中,要么在运行队列里。下面画图作出解释:

走进Linux——进程(二)进程状态_第2张图片

        正在占有CPU资源进程的R状态也就对应运行态,当它的时间片结束,将放到运行队列尾部,此时的R状态对应的状态是就绪态。

        R状态在Linux下很好检测到,写下如下测试代码:

#include 
#include 

int main()
{
  while(true)
  {
  }

  return 0;
}

走进Linux——进程(二)进程状态_第3张图片

走进Linux——进程(二)进程状态_第4张图片

         运行后使用ps axj | grep myproc指令可以发现进程处于R状态。

二、S状态

        S状态的进程在等待某个事件的完成:如网络、I/O资源等。(这里的睡眠也称为可中断睡眠)。进程为了等待某个事件的完成,从运行队列(R状态)放到等待队列(S状态)中,叫做挂起等待;从等待队列(S状态)放到运行队列(R状态)中,CPU的调度就叫做进程唤醒。

走进Linux——进程(二)进程状态_第5张图片

 走进Linux——进程(二)进程状态_第6张图片

        写下如下测试代码:

#include 
#include 

int main()
{
  sleep(20);

  return 0;
}

        运行后使用ps axj | grep myproc指令可以发现进程处于S状态。

三、D状态

        D状态即不可中断睡眠状态,在这个状态的进程通常会等待IO结束。D状态的进程和S状态的进程一样,会被放入等待队列,设计D状态的独特意义是什么呢?

        我们可以想象这样一种情况:进程向磁盘申请I/O资源,向磁盘写入。此时如果进程进入S状态,然而此时内存资源已经不充足了,操作系统为了缓解内存资源的高占用,选择杀死一些S状态的进程。此时就将正在等待I/O资源的进程杀掉了。此时磁盘完成了工作,需要将结果告诉进程(成功或失败),而进程已经被杀掉,结果无法向上传递。可能导致明明失败了,而用户却浑然不知的情况。

走进Linux——进程(二)进程状态_第7张图片

走进Linux——进程(二)进程状态_第8张图片

走进Linux——进程(二)进程状态_第9张图片

        于是,此种情况下,进程会进入D状态,不可被OS杀掉,也即不可中断。

四、T状态

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

        测试代码:

#include 
#include 

int main()
{
  while(true)
  {

  }

  return 0;
}

        运行后该进程原本处于R状态,发送信号后,变为T状态

走进Linux——进程(二)进程状态_第10张图片

五、t状态

        t状态即追踪状态,进程调试时所处的状态,这里不做过述。

六、x状态

        所谓x状态指定是死亡状态,此状态只是一个返回状态,无法在任务列表中看到这个状态。因为回收进程是一瞬间发生的事情,我们很难直接捕捉到。

七、Z状态

        Z状态是指僵尸状态,该状态指的是一个比较特殊的状态。当进程退出且父进程没有回收进程时,就会产生僵尸进程。

        僵尸进程会以终止状态保持在进程列表中,并会一直等待父进程读取退出状态码。因此只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态。

        测试代码:

#include 
#include 

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

  if(id == 0)
  {
    //子进程
    while(true)
    {
      std::cout << "child is runing" << std::endl;
      sleep(2);
    }
  }
  else if(id > 0)
  {
    //父进程
    std::cout << "parent is sleeping" << std::endl;
    sleep(100);
  }
  else
  {}

  return 0;
}

        进程运行后,父子进程都处于S状态

        终止掉子进程。由于父进程正在睡眠,无法回收子进程,子进程进入僵尸状态

走进Linux——进程(二)进程状态_第11张图片

         待父进程结束回收子进程后,两个进程最终都会终止

        僵尸进程的危害

        进程如果一直处于Z状态(即僵尸进程),该进程的task_struct也会一直被维护,也即相关的代码数据与内核数据结构会一直存在。因此系统中如果存在过多的僵尸进程,会导致内存泄漏,造成内存资源的泄漏,因此需要避免这种情况的出现。

        下面再补充系统中的一种进程——孤儿进程

孤儿进程

        事想可能出现如下一种情况:父进程先于子进程退出。

        这样一来,子进程是不是就无法被回收了?我们将这样的子进程称为孤儿进程,孤儿进程会被1号进程所领养,自然也会被1号进程所回收。

        测试代码:

#include 
#include 

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

  if(id == 0)
  {
    //子进程
    while(true)
    {
      std::cout << "child is runing" << std::endl;
      sleep(2);
    }
  }
  else if(id > 0)
  {
    //父进程
    std::cout << "parent is sleeping" << std::endl;
    sleep(10);
    exit(1);
  }
  else
  {}

  return 0;
}

        运行后父进程将会于10秒后退出,之后我们再来看看子进程的状态

走进Linux——进程(二)进程状态_第12张图片

         10秒后,子进程的父进程pid变为1,也就验证了孤儿进程会被1号进程所领养。

你可能感兴趣的:(Linux系统编程,linux,后端,c++,服务器,运维)