深入了解进程概念 第二章
在上一篇的进程的文章中我们初步了解了进程的概念,我们知道了怎么查看进程,操作系统管理进程的方法就是把进程用一个个结构体进程描述然后对这一个个结构体进行管理就行了,然后我们还知道了进程的名片(pid).了解了怎么创建出一个进程今天我们再来进一步了解进程的相关的知识本文从通用进程状态切入,涵盖就绪、阻塞、挂起三大基础状态,并点明内核链表支持进程同时挂入多队列的设计原理;接着聚焦 Linux 下的具体进

个人主页:小则又沐风
个人专栏:<数据结构>
<竞赛专栏>
<Linux>
座右铭
路虽远,行则将至;事虽难,做则必成
目录
前言
在上一篇的进程的文章中我们初步了解了进程的概念,我们知道了怎么查看进程,操作系统管理进程的方法就是把进程用一个个结构体进程描述然后对这一个个结构体进行管理就行了,然后我们还知道了进程的名片(pid).了解了怎么创建出一个进程
今天我们再来进一步了解进程的相关的知识
进程状态

从上面的图片中我们可以得到的信息是进程的状态大概是如下的几种状态:
- 就绪状态
- 挂起状态
- 阻塞状态
但是这时候就有人问了不对吧,这明明图片上有着什么就绪挂起状态什么的,怎么没有包含到上面的总结中呢?
这是因为这些没有说的状态是两个状态的叠加而已,我们了解了单个的状态就可以了解组合的状态了
现在我们来逐个了解这些状态
就绪状态
首先在了解这些进程状态之前我们需要知道的是
一个cpu只有一个调度队列,所有的要运行的进程都要挂在这个上面
所以什么状态下才是就绪的状态呢?
我们知道我们的进程是由自己的task_struct和代码数据构成的,当这个进程被挂在了调度队列上的时候他的状态就是就绪状态了,在等待被调度
一句话总结的就是:只要这个进程是在调度队列上那么他的状态就是就绪状态
阻塞状态
那么什么是阻塞状态呢?
简单的来说当这个进程处于等待的队列中的时候他就是阻塞的状态.
但是它进程在等啥?天青色等烟雨吗?
不是的,在我们平常写的代码中我们就会经常遇到这种的情况.
例如:我们写了一个scanf然后我们运行我们的代码的话,我们的代码运行到这个scanf的时候就会等待我们输入数据,这就是等待.
也就是说当进程需要键盘,显示器.....等待这些外界的设备就绪的时候这个进程就是处于阻塞的状态
当我们的外界的资源就绪的时候,也就是如果我们等待结束的时候这个进程就会从等待的队列,挂到了调度队列,变成就绪状态.
挂起状态
需要知道的是进程的运行也是有优先级的,也按照一个先来后到的,但是我们的cpu的内存就那么大,不能来一个进程就放到了调度队列中,这样的话cpu的运行速度会下降的,这时候就需要把一些优先级不那么高的进程,放回到磁盘中,但是在这里放回的不是进程的全部,而是把他的数据和代码放回去,只留下task_struct当轮到这个进程的时候在拿回来.
这样的状态就是挂起状态.
这时候我们需要分辨的就是,当这个进程的数据和代码被挂起的时候,如果他的task_struct是在调度队列中的话,那么他就是传说中的就绪挂起状态.
如果是在等待的队列中的话就是阻塞挂起状态了
理解内核链表(为什么可以同时挂在多个队列中)
这时候我们可能就有一些疑问了,这个进程的pcb为什么这么厉害,可以存在于多个的队列中.
要知道的是我们在之前学的链表的结构是这样的
struct list
{
int date;
struct list * next;
struct list *pre;
}
这样明明只可以挂在一个队列中啊,但是在这里是怎么设计的?
在这里的链表是这样设计的
struct list_head
{
struct list_head * next, pre;
}
struct task_struct
{
int x;//在这里表示的是进程的相关的信息
int y;
list head link;
}
这样的设计有什么区别呢?
我们来看下面的图片

就是这样的情况了,我们会发现本来我们的链表的指针是指向的整体,但是现在这个设计下指向的就是局部了,那么如果我们在这个结构中多加几个link呢?
这样就可以连接到了多个队列中了,但是这时候就出现了一个问题----怎么访问结构体中的数据?
结构体中数据的访问我们只需要拿到起始位置的指针就可以了,那么我们就可以这样做
&(struct task_struct *)0->link;
这里我们从零地址处创建出一个结构体,然后我们就可以拿到了相对于零地址到link的偏移量.
这时候我们的list-偏移量就是初始地址了
在Linux下进程的状态
在上面我们知道了进程的变化就是在不同的队列中切换,那么我们现在来看看在Linux下具体的实例
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 */
};
怎么有这么多的状态呢?但是没关系万变不离其宗,我们来一个个的查看这些状态.
运行状态
也就是上述的R状态了,也对应的是刚才说的就绪状态,
这个状态表示这个进程处于调度队列中,不一定正在运行
浅度睡眠状态
第二个的S的状态就是这个浅度睡眠状态了,但是什么是浅度睡眠状态?
这和我们刚才讲的那三个状态有什么关系?
这个状态和D(深度睡眠状态---不可中断)都是属于阻塞状态的
怎么触发这个状态呢?我们可以让他阻塞等待输入,也可以让我们的程序sleep上几秒
深度睡眠状态
那么这个状态是怎么个情况?为什么要单独加上一个深度睡眠的状态呢?
我们来看这样的一个场景,我们现在要对磁盘中写入数据,那么在写入的过程中我们的进程就是处于睡眠的状态的,因为他在等待磁盘返回的数据
补充:
写入数据的本质就是数据的拷贝,硬盘在拷贝数据的时候我们最后要得到写入数据的返回值,来表示写入数据的成功与否,所以我们的进程处于阻塞状态
那么在这个时候操作系统中的内存不够用了,看见这个浅度睡眠的进程了,二话不说直接把他结束了.
因为不结束将会导致操作系统的内存严重不足了
最后磁盘写入失败了,当他想把这个结果告诉进程的时候,进程早就不在了.磁盘不能拿着这个返回值度过终身,就把这个数据扔了.
这时候就是传说中数据的丢失了
但是问题是在这个问题产生的过程中没有人是错的,进程等待数据的返回,磁盘无处返回仍数据,操作系统为了全局杀进程都是对的.
那么这时候就需要这个深度睡眠的状态了,就规定了当进程处于深度睡眠状态的时候,操作系统没有权利删除这个进程
在这里插一句话:
如果你的机器处于了深度睡眠的话,那么就说明你的机器要不行了,因为写入数据的速度太慢了
暂停状态
下面的T/t状态就是暂停状态了,那么这两个状态有什么区别呢???
T暂停状态
这个暂停的话就是我们手动的强制的暂停了,怎么触发的呢?
就是把CTRL+c就可以了
t暂停状态
这个暂停状态和上面的状态并不一样,这个出现的场景就是我们进入了调试代码的状态了
就会进入这个暂停的状态
死亡状态
当我们的程序运行结束后,运行结果回收过后,我们的进程就会处于这个状态了
僵尸状态
什么是僵尸???
不是我们电视剧中的所说的僵尸而是我们的进程运行结束后就会进入死亡的状态但是我们知道的是必须有一个进程来得到这个进程运行结束的信息啊
举个例子:
在路边嘎巴一个人死了,首先就会有法医来判断这个人的死亡的信息,看看是不是他杀什么的,这时候来接收进程的返回的状态的时候就是僵尸状态了
僵尸进程
我们知道了在什么情况下会造成僵尸状态之后,下面我们就来了解一下什么是僵尸进程
我们知道的是当一个进程运行结束的时候是谁来接收它结束的信息的?
答案就是父进程
但是当我们的子进程已经结束的时候,这时候就要进行托付他的结束的信息的时候,但是父进程还在运行没有空来接收信息的时候,这时候子进程就会进入僵尸状态
那么如果我们的子进程的数据一直没人管呢?
那么我们的子进程就会变成僵尸进程一直等下去
孤儿进程
在我们讲完了什么是僵尸进程之后我们需要了解的就是这个孤儿进程了,
什么是孤儿???
就是没有父进程的进程呗,但是什么情况下这个进程才会成为一个孤儿呢???
那就是当子进程还在运行的时候我们的父进程就已经退出了,需要知道的是这时候我们的子进程还没有结束,如果结束了就是僵尸进程了.但是僵尸是不可避免的,那么怎么才能让这个进程的数据有人接收呢???
那么我们的1进程就会认领这个进程成为这个进程的父进程
这个进程就会把这个差点成为孤儿进程的进程的退出的数据什么的都接收处理.
总结
本文从通用进程状态切入,涵盖就绪、阻塞、挂起三大基础状态,并点明内核链表支持进程同时挂入多队列的设计原理;接着聚焦 Linux 下的具体进程状态,包括运行、浅度睡眠(可中断阻塞)、深度睡眠(不可中断阻塞)、T/t 暂停、死亡与僵尸状态;最后延伸到僵尸进程与孤儿进程的定义,完整覆盖了进程从创建到消亡的全生命周期关键状态与相关概念。
谢谢大家的观看!!!
之后将会讲解进程的优先级和切换调度.
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)