前言

本章讲关于进程的进程状态。

一、操作系统的进程状态

(1)核心三态:

就绪状态:进程已经准备就绪,正在等待队列中等待,已经获取了除了CPU资源以外的其他资源。

运行状态:正在被CPU执行,单核CPU下,最多只有一个进程处于这种状态。

阻塞状态:等待某种状态(键盘输入...),而暂时无法运行,即使CPU闲置也无法运行。

(2)扩展两态

创建状态:进程正在被创建,PCB已经形成但是数据和代码没有被分配,无法执行。

结束状态:进程结束运行,操作系统正在回收占用资源,PCB还在来让父进程处理(否则就是僵尸进程).

(3)深化两态

就绪挂起态:进程在外存中准备就绪,只要被唤入内存就可以运行。

阻塞挂起态:进程在外存中被阻塞,等待事件。

(4)转换关系

就绪态->运行态:处于调度队列最开始,被CPU执行。

运行态->就绪态:时间片用完或者被更高优先级程序抢占。

运行态->阻塞态:请求I/O或等待事件。

阻塞态->就绪态:等待事件完成。

这里通过一个图片讲述一下挂起

磁盘中有一个区域叫swap交换空间,当内存空间不足时,我们就将暂时用不到的数据和代码放入这个区域中以减缓内存的压力,这个就是挂起,PCB不会放过去。当然不能把所有的代码和数据放入进去,只有在触发一个内存不足的阈值才会将暂时用不到的数据和代码放入。毕竟时磁盘,CPU访问数据慢,所以系统的速度就会降低。所以挂起的本质就是通过将数据放入磁盘但是访问数据变慢来换取空间,即时间换空间。

二、Linux下进程的状态

1、常见的信号信息

2、详细讲解

你可以用 ps aux |grep  "文件名"   或者 ps ajx |grep "文件名"去看进程。下面是一张Linux进程状态图。

Linux操作系统下的进程状态与操作系统下的类似,进程不是一直处于同一种进程状态,而是可以切换不同种的进程状态。看看源码。

static const char *const task_state_array[] = {
"R (running)",

"S (sleeping)",

"D (disk sleep)",

"T (stopped)",

"t (tracing stop)",

"X (dead)",

"Z (zombie)",

};

(1)R(running):可运行状态

这个状态有两个子状态,一个是正在运行状态,还有一个是在调度队列中等待时间片的就绪态。由于运行态时间片时间非常短,所以我们大部分时间看到的其实都是就绪态,这个最常见的一个误区就是,这个状态不意为着运行态,还包括就绪态。

int main() {
pid_t pid=fork();
while(1)
{

}
return 0;
}

结果

这里是创建了个父子进程所以才有两个exe,注意这里while里不写printf,sleep之类的才可以看到R进程,否则非常容易看到S这个进程,就像这样。

补充一点,就是后边有一个+就是在前台,同时可以看到R状态的两个进程,一个96.3一个是97.7,可以看到二者占用CPU之和大于100,也就是说是并行运行状态,还有一个状态是并发运行状态后续会细讲。

(2)S(Sleeping)睡眠状态:可中断

这时进程正在等待一个可中断的事件,比如说等待键盘输入,等待sleep时间等等等,并不占用CPU,比较空闲,当等待的事件发生时或者信号传来(kill),则会中断进入R状态。

int main() {
    //pid_t pid=fork();
   int a; 
   scanf("%d",&a);
   printf("%d",a);
   return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main() {
    //pid_t pid=fork();
    while(1)
    {
        sleep(1);
        printf("我是一个进程");      
    }
   return 0;
}

可以看到第一个代码我等待输入时S状态以及sleep时也是S状态。

补充:这个和后边的D有区别,这个叫浅度睡眠,可中断的睡眠。

(3)D(Disk sleep):不可中断状态,深度睡眠

进程在等待关键I/O操作的状态,此时不占用CPU,不能被信号中断(kill)

重点:D是一个保护态,因为如果在内核操作数据的过程中被中断,可能导致数据损坏或系统崩溃。D态进程无法被唤醒,只能等待I/O完成或系统重启。

原因及作用:1、内核的自我保护

           2、防止数据损坏

           3、防止系统崩坏

(4)T(stopped)停止状态:

   进程主动暂停,不消耗资源也不消耗CPU。

 触发条件(进入T态):发送SIGSTOP信号,或者按CTRL+Z实现挂起操作。kill -SIGSTOP 文件PID

   结束条件(退出T态):可以通过发送SIGCONT信号将其唤醒,回到R态。kill -SIGCONT 文件PID

(5)t(tracing stop)追踪停止状态(不重要):

        这是一种特殊的暂停,特指被调试器(如gdb)追踪而暂停。当你在调试器中设置了断点,进程执行到断点处就会进入t态,此时调试器可以读取和修改其内存或寄存器。

  (6)X(dead)死亡状态(不重要):

       进程已彻底终止,其task_struct(PCB)也已被内核释放。这是一个仅存在于理论的状态,我们无法通过任何命令看到它,因为“看到”的动作发生在它存在期间。

(7)Z(zombie)僵尸状态(非常重要):

        定义:子进程已经退出,其资源(内存、文件)已经被释放,但是PCB没有被释放,它的父进程还没有来读取,此时就会占用PID和内核进程表的一个条目。

        特点:不占用内存和CPU资源,并且没有办法kill。

        危害:占用PID(有限资源),导致后续无法生成新的PCB,并可能造成内存泄露.

        处理:1、可以杀死它的父进程或者等父进程退出,此时就会它的父进程就会PID=1的进程,此时就会读取它的PCB,完成回收工作。 

                   2、等父进程调用函数读取子进程的PCB,回收(理想情况)。

僵尸状态验证代码:(就是为了让子进程先退出,而父进程后退出)

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

看一下怎么清理僵尸进程退出父进程,

或者杀死父进程,

   三、孤儿进程

     这个是一个进程关系并不是一个进程状态,这个孤儿进程可能存在任何一个状态。孤儿进程顾名思义就是父进程提前退出了,子进程没有退出此时就会被PID=1的进程收养,此时这个子进程的父进程就相当于PID=1这个进程,此时就可以被回收也就不会产生像僵尸进程那样的PID占用问题。这里看一段代码和结果。

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

   这里需要将子进程kill掉才能退出进程,具体看右边,否则退不出去。

        为什么需要这样才能退出呢?是因为它脱离了前台进程组,内核阻止了终端中断信号的传递。可以保护后台任务。

四、总结

    本文讲述了操作系统的进程状态,有相应代码,可以下边尝试一下。同时可以点点赞关注一下。

下次更新优先级以及调度的问题。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐