目录
1.进程排队
时间片
时间片的分配
结构体内存对齐
偏移量补充
对齐规则
为什么会有对齐
2.操作系统学科层面对进程状态的理解
2.1进程的状态理解
①我们说所谓的状态就是一个整型变量,是task_struct中的一个整型变量
②.状态决定了接下来的动作
2.2运行状态
2.3 阻塞状态
2.4 挂起状态
3.linux下具体的进程状态(重要)
3.1 R和s
(top)命令
+:前后台程序
3.2 休眠状态s
3.3 disk sleep 不可中断睡眠也叫作深度睡眠。
3.4 T状态:进程变成暂停状态
3.5 t状态
3.6 z状态:僵尸状态
4. 孤儿进程
5.结语
1.进程排队
狭义上讲,进程 = task_struct + 可执行程序。
①当一个进程加载到内存的时候,首先这个进程不是一直在内存中运行的也不是一直在运行的。我们启动一个软件,并不是说我们启动这个软件,他就是一直被运行的,他可能因为要等待某种资源加载而处于某种等待状态。
等待键盘资源输入,所以进程卡住等待我们输入:
②即使进程被cpu调度,也不是一直在运行的。
当我们写一个while死循环的程序,交给cpu去运行,cpu可能被打满,如果cpu只是执行我们这个程序,那么就意味着这个程序会一直在cpu上运行,没有结束就从cpu上下不来,其他进程不可能被调度,那么其他所有进程跑不了,但是事实是:可能此时会卡,但是其他进程还是正常运行,这说明进程不是放在cpu就要等执行完才下来,当代计算机都支持一个时间片的概念,cpu会给你这个程序一个运行时间,你上来就跑这么长时间,跑完就走,还要让其他进程执行,比如这个时间为1毫秒,1毫秒就是时间片.
时间片
时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。现代操作系统(如:Windows、Linux、Mac OS X等)允许同时运行多个进程 —— 例如,你可以在打开音乐播放器听音乐的同时用浏览器浏览网页并下载文件。事实上,虽然一台计算机通常可能有多个CPU,但是同一个CPU永远不可能真正地同时运行多个任务。在只考虑一个CPU的情况下,这些进程“看起来像”同时运行的,实则是轮番穿插地运行,由于时间片通常很短(在Linux上为5ms-800ms),用户不会感觉到。
时间片由操作系统内核的调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片,然后每个进程轮番地执行相应的时间,当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片,如此往复。
时间片的分配
通常状况下,一个系统中所有的进程被分配到的时间片长短并不是相等的,尽管初始时间片基本相等(在Linux系统中,初始时间片也不相等,而是各自父进程的一半),系统通过测量进程处于“睡眠”和“正在运行”状态的时间长短来计算每个进程的交互性,交互性和每个进程预设的静态优先级(Nice值)的叠加即是动态优先级,动态优先级按比例缩放就是要分配给那个进程时间片的长短。一般地,为了获得较快的响应速度,交互性强的进程(即趋向于IO消耗型)被分配到的时间片要长于交互性弱的(趋向于处理器消耗型)进程。(引自百度百科)
所谓进程排队,一定是为了等待某种资,可以包括cpu,键盘、磁盘、等等...........
而内存中有很多的进程队列,进程排队是经常的进程描述块在排队。就比如我们去找工作时,投递简历,简历在hr处排队,拿到誰的简历了,就让相应的人来,是简历在排队。而简历就可以说是描述人的工作属性的一个结构体或者类。。所以只要是排队,一定是进程的task_struct在排队。 而一个task_struct可以被链入多种数据结构,linux中是被链入双链表中的,linux实现双链表的方式和我们c语言中是不大一样的:
首先在pcb中定义了这样一个结构体:
struct listnode
{
struct listnode *next;
struct listnode *prev;
};
在每个进程的pcb中
我们通过链式对象找到listnode对象,那么还有很多的属性怎么访问呢。原理
struct listnode 是 task_struct中的一个变量也是一个地址。现在就相当于我们知道了结构体中一个变量的地址如何知道其他成员的地址呢。
结构体偏移量:补一下结构体偏移
了解的伙伴可以直接跳过
结构体内存对齐
题型考察结构体的大小,我们来看一下例子引入,请问如下这段代码输出分别为什么:
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
明明是两个一样的结构体,为什么却不一样大,要知道答案我们就要知道结构体的大小是如何计算的,结构体大小的计算并不是单单就靠结构体内部元素的类型大小来决定,让我们来看一下:
偏移量补充
offsetof() 这个宏可以计算结构体某一个成员相较于起始位置的偏移量
头文件:stddef.h
需要在宏中传入的是:结构体类型和结构体变量名,下面我们来计算一下结构体s1中成员的偏移量和S2结构体的偏移量
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; int main() { /*printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2));*/ printf("%d\n", offsetof(struct S1, c1)); printf("%d\n", offsetof(struct S1, i)); printf("%d\n", offsetof(struct S1, c2)); return 0; }
对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8 Linux中没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。所有成员对齐数的最大值。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们来看一下图解S1的对齐:
对对齐规则第四条的解释:
让我们来看一下结构体嵌套的对齐算法:
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
看一下这段代码输出风别为多少
struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S3)); printf("%d\n", sizeof(struct S4)); return 0; }
如果出现数组,就当做多个同类型数据处理
为什么会有对齐
大部分的参考资料都是如是说的:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。比如某些平台规定整型必须存放在4的整数倍地址处。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。
虽然浪费了一些空间,但是换来了访问效率的提升
总体来说: 结构体的内存对齐是拿空间来换取时间的做法。 那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到: 让占用空间小的成员尽量集中在一起。
了解了结构体偏移量,就可以利用偏移量,计算我们的task_struct的地址了:
&c = &首元素+偏移量。结构体中成员越靠后地址越大
一个整型四个字节,每个字节一个地址,拿到的是最小的,因为有类型,也就是首地址+偏移量拿到4个字节的内容。数组访问,结构体成员访问底层都是这个样子。所以这里也是一样,我们可以使用偏移量来访问task_struct中所有的的其他属性内容。
&task_struct = &struct listnode+偏移量,那么这个偏移零的算法为:设我们一个节点为n
&((task_struct*)0->n);
&n-&((task_struct*)0->n) = 开始
(task_struct)(&n-&((task_struct*)0->n)) 得到task_struct地址
这既是linux内核的实现,会是一个宏的形式,这里是介绍会忽略掉一些类型转换。
那么我们说再全局层面上,所有的进程的pcb都要被链接入一个链表,方便我们的操作系统进行管理,那么,当我们的经进程需要排队运行的时候,需不要单独将进程从整体的双链表上取下来单独放入排队等待这个队列里面呢,答案是不用,
原因:誰规定我们的pcb里面只能有一个listnode这种结构体呢,没有人规定,那么我们的pcb中就可以有多个这种结构体,一个用于将这个task_struct链接入整体的双链表中进行操作系统级别的管理,其他剩余的可以在不把我们的单个task_struct拿下来的前提下再链接入其他的数据结构,比如进程排队的队列等等等等。
这个就叫一个task——struct可以被 链接进入多种数据结构的原因。
那么当我们的操作系统删除一个进程的时候,一定会伴生着将当前的进程从链表、队列中移除,但是所有的移除和task_struct没有关系,只用移除struct listnode.
这就是进程排队,那么进程排队是去哪里排队呢?接下来看进程状态
2.操作系统学科层面对进程状态的理解
首先教材删上的表述:
对于初学真的不好理解
2.1进程的状态理解
①我们说所谓的状态就是一个整型变量,是task_struct中的一个整型变量
#define NEW 1
#define READY 2
#define RUNING 3
#define BLOCK 4 1
我们进程的task_struct中
task_struct
{
int status;
};
也就是说我们未来进程的状态就取决于这个ststus变量里面保持的是数字几,。
②.状态决定了接下来的动作
今天状态不好今天想摆烂,今天状态好,库弛库弛的写几套卷子,所以状态决定了对象的后续动作。linux中存在很多的进程,每一个进程都有一个状态,那么就可能存在多个进程都要根据他的状态执行后续的动作,而且还必然可能存在多个进程的状态是一样的,(虽然进程很多,状态类型也多),所以就需要进程排队
2.2运行状态
一个cpu一个运行队列,(如果电脑中有两个cpu,会有两个运行队列,操作系统在内核编译的时候,识别到两个cpu,就会创建两个队列,当运行多个进程的时候,原则上,操作系统是要均衡的将进程负载到两个cpu上,这也成为SMP,负载均衡式的给两个cpu平均分配资源,但是值得注意的是,一个进程这次调度在这个cpu上,下一次还是被这个cpu调度。)当我们的某个或者某些进程的资源的准备OK了,那么就可以进进程放入cpu的运行队列了,将进程的task——struct放入运行队列:
(关于怎么排,在优先级内容里面讲解)
只要放在cpu上对应的运行队列中1,我们就把该进程当前状态称为运行状态
(至于运行队列里面保存的是pcb结构体还是只是节点struct listnode ,这和具体的系统和设备有关,有些操作系统可能用的指针或者一样的node,但是我们只要能够理解要的是链接结构就OK了。)
解释:
一个进程是不是只有在cpu上跑的时候才是运行状态,对吗?这样的操作系统有,不过是在教材的描述里面,这样的描述是在教材里面的描述,当然,在cpu上运行当然是运行状态,不过主流的操作系统,只要在运行队列里面都可以叫做运行状态。所以,所谓的创建和就绪在实际的操作系统中并没有实现,要运行就放在运行队列,不运行就放在常量队列里面。所以r状态就表明进程已经准备好随时被调度了,所以就绪和执行被二合一,因为这两个独立的状态看不到。
2.3 阻塞状态
以硬件为例子:
os对硬件进行管理
先对硬件的属性进行描述
struct device
{
int tyoe;//1表示键盘,2,表示网卡,3表示磁盘......
//设备的操作方法,
//设备的状态
struct listnode n;
:::
:::
:::
};
在将各个设备使用数据结组织起来
和进程的管理方式一模一样,这就是操作系统为了对硬件做出管理做出的数据结构描述对象,
当前假设我们的计算机内部是这个样子:
现在比如说我们运行这个程序:
当我们程序运行起来,是不是就意味着此时进程需要等待键盘输入,不能继续往下面执行了,除非键盘输入 。所以,此时操作系统就来了,这个进程在等待什资源,就将这个进程的状态从运行状态改为非运行状态,我们可以暂时称为阻塞状态,然后将这个进程放到我们对应设备资源的队列当中。我们的cpu也是属于设备的,只不过是一个跑得快的设备,那么cpu可以有进程的运行列表队列,我们其他的设备一样的可以有我们的进程队列,当我们的进程需要对应的设备资源的时候,这个时候要等待这个设备就绪(键盘输入)才能继续执行,进程不能继续执行就从运行队列连入到我们的设备进程队列里面,因为可能需要这个设备资源的进程不止这一个。
接下来,cpu就不会调度这些连入底层设备调度队列里面的进程了,cpu只会调度在运行队列里面的进程,所以这个进程后面代码不执行了,也就是程序卡在这个地方等待输入。当我们在键盘输入数据了,而我们的键盘输入没有数据,也就是硬件的就绪状态,只有我们的操作系统清楚,操作系统可是我们硬件的管理者,所有用户按键过后,键盘工作OK了,操作系统就将这个进程将这个进程移到cpu的运行队列里面,将阻塞状态改为运行状态,我们操作系统的工作就完成了,剩下的就交给cpu去调度。(以上对两个进程pcb描述的移动,只是形象化的说法,我们进程的pcb是不会也不能删除的,只不过的是改变pcb里面对应的链接字段也就是listnode)
我们不要用人类的感受衡量计算机的速度,此时再次执行到输入的话,我们的设备状态就绪(理解为操作系统已经将数据从外设搬到内存了),有数据了,就可以继续往下执行。
结论:
当我们的进程在进行等待软硬件资源的,资源如果没有就绪,我们的进程task_struct只能将自己设置为阻塞状态,将自己的pcb连入等待的资源提供的等待队列中。
所以状态的变迁,引起的是pcb搬迁到不同的队列中。
2.4 挂起状态
挂起状态在linux中并不是一个比较常见的状态,大部分情况下计算机极少出现这种情况
挂起状态的前提:
计算机资源比较吃紧,也就是说计算机的内存资源比较吃紧了,(并不排除其他情况)
进程在阻塞状态的时候,我们的程序的代码和数据是不会被执行和访问的,那么这个时候,这个进程又不会被调度,但是进程的代码和数据又占据着内存的空间,如果此时计算机内存已经很吃紧了,操作系统很大可能就会将这个进程的代码和数据放回磁盘中,当这个进程快要被调度的时候,再拿回内存。这个我们称之为:阻塞挂起。还有运行挂起等等
(进程会等待某种资源,此时进程大概率是不会被执行的,这种资源如果短期内不会就行,进程就在阻塞队列等待,如果突然计算机内存资源非常吃紧,cpu和整机资源越来越少,甚至严重不足,已经无法为上层运行分配内存,此时对于计算机来说即将面临大量操作的失败,操作向上提供好的环境其他就无了,操作系统也面临着崩溃问题,要么摆烂崩溃,要么搞点内存,所以操作系统就会对整个机器的每个内存做说明,有些内存现在就要用,有些现在不用却占着内存比如阻塞状态进程,那么就造成资源的浪费,所以操作系统就尝试将这个进程的代码和数据交换到磁盘中,磁盘有固定的区域,是为了在内存紧张的时候用于和操作系统进行数据的换入换出叫做swap分区,
真正当内存吃紧,操作系统会将很多很多进程的代码和数据换入换出,节省空间,还有一些为网络预留的空间也会被释放,也就是说整个系统中能被释放的都要释放。传出到磁盘叫换出,等要用或者即将用到的时候再加载回来叫换入,一但对应的进程对应的数据和代码不在内存中,就称当前状态为挂起状态。特指阻塞挂起)
所以所谓挂起,就是将进程的代码和数据写入到外部的磁盘,当进程要被调度的时候再放回来,这是临时的腾空间,这是操作系统在内存不足的情况下进行辗转腾挪,想把空间变大一点,要是内存分配不足,操作系统挂了,整个就挂了,慢点总比挂了好。
思考:,换入换出的时候,pcb数据结构是不会被换出的,因为操作系统就是根据属性知道的进程处于什么状态。
创建进程是先创建pcb还是先将进程的代码和数据先加载到内存?
如果我们的内存很小,比如只有16G,但是我们现在要运行80g的游戏,
只会先加载一部分, 自己的代码和数据刚开始可能非常大,根本加载不完,只会先加载一批,那么当前进程该不该运行呢?先将pcb保存内存里,没有代码和数据也不影响,未来也可以换入,再调度执行,所以双击运行的时候,会先创建管理字段,创建完,内存和字段都还不会加载到内存。
辅助理解例子:
我们高考毕业的时候,人还没有到学校,但是学校在录取我们的时候就已经将我们的档案拿走了,等我们九月份开学的时候,我们该在那个班级,学号是多少,都被排得明明白白,就好比,进程运行的时候,先将pcb建立出来,再将数据等加载,然后就静等调度了。
既然数据要换入换出,可以将磁盘分区搞大些吗?绝对可以,操作系统hold得住,但是一般在系统设置的时候,内存分区的大小一般为内存的一半或者就是内存的大小,最多不超过内存的两倍。
理由:整体的换入换出是将数据进行拷贝的过程,冯诺依曼体系告诉我们,计算机当中,本质进行数据访问访问外设速度是比较慢的,在阻塞挂起时,换入换出,使用效率换系统可用性,就是运行变慢和系统可用之间选择了系统可用。如果分区设置非常大,系统就会很依赖这个swap分区,想办法将这个用完,那么操作系统和外设的io操作比重就很高,效率就会越来越慢。
3.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 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- Z(zombie)-僵尸进程 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
3.1 R和s
编写一段代码如下,执行:
查看我们的进程:
stat就描述了当前进程的状态
为什么程序在运行,状态却是S,睡眠状态
因为cpu执行我们这行输出的时间非常快,然后我们写了sleep,睡眠1秒,那么对于cpu来说,整个程序很长的时间都是在sleep,那么这个状态显示就不奇怪了
接下来修改一下代码,注释掉sleep
代码疯狂运行:
状态还是s
因为,我们自己的代码中有printf,向显示器打印,这个程序在远端服务器跑的,当进行执行的时候,结果打印在我们的电脑上,printf是要访问外设的,不论是网络设备还是显示屏,都是要访问外设的,那么我们想打印的时候,我们的设备就绪了吗?,cpu可以比其他外设的速度快多了,第一次打印了,第二次能保证数据已经刷新到设备了吗?当执行打印代码,将数据执行到缓冲区,设备来得及刷新吗?不一定,所以printf里面就伴生着使得设备处于就绪状态的东西,显示是睡眠状态,就说明当期进程百分之95的时间都是在等待,只有百分之五的时间在运行。
我们再修改代码:
此时就是运行状态了(不访问任何外设还是个死循环)
在目前系统里,可能有多个进程,只有在运行队列里才是运行状态,大部分进程等待外设是非运行状态
(top)命令
为什么grep总是r
grep过滤也是一个进程,只有被调度时才可以帮我们打印过滤结果,把自己过滤出来,就相当于自己知道自己是什么状态,所以一睁眼就知道自己醒着。
+:前后台程序
表示这个程序是前台进程还是后台进程(信号文章详细讲解)
一般程序在运行的时候,我们将程序称为前台程序
此时的状态为:s+
我们执行一些指令时没有办法截停这个程序,当我们执行ctrc就可以截停
此时我们在执行程序命令后面+ &
我们运行一些指令也是可以的:
发现按住ctrls 无法截停
当前进程状态为:
就为s没有+号,这种进程我们就称为后台进程,前台进程可以被键盘直接杀掉,后台进程不可以,只能通过kill -9 进程pid 来杀死
批量化下载等可以在指令后面+&
3.2 休眠状态s
编写代码如下:
在等待键盘输入,linux中的休眠就等价于操作系统学科中的阻塞状态。
阻塞描述的是宏观,在linux中是s状态,但是不仅仅只有上述一种阻塞s状态,
这种进程可以通过ctrlc结束,所以我们也将linux中的这种状态称为可中断睡眠,也称为浅度睡眠
3.3 disk sleep 不可中断睡眠也叫作深度睡眠。
举例来说当我们的进程要将一块很大的数据给我们的磁盘,磁盘比较慢,进程就一直等待等待,那么在内存比较吃紧的时候,操作系统也是会移除某些进程的,操作系统过来一看你什么也不干,还在这里占着内存,将移除了这个进程,然后此时如果我们磁盘出了异常,拷贝的数据找不到来头,无法继续拷贝就丢弃了,这不行,当这个进程一直等待,操作系统也不能将他移除,就叫深度睡眠。
3.4 T状态:进程变成暂停状态
某个进程要访问某种资源,但是呢某种资源又不能被这个进程所访问,操作系统又不想杀掉这个进程,但是为了不让这个进程做某些非法的操作,就将进程设置为暂停状态。
kill -l 命令·可以·查看信号
9号命令可以帮我们杀死一个进程
19号命令:给一个进程发送一个暂停信号
状态变为暂停,没有+号 ,进程一但暂停,就从前台转变为后台进程了。
18 号信号,进程继续启动:
3.5 t状态
我们调试这段代码:
我们在输入出打上断点过后,让程序运行后我们看一下进程的状态
进程会变成t状态,也是一种暂停,是一种被追踪的状态
等待我们输入或者按下n
等待也是一种阻塞状态。
3.6 z状态:僵尸状态
进程创建的目的是让进程帮助完成某种任务,任务死掉了,我们要获取进程的退出数据,是正常退出还是遇到异常,退出后,代码和数据可以直接释放,因为进程死掉了,后续的代码不会再执行了,但是进程pcb不应该立马释放,应该缓一下,为了未来让系统或者其他进程获取一下他的退出数据,只有进程的退出数据被拿走之后,这个进程才是正常死亡状态,pcb才可以被释放。
我们将一个进程执行完毕,但是当前并没有去获取这个进程的退出数据时,我们将这个进程的状态称为僵尸状态。
通常情况下,是由父进程产生子进程,当子进程退出,父进程就必须去读取一下子进程的退出信息,怎么读取,不是目前所关心的话题,如果父进程不读取,这个子进程的代码和数据被释放,但是pcb不能被释放,其中包含了退出时进程的状态信息。
程序前五秒父进程和子进程一起运行,五秒过后,子进程退出, 就应该看见子进程变成僵尸
defunt就是无用的,失效的,死亡的意思
进程退出,pcb不能释放,保存退出时的状态信息,等待父进程读取,进程的代码和数据合约先释放:
接下来我们让父进程读一下,然后看一下僵尸进程的消失:
前五秒父子进程同时运行,然后中间五秒子进程变成僵尸,十秒后,父进程回收子进程
为什么维持僵尸状态:
未来我们创建进程是希望这个进程给用户完成某种工作的,肯定要求一个结果,将子进程创建出来,那么子进程必须要有结果数据,这个数据是保存在pcb中,子进程结束,代码和数据可以释放,但是pcb还不能被释放
什么是z状态:
进程已经退出,但是当前进程的状态需要自己维持住,供上层读取,那么就必须处于z状态
只有当通过我们的方式读取pcb里面的退出信息了,才能变成x死亡状态
如果不读取,僵尸状态会一直存在,是会有影响的,一直存在疑问着task_struct会一直存在,维护这个对象都是要占据内存的。这块内存得不到释放又不能使用叫做内存泄漏。所以父进程必须等待,所有的进程都必须先到z,再到x。bash父进程会自动读取,其余父进程不确定,可能自己读取,可能像我们上面一样要手动读取等待。bash命令行会主动回收子进程状态,所以看不到。
那么这张图在我们linux下是适用的
4. 孤儿进程
修改代码如下:
父进程先退出,父进程很难查到自己的僵尸状态,bash会回收,那么父进程先退出了,为了子进程变僵尸,誰来回收呢?
父进程先挂掉,子进程会被1号进程领养,我们可以理解为操作系统,或者说父进程先结束,子进程必须被操作系统领养,因为无主进程,一直僵尸浪费资源,我们将这种进程称之为孤儿进程。 还会将自己变成后台进程。
孤儿进程也是可以有子进程的。
5.结语
今天的主要内容是进程的排队,进程状态在操作系统学科层次的理解和linux操作系统下的观察。扩展了很多内容,补充了僵尸进程和孤儿进程,后续进程的优先级和进程相关内容,欢迎大家关注。创作不易,如果大家觉得有所收获,欢迎关注,一起交流互进。