赞
踩
头文件:#include<stdio.h>
格式:fork()
功能:创建子进程
看下面的一段代码。
#include<stdio.h>
#include<unistd.h>
int main()
{
fork();
printf("Hello fork\n");
sleep(1);
}
经过运行以后我们发现,printf
语句执行了两次,因为父进程通过fork
命令创建了子进程,所以存在了两个执行流,将语句执行了两次。
并且我们还发现创建出来的进程之间还具有父子关系,在前面我们有提到进程之间的父子关系,现在我们开始详谈Linux
中的父子进程之间的关系。
1.在Linux
中,fork
创建的父进程和子进程之间具有继承关系,子进程会默认继承父进程的代码和数据。
代码:子进程会共享父进程的代码,也就是说子进程使用的是父进程加载到内存中的代码。
数据:子进程会也会共享父进程的数据,但是当数据遭到修改的时会发生写时拷贝
。
2.一个进程被创建出来,一定有自己的PCB
,所以子进程的PCB会以父进程的PCB
为模板被创建出来(事实上还会以父进程的进程地址空间,页表为模板构建自己的进程地址空间和页表,关于这个我们会在后面的进程地址空间展开讲解)。
注意:我们在这谈到的父进程和子进程的继承关系,是基于fork创建的子进程才会这样。普通的父进程和子进程之间有继承关系,但是不具备上述的这些。
问题:为何子进程和父进程的代码是共享的?
父子进程的运行代码基本一致,操作系统为了节省空间所以只开了一块空间给两个进程共享。
注:其实无论是代码和数据,其本质上都是内存中的一块块空间。
在计算机中,进程都是互相独立的,如在windows下,微信,qq,英雄联盟都是一个个进程,其中一个进程挂掉不会影响其他进程的运行,这是进程的独立性。
问题又来了,子进程和父进程共享代码和数据,那么代码和数据被修改时,进程的独立性是否会受到影响?
子进程和父进程的代码和数据是共享的,代码是不能被修改的,这意味着代码不会影响进程的独立性,所以我们只需要考虑数据被修改的情况。(事实上代码也是有可能被修改的,被修改的时候也会发生写时拷贝,在后面我们会再详细说明这个情况,现在暂时不用管。)
Linux
大佬在设计的时候考虑到了这一情况,
所以在修改数据的时候会发生写时拷贝
:
再次理解fork
函数
fork
函数的返回类型为pid_t
,并且fork
函数的返回值有两个。
情况一:当进程创建失败时:给父进程返回-1
。
情况二:当进程创建成功时:给父进程返回子进程的pid
,给子进程返回0
。
情况二其实很好理解,父进程和子进程具有父子关系,给父进程返回子进程的pid
,有助于父进程对子进程的管理,但是为什么返回值是不同的,两者不是共享同一份数据吗?因为返回值其实也是数据,所以在执行return
的时候发生了写时拷贝。
对于fork的返回值我们需要有更深的理解,为何fork函数会有两个返回值?
fork
函数的代码大致可以看作是由以下组成:
pid_t fork()
{
//fork的主体
return
}
编译器在执行代码的时候,当执行到return
的时候,fork
函数的核心功能已经完成,这时子进程已经被创建出来了。这时就存在两个执行流,父子进程都会开始执行接下来的代码,所以fork
函数具有两个返回值。
注:fork之后,父子进程的运行顺序不确定,CPU会进行调度控制,并且进程的返回值本身没有太多意义,但是通过返回值的不同,我们可以使用if else 语句进行分流操作。
在Linux
中,进程也是具有分类的,一般通过不同的状态来对进程进行分类,方便操作系统快速判断进程,完成特定的功能,进程的状态信息一般存储在PCB
中,状态其实也是操作系统对进程的一种评估。
(
1
)
(1)
(1)R状态
R
状态是一种运行状态,处于R
状态的进程随时能够被CPU
进行调度,由R
状态组成的队列又叫做运行队列(run_queue
)。
处于R状态的进程一定正在占用CPU吗?
不一定,处于R
状态的进程表示其随时可以被CPU
调度,但是不代表其正在被调度,其可能在运行队列等待CPU
的调度。
注:在这些队列里排队的都是进程的PCB
。
( 2 ) (2) (2)S状态和D状态
S
状态(sleeping
):浅睡眠状态(可以通过ctrl c
和kill
等方式结束进程)。
D
状态(disk sleep
):深睡眠状态(进程不可被中断,即使是操作系统都不能干掉该进程)。
这两个状态又可以称作是等待状态,一个进程因为某种原因不满足运行条件的时候,就需要进入等待状态,这里我们需要知道,进程不是只需要等待CPU
的调度,还需要等待其他硬件,如内存,io设备等待,由S/D
状态组成的进程队列又叫做等待队列(wait_queue
)。
我们使用网易云音乐的时候,网络断了,这时数据得不到更新,这时系统提示是否终止程序,这时进程就处于等待状态。
注:在队列里排队的是进程的PCB
。
把处于等待队列的task_truct放入运行队列称作唤醒。
把处于运行队列的task_struct加入等待队列称作挂起等待(阻塞)。
D
状态是进程里的大爷,操作系统都杀不掉,因此一个系统存在大量D
状态的进程就会占用大量的内存空间。
(
3
)
(3)
(3)T:暂停状态
T
状态也是一种暂停状态,这里我们用类比的方式进行大致的说明:
S
状态和T
状态的区别:
S
状态虽然什么事也没干,但是一些核心数据可能会被更新,如sleeping
时,会记录slepping
的时间,到时间就进行唤醒
T
状态则是彻底的暂停,不会有数据的更新。
(
3
)
(3)
(3)t状态
t
状态又称作追踪状态,也是一种暂停。
如调试的状态,可以查看一些信息.。
(
4
)
(4)
(4)X状态
X
状态又叫做死亡状态。
当一个进程进入死亡状态以后其资源会被回收:
1.进程维护进的相关数据结构,如PCB
。
2. 进程加载到内存上的代码和数据。
注:其实还有页表
,mm_struct
…在后面我们会提到,一个程序加载成为进程,进程退出时相应的资源就需要被回收。
(
5
)
(5)
(5)Z状态
Z
状态(zombie
)叫做僵尸状态:如果一个进程的退出信息没有人检测(一般是父进程来完成这个任务),所以一般情况下,子进程退出的时候,父进程在休眠,这时子进程存储在PCB中的退出信息父进程无法接受,这个子进程就会进程Z
状态,父进程休眠结束后,会进行回收,这时子进程才能正常退出。
注:关于父进程是如何检测的在后面的博客中我们会详谈。
以生活中为例:警察让法医进行鉴定,在鉴定期间,人一直躺着的状态就是僵尸状态,警察需要判断这个人死亡的原因
为什么要有僵尸状态?
辨别退出死亡原因,需要分析进程为什么退出/死亡以及进程任务的完成状态…
进程进入僵尸状态都会做哪些事?
一个进程退出的时候不是立即释放所以资源,而是先让进程进入僵尸状态,处于僵尸状态的进程会将退出信息写入PCB
中,PCB
中有字段保存进程的退出码和退出信号,供操作系统或者父进程进行读取,关于这个我们后面会再说明。
注意:子进程的退出信息一般由父进程进行接收,分析。如果父进程无法做到,最终也会由1
号init
进程进行接收,分析。进程的资源一般是由操作系统最终来进行释放,不由父进程进行释放。
正常的死亡流程:
僵尸状态 -> 死亡状态
僵尸进程的危害
如果一个进程的资源不被回收那么他将一直处于僵尸状态,僵尸进程会以终止状态保持在进程表中,等待父进程读取退出状态代码,并且状态信息是存储在PCB
中的,处于僵尸状态的进程需要持续的维护自己的PCB
,PCB
是需要占用内存空间的,因此就会造成内存资源的浪费。
注:进程进入僵尸状态以后,可以看作是一种无敌的状态,这时通过kill
命令都不能杀掉该进程,需要父进程或者系统进行清理回收工作。
(
1
)
(1)
(1)R状态的验证:
验证方式
:让进程不断的接收CPU
的调度
注:+
号的意思:表示进程在前台运行
#include<iostream>
using namespace std;
#include<unistd.h>
#include<cstdlib>
int main()
{
while(true);
}
( 2 ) (2) (2)S状态的验证:
验证方式
:cout
的作用是将数据打印到io
设备上,io
设备很慢,CPU
很快,所以大部分的时间该进程都在等待io
设备的刷新,所以该进程频繁处于S+
和R+
状态的切换。
#include<iostream>
using namespace std;
#include<unistd.h>
#include<cstdlib>
int main()
{
while(true)
cout<<"HELLO Linux"<<endl;
}
(
3
)
(3)
(3)T状态的验证
在验证之前,介绍一个命令
命令 kill -l
功能:查看Linux中的控制信号
验证方式
:通过进程信号让进程进入T
状态。
通过sigstop
信号让进程变成T
状态。
通过sigcont
结束进程的暂停。
注:
格式:./可执行程序名 &
功能:让程序在后台运行
在前台运行:不可以输入命令,可以通过ctrl +c
命令杀掉
在后台运行:可以输入命令,但是ctrl +c
杀不掉,得使用kill
(
3
)
(3)
(3)Z状态的验证
前面我们介绍的时候说了,如果一个进程退出的时候没有人进行检测和回收这个进程就会成为僵尸进程,保存在进程表中。
验证方式
:50s
之内把子进程干掉,父进程在休眠,这时子进程的退出信息父进程无法接收和分析,该子进程就变成僵尸进程。
int main() { pid_t id =fork(); while(true) { if(id==0) { sleep( 2); cout<<"i am son"<<endl; } else { sleep(50); } } }
问题:如果父进程退出,子进程还在运行,那子进程怎么办呢?
通过一段代码我们来看下会发生什么?
让子进程不断运行,第10s
的时候让父进程退出。
int main() { pid_t id = fork(); if (id == 0) { while (true) { cout << "I am a child proc" << endl; sleep(2); } } else { cout << "I am a father proc" << endl; sleep(10); exit(0); } }
通过运行结果发现,父进程退出以后,子进程被1号init
进程(操作系统)领养,这时子进程又叫做孤儿进程。
进程的正常的死亡流程是:僵尸状态—>死亡状态
子进程准备退出时,会将进程的退出信心保存在PCB中,如果父进程无法接收(一般是父进程处于休眠或者运行),则子进程会进入僵尸状态,待父进程休眠结束
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。