[Linux]进程状态

[Linux]进程状态

进程状态的概念

了解进程状态前,首先要知道一个正在运行的进程不是无时无刻都在CPU上进行运算的,而是在操作系统的管理下,和其他正在运行的进程轮流循环使用CPU,而操作系统管理进程是否需要放到CPU上进行计算,所依据的就是进程状态。

阻塞状态

阻塞状态是进程因为等待某种资源就绪,而导致的一种不推进的状态。

阻塞状态从主观上给人的感觉就是进程“卡”住了,比如进程在下载某一个软件过程中,网络断了,进程下载软件的过程就会"卡"住,进程需要等待网络这种资源就绪,才能继续推进。因此进程处于阻塞状态时,一定是在等待某种资源。进程进入阻塞状态是想要通过等待的方式,等具体的资源被别人使用完后,给自身使用。

由于操作系统要管理各种各样的硬件,因此操作系统需要用某种结构的描述硬件,然后为了方便管理要将这些结构组织起来,描述这些硬件时,会有一个描述信息就是等待队列,这个队列会记录正在等待当前硬件资源的进程的PCB,操作系统就是通过这样的大致原理实现的让进程进入阻塞状态:

image-20230804144704352

挂起状态

挂起状态是进程等待某种资源就绪时,操作系统系统将进程在内存中的代码和数据释放的状态。

挂起状态下进程的PCB还在硬件的等待队列上,操作系统认为进程等待的资源需要很长时间才会准备就绪时,就会释放进程在内存中的代码和数据,让多出来的内存空间用于做其他的事情,提高内存的利用率,当进程等待的资源准备就绪后,操作系统就会把进程的代码和数据再加载到内存中,将进程启动起来。

Linux下的进程状态

Linux系统下,在kernel源代码里定义了如下进程状态:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
    
    
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

说明: Linux下的进程会在task_struct设置一个变量来记录进程状态。

R状态

R状态就是运行状态,说明进程的task_struct在操作系统中的运行状态等待队列之中,在操作系统轮循执行处于运行状态的进程的代码的过程中,被执行。

注意: 运行状态的进程不代表一直都在CPU上执行,而是和其他处于运行状态的进程一样,轮循的被执行。

为了验证R状态,编写如下代码:

#include <stdio.h>

int main()
{
    
    
  while(1)
  {
    
    }
  return 0;
}

在Linux系统下运行程序并查看进程状态:

image-20230823170847439

由于这段代码的执行不需要任何资源,不会进入阻塞状态,因此会一直保持运行状态。

S状态

S状态是可中断休眠状态,可中断休眠状态是Linux系统中阻塞状态的一种。

为了验证S状态,编写如下代码:

#include <stdio.h>

int main()
{
    
    
  while(1)
  {
    
    
    printf("hello world\n");
  }
  return 0;
}

在Linux系统下运行程序并查看进程状态:

image-20230823171246424

在这段代码中虽然程序是一个死循环操作,但是由于循环结构的代码运行很快,而将数据打印到屏幕上很慢,因此造成了程序运行时,大部分的时候都是在等待打印屏幕的资源,因此查询状态时大部分时间查询到的都是S状态。另外可中断休眠状态下的进程可以选择输入ctrl+z终止进程。

D状态

D状态是不可中断休眠状态,不可中断休眠状态是Linux系统中阻塞状态的一种。

D状态是在进程将数据从内存传输到磁盘时,磁盘的压力过大,导致传输数据的速度很慢,进程必须等待数据传输完成处于休眠状态,为了避免操作系统将这个处于休眠状态的进程杀死,因此将进程设置成不可中断休眠状态。D状态不是一种常见的状态,如果进程处于D状态一般说明计算机磁盘压力过大。

T状态

T状态是暂停状态,暂停状态下进程不再继续运行。

为了验证T状态,编写如下代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    
    
  int i = 1;
  while(1)
  {
    
    
    printf("hello world %d\n", i);
    i++;
    sleep(1); //Linux系统提供的休眠函数,头文件是unistd.h
  }
  return 0;
}

在Linux系统下运行程序使用kill -19 进程id暂停进程并查看进程状态:

image-20230823184608894

输入kill -18 进程id可以重启进程:

image-20230823185051979

补充: Linux下进程状态后面的+的含义是这是一个前台进程:

image-20230823185204139

进程状态中没有+的是后台进程:

image-20230823185408461

在Linux下前台进程运行时,bash是失效的,而在后台进程运行时bash是可用的,另外后台进程可以使用kill -9 进程id来关闭。

t状态

t状态是追踪式暂停状态,是T状态的一种特例。

为了验证t状态,可以借用gdb工具,使用gdb开启调试一个程序:

image-20230823190655655

Z状态

Z状态被称作僵尸状态,僵尸状态下进程已经完全停止了,但是保留了进程的task_struct和部分数据。进程进入僵尸状态是为了了解进程的运行结果是否出现问题,因为保留的数据中包含退出码等信息,而退出码是用于判断进程运行结果是否出现问题的数据。进程终止后都会进入僵尸状态等待父进程的回收处理。

为了验证T状态,编写如下代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    
    
    pid_t id = fork();
    if (id == 0)
    {
    
    
        //子进程
        while(1)
        {
    
    
            printf("我是子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    else if (id > 0)
    {
    
    
        while(1)
        {
    
    
            printf("我是父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

在Linux系统下运行程序使用kill -9 进程id杀死子进程并查看进程状态:

image-20230823200716754

僵尸进程的危害: 由于僵尸状态下进程的task_struct和数据得到保留,因此会占用内存空间,如果创建多个子进程不回收就会造成大量的空间被占用,造成严重的内存泄露问题。

X状态

X状态被称作死亡状态,死亡状态下进程完成停止了,并且操作系统会很快将进程的task_struct和代码和数据回收释放。

孤儿进程

孤儿进程是在进程运行时,父进程停止,父进程转为操作系统(1号进程),被操作系统管理的进程。

为了验证孤儿进程,编写如下代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    
    
  pid_t id = fork();
  if (id == 0)
  {
    
    
    //子进程
    while(1)
    {
    
    
      printf("我是子进程,我的pid:%d, 我的ppid:%d\n", getpid(), getppid());
      sleep(1);
    }
  }
  else if (id > 0)
  {
    
    
    //父进程
    int cnt = 5;
    while(1)
    {
    
    
      printf("我是父进程,我的pid:%d, 我的ppid:%d\n", getpid(), getppid());
      sleep(1);
      if (--cnt == 0) break;
    }
  }
  return 0;
}

为了方便编译,编写如下makefile文件:

myproc:myproc.c
		gcc -o $@ $^
.PHONY:clean
clean:
		rm -f myproc

准备好代码和makefile文件后编译得到程序,然后运行程序,并使用while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; done指令来每隔一秒查询一次进程状态:

image-20230824134857086

进程myproc中的父进程的父进程是bash,它停止后进入僵尸状态后,bash作为其父进程会进行回收操作,因此无法看到其僵尸状态,进程myproc中的子进程仍在进行,但是原有父进程停止了,需要有新的父进程来回收它,否则会造成其停止后,僵尸状态无法回收的情况。

变成孤儿进程后,就变成了后台进程,可以选择使用killall 进程名指令杀死同一进程名的所有进程。

猜你喜欢

转载自blog.csdn.net/csdn_myhome/article/details/132472734