进程控制——进程创建

目录

一 进程创建的函数——fork

1 OS分配新的内存块和内核数据结构给子进程

2 将父进程部分(子进程有自己独有的数据,并不一定完全使用父进程的)数据结构中的内容(代码和数据)拷贝(这是一种特殊的拷贝)给子进程。

关于写时拷贝技术:

为什么会有写时拷贝技术:

写时拷贝是如何实现的呢?

3 将子进程添加到系统进程的列表中去

4 fork返回,由调度器开始执行调度。

二 关于fork创建子进程的用法:

三 关于fork调用失败的原因


一 进程创建的函数——fork

  在之前已经对fork函数的使用,返回值是什么,为什么有两个返回值,并且返回值为什么不同等问题做了初步的探讨,因此这里不再赘述了。

  接下来主要分析,fork之后创建子进程,OS都做了什么工作?

1 OS分配新的内存块和内核数据结构给子进程

子进程被创建出来之后也是要被操作系统管理起来的,因此OS也会给他分配对应的内存块(task_struct)和内核数据(mm_struct和页表)结构。这时候父子进程各自有各自的内核数据结构了。

2 将父进程部分(子进程有自己独有的数据,并不一定完全使用父进程的)数据结构中的内容(代码和数据)拷贝(这是一种特殊的拷贝)给子进程。

由于进程具有独立性,对于子进程使用父进程的代码和数据,从代码层面上看,由于代码是只读的,因此父子共享是没有问题的,不会因为其中一个进程的修改而影响到另一个进程。但是对于数据的话,由于她的属性是可读可写的,那么是不可以被直接拷贝给子进程的,但是子进程又没有自己加载数据的一个过程,这样就很难办了。比方说代码中的大量的全局变量和其他数据,如果父子进程双方都会使用到的话,那么一旦被修改,父子进程中的数据就会跟着被修改了。因此引入了一种技术,叫做写时拷贝技术!

关于写时拷贝技术:

为什么会有写时拷贝技术:

如果将父进程的数据全部拷贝给子进程的话,有些数据子进程并不会使用到,但是仍然在物理内存中占据着空间,相当于物理内存中有两份一模一样的数据;再者,可能这些数据并不需要写,只需要读,那么这一部分数据没有必要再分配空间;还有,可能即使给子进程分配了对应的物理空间,可能也不会立马被使用。但是又要保证,父子进程的数据是独立的,分离的。这样的话,就引入了这样的一个技术。

那么什么类型的数据必须得被写时拷贝?

将来父进程或者子进程要对其进行写入的数据。

因此,总结:

一般而言,即便是OS也无法在代码还没编译之前就知道,哪些空间可能会被写入。

即便是提前在物理内存中拷贝了对应子进程的数据,如果不会立马使用,就造成了空间的浪费。

所以OS提出了一种写时拷贝的技术来将父子进程的数据分离。

写时拷贝本质是一种延迟申请,当你用的时候再给你,因此你不用的时候,不会占用资源,内存的使用效率非常高。提高了整机内存的使用率。

因为有写时拷贝技术的存在,所以父子进程得以彻底分开。完成进程独立性的技术保证。

写时拷贝是如何实现的呢?

首先子进程在fork之后被创建出来,这里的创建出来,指的是对应的内核数据结构(task_struct,mm_struct,页表)和数据(代码和数据)都被创建出来。这时候,父子进程的内核数据结构二者是各自都有一份的。但是由于暂时没有写入的需求,因此在物理空间中,父子进程共用一块物理内存,而当子进程真正要写入数据的时候,OS会在对应的物理空间上开辟出一块全新的空间,将父进程的数据拷贝到子进程中去,子进程再对其进行修改,这样子就保证了进程的独立性,实现了父子进程的数据分离。同时这一种延迟申请的策略,也提高了整机的效率和空间的利用率。

3 将子进程添加到系统进程的列表中去

4 fork返回,由调度器开始执行调度。

调度器决定进程如何调度,之后交由CPU执行。

那么,CPU是如何知道我们的程序执行到哪一行代码了的呢?比如说,如果程序中有循环语句等,执行完之后怎么记住这个入口的呢?

程序汇编之后,会有很多行代码,而且每行代码加载到内存中之后,都会产生对应的地址。

CPU运行其实就是取指令,分析指令,执行指令。进程在被CPU调度的时候随时可能被中断(而且可能并没有执行完 ),下次回来还必须从之前的位置继续运行,因此要求CPU必须随时记录下当前进程执行的位置。因此CPU内有对应的寄存器,用来记录当前进程的执行位置,它是EIP(PC指针):程序计数器。EIP在其中扮演了一个类似于司令官的角色,他告诉CPU在哪执行。永远记录的是当前正在执行的代码的下一行代码的地址。(当前指令已经被执行了,不需要记录当前的位置了)比如当前指令的地址+当前指令的长度=下一条指令的地址,存储在PC指针中,这样子CPU就可以找到对应的执行位置了。

分析指令和执行指令的前提是CPU必须要认识指令,所以一般的CPU都有对应指令集。我的代码经过编译之后形成的汇编代码之后的二进制代码,对应的就叫做指令。

寄存器在CPU内只有一份,但是寄存器内的数据是可以有多份的。这个数据也被叫做上下文数据,被存储在task_struct中。当父进程创建子进程的时候,这个数据也是要以写时拷贝的技术交给子进程的。因此虽然父子进程各自调度,各自有自己的EIP,但是子进程依旧认为自己那一份EIP起始值是fork之后的代码。fork之后是所有的代码共享,但是有两个不同的执行流在被运行了。

总结上述过程:

fork创建子进程,相当于系统里多了一个进程,相当于加入了对应的pcb结构体,地址空间,页表 并将代码和数据加载到内存,构建映射关系。等待被调度。一旦CPU调度了这个进程,就可以通过映射关系来按照顺序和顺序语句,来执行进程内的代码完成各种业务。

进程=内核数据结构+进程的代码和数据(一般从磁盘中来,一般是C语言和c++程序,加载之后的结果)

二 关于fork创建子进程的用法:

1 父进程希望子进程复制自己,各自执行同一份代码的不同区域。

2 后续创建子进程,让父子进程分别执行不同的事情。


三 关于fork调用失败的原因

1 系统当中有太多的进程了

进程创建本质上消耗内存资源,一旦创建进程,那么系统中一定存在的大量的数据结构+代码本身的代码和数据,都是需要占用内存的,内存没有那么多了,就无法创建了。

2普通用户也无法创建很多的进程。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值