目录
1.进程状态
进程状态是指一个进程在其生命周期中所处的不同状态
1.1.运行状态
当操作系统选择一个就绪状态的进程来执行时,该进程进入运行状态。在运行状态下,进程占用CPU资源,执行其指令。
当PCB进入运行队列后就可以修改它的pcb->status的值,表示可以被CPU调度
1.2.阻塞状态
当一个进程在执行过程中发生某些事件,如等待用户输入、等待I/O操作完成等,它可能会进入阻塞状态。在阻塞状态下,进程暂停执行,直到事件发生并得到处理。
我们知道在代码运行的过程中,或多或少会访问系统中的某些资源!比如:磁盘、硬盘、网卡等各种硬件设备。当我们在使用cin>> scanf() 时如果我们故意不输入,这时候进程就一直无法继续也就是处于阻塞状态,不具有访问条件,进而进程代码无法继续向后执
在这个进程开始运行到因为不输入导致阻塞的过程中,这个pcb从运行队列中转移到硬件等待队列,如果这个进程开始就绪,那么就退出等待队列回到运行队列中。
这时我们可以猜到操作系统中会有许许多多的不同的队列,运行队列,等待队列,也就可以更好的理解为什么需要pcb这一个结构了!并且一个进程的pcb并不只在一条链表里
那么进程状态变化本质
1.更改pcb->status这个整型变量
2.将pcb连入不同的队列中
也就是进程相关的过程,只与pcb的状态有关,与代码和数据无关。并且操作系统是第一个知道他所管理的设备状态的变化(操作系统是第一时间察觉进程状态的改变并对pcb进行操作),处于资源无法就绪,阻塞状态时,就把PCB连入相应的等待队列。当资源就绪时,就把进程状态换为R,放入运行队列,准备好给CPU调度。
1.3.挂起状态
是指一个进程或线程暂时停止执行,但仍然保留在内存中等待重新调度的状态。在挂起状态下,进程或线程不会占用CPU资源,但其在内存中的状态和数据仍然被保存
如果一个进程处于阻塞状态,那么我们知道该进程是无法被调度,处于内存中等待资源就绪。如果此时,os恰好内存资源已经严重不足时,这时候会怎么样?
实际上磁盘上会专门开辟一块swap分区,留一部分空间给操作系统进行内存外存数据的置换(用时间换取空间,会牺牲时间)。当进程的资源就绪并被OS调度时,曾经被置换的进程代码和数据,又要重新加载进来。
那么这个场景就为:阻塞挂起
1.4.终止状态
当一个进程完成其任务或被操作系统终止时,它进入终止状态。在终止状态下,进程释放占用的资源,并从系统中移除。
2.Linux下的进程状态
因为在不同操作系统中,进程的状态也会不同,这里我们主要以Linux为主要讲解对象
/*
* 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 */
};
上述代码为kernel源代码中对于状态的定义,也就是Linux的进程状态
2.1.常规的进程状态
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
我们发现为什么已经有了S状态的同时还是需要D状态?他们两个不都是休眠状态吗?
首先休眠状态对应进程状态中的阻塞状态,也就是此时资源不足以让某些进程被CPU调度。如果内存中运行队列已有大量进程,这时候操作系统为缓解内存的资源不足,会主动杀掉某些阻塞状态的进程。在某些场景中,假如某个进程正在往磁盘中写入一些重要数据,当写入过程中数据无法正常写入造成的阻塞状态被操作系统误删掉这个进程导致重要数据的丢失!所以我们需要一个特定的阻塞状态,也就是D状态,来保证当进程在向磁盘中写入数据时,被操作系统标志为不可中断的睡眠状态,从而跳过D状态的进程,保留这一份数据,返回错误信息给用户。
2.2.僵尸进程和孤儿进程
进程退出的核心工作之一:将 PCB 和 自己的代码和数据 从内存中释放。
也就是在进程退出时,我们不仅需要释放资源,还需要接收进程退出的相关信息,一般就是该进程对应的父进程读取子进程的PCB,来了解子进程对任务的完成情况。
实际操作系统会在进程退出时,将退出信息写入当前进程的PCB,允许代码和数据空间立即释放,但是进程的PCB不可以被立即释放,需要被父进程接收退出信,得出子进程的退出原因。
2.2.1僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id > 0)
{
while(1)
{
printf("parent is running...\n");
sleep(1);
}
}
else
{
int count = 5;
while(count--)
{
printf("child is running \n");
sleep(1);
}
printf("child is zombie\n");
exit(EXIT_SUCCESS);
}
return 0;
}
我们在Linux下运行上述代码,并观察进程信息,发现经过子进程退出时,父进程未读取到子进程的退出信息,确实会导致子进程进入僵尸进程。
僵尸进程的危害:我们知道当一个进程一直处于Z状态,这时它的PCB一直没有被父进程读取,就一直在内存中占据空间,最终导致内存泄漏。
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护。如果一个父进程创建了很多子进程,但是不回收,就会造成内存资源的浪费。因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。
2.2.2.孤儿进程
我们知道僵尸进程是子进程退出后没有被父进程读取PCB,那么如果子进程在正常运行时,父进程意外退出,那么后续子进程将要退出时的PCB也无法被读取。这个时候的子进程即为孤儿进程。(正常运行的子进程,没有父进程了)
实际上当子进程进入孤儿进程时,会被操作系统分配给init进程,也就是被其他进程领养,进而当该子进程退出时,也会有父进程来接收它的PCB中的退出信息。