进程控制
进程创建
Linux创建进程通过fork()函数调用!在之前的学习已经提到了这个知识点!
父进程在主函数中调用fork函数,此时os就在创建了一个子进程,此时父子进程就开始并发调度,分流运行!
fork函数返回: 子进程返回0,父进程返回子进程PID,若子进程创建失败返回-1!
1)我们知道进程 = 内核数据结构+数据,所以创建子进程,就要为子进程分配对应的数据结构和数据
2)将父进程的PCB、mm_struct、页表拷贝给子进程,个别数据进行修改
3)将子进程添加到系统进程列表中! 并且可以将子进程添加到进程调度队列!
我们知道父子进程数据默认共享,直到进程某一方开始对某个数据进程修改时,OS会使用写时拷贝,来保证进程的独立性!
我们知道进程具有独立性,那一个进程的代码是另一款OS呢?那么就可以在LinuxOS上运行其他的OS,这并不会有影响,因为进程就有独立性,这就是内核级虚拟机的原理
写时拷贝
子进程因为拷贝了父进程的内核数据结果,所以父子进程在默认情况下,是冰箱数据和代码的!父子进程的虚拟地址空间,和页表都相同,所以都指向同一块物理地址,所以共享!
我们知道页表也有权限字段,控制用户使用虚拟地址访问物理内存!
我们知道代码段数据不能呗修改!所以在页表权限部分设置代码区的映射访问权限设为只读状态!所以当用户尝试通过虚拟地址修改代码区数据的时候,OS不运行通过地址映射!
那么为什么图中数据区的映射权限也为只读?
当只有父进程一个时候,数据区的映射权限是为读写的!但是当创建子进程,有了多个子进程的时候会,父子进程的数据区映射访问权限都设为只读!
当父子进程其中一个进程尝试修改数据区数据,OS会发现访问的数据是合法的,虚拟地址和物理地址都有,OS又通过mm_struct知道访问的是数据区的数据,但是发现数据段是只读的!OS就会报错,但是这种报错并不是真的出错,而是O检查到用户要讲一个只读的区域写入,而且这个区域是数据区,这个时候OS就会启动写时拷贝(COW)!
所以处于共享状态父子进程,是通过让用户修改只读的数据区,页表向OS报错,从而进行写时拷贝机制
所以查页表报错机制也有多种情况!
1、地址不存在
2、缺页中断,访问的物理内存块不存在要加载
3、访问的物理内存块存在,但是全是是只读的
为什么要写时拷贝
我们知道进程具有独立性!那如果我们创建子进程的时候直接把代码和数据全部拷贝一份给子进程可以吗?理论上是可以的,但是要考虑现实问题,拷贝全部的代码和数据,拷贝的时间需要考虑吧!再说了,整个程序,代码不可能被修改,数据也可能不会全部被修改,数据中也有只读的数据,所以如果全部都拷贝必然会产生内存浪费!所以写时拷贝是OS内最精细的内存管理操作,对不修改的数据不进行拷贝,只对修改的数据进行拷贝
减少创建子进程的成本(时间)
减少内存浪费
fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。(通过创建子进程,让父子进程分流运行代码后面的部分,实现父进程代码的一部分功能)例如:父进程等待客户端请求,生成子进程来处理请求
- 一个进程要执行一个不同的程序,例如子进程从fork返回后,调用exec函数(其实我们平时调用的指令,就是通过bash父进程创建出来的子进程来完成的功能)
fork调用失败的原因
fork创建子进程失败,进程 = 内核数据结构+ 数据
所以要么就是OS创建内核数据结构创建失败,PCB、虚拟地址空间、页表的物理地址申请失败了,这些东西说白了就是数据结构对象,开辟还是需要空间的,要么就是加载数据到内存失败了,这两者失败的原因指向同样的方向——内存空间不够了!
所以OS为了减少内存不足的情况,OS一般会限制进程数量,并且会限制用户调用进程的数量!
- 系统中有太多的进程
- 实际用户的进程数超过了限制