当前位置:   article > 正文

进程的控制(进程终止/进程等待/进程程序替换)_进程执行系统通用fork()、wait(0)、exit(0)时,操作系统分别要进行哪些进程控制操

进程执行系统通用fork()、wait(0)、exit(0)时,操作系统分别要进行哪些进程控制操

fork()函数初识

✳️
fork()有两个返回值,会返回两次,这是因为父子进程都会执行return;
同一个id会有不同的值,是因为地址空间的存在,一个地址能够通过页表映射到不同的物理内存,会产生出同一个变量会不同的值 。

✳️在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

✳️#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

1

✳️进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.分配新的内存块和内核数据结构给子进程:一个是task_struct需要创建出来,然后第二个mm_struct进程的地址空间也要创建出来。

2.将父进程部分数据结构内容拷贝至子进程:以父进程为模版,子进程的相关数据结构设置为和父进程的相关字段保持一样,最典型的是task_struct里面有很多都一样,包括地址空间mm_struct也是一样,地址空间区域划分也是一样,但是拷贝也不是无脑拷贝,一定会有自己特有的一部分内容,最基本的就是pid、ppid、调度的时间片等等会有些不一样。

3.添加子进程到系统进程列表当中:将子进程列入到运行队列当中

4,fork返回,开始调度器调度:上面三个工作都已经做好了,父进程当然继续执行,执行return,而子进程有没有被调度呢?有可能被调度了,所以子进程也有可能会执行return,返回的本质就是通过寄存器向我们的接受变量写入,写入的本质就是要进行修改,所以会发生写时拷贝,进而让同一个变量会出现不同的值这样就完美解释了fork之后会有两个不同的值。

✳️所以fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意fork之后,谁先执行完全由调度器决定。
因为fork之前只有父进程,所以父进程独立执行;
fork之后有两个进程了当然两个执行了分别执行;
❓:那么fork之后,是否只有fork之后的代码是被父子进程共享的??
我们之前有个观点:进程具有独立性。除了我们感受到的数据结构方面是独立的,代码和数据也都是独立的(给了写时拷贝的解释)。
我们说过代码只能读取,因为我们之前写C/C++觉得进程的代码不合适,让代码把自己程序代码改改,从来没有,我们基本改的都是数据。因为是只读的所以不可能被写入,也就不可能发生写时拷贝。确实如我们所说,。fork之后代码是共享的!
❓但我们要明确一下fork之后代码共享指的是什么??(fork前面的代码呢,前面的代码也是父进程的,既然是父进程,那么会不会也被共享呢?)
✳️fork之后代码共享不算准确,明确一下fork之后,父子共享所有的代码! —➡️所以子进程执行的后续代码 != 共享的所有代码,只不过子进程只能从里开始执行。
❓那么为什么子进程只能从fork之后开始呢?
解答:是因为,我们要记住,当我们在执行进程的时候,CPU里面有一批寄存器,寄存器会保存当前执行到什么位置,叫做eip寄存器,也叫做程序计数器。eip叫做,保存当前正在执行指令的下一条指令!(也叫pc指针)也就是说我们的代码被编译之后,会变成我们对应的具有地址的,所以我们怎么知道程序从上往下执行,接下来该循环了,循环怎么实现的,函数跳转又是怎么实现的,本质上就是通过修改eip来执行的,eip在创建子进程的时候,属于当前父进程的上下文,它会被拷贝到我们子进程当中。
所以eip程序计数器会拷贝到子进程,子进程便会从该eip所指向的代码块处开始执行啦!(比如说我们进程有很多行代码,当我们eip指向第一行,CPU就读取第一行,CPU读取指令总是伸手向eip去要,一旦把指令读到CPU后,eip自己就会做++,指向下一行代码,就这样不断指向下一条指令,那么fork指向完毕后会指向下一条命令,接下来就是创建子进程了,创建完成后吧eip拷贝给子进程,所以子进程也只能从这里开始,所以并不能代表子进程只有fork之后的代码 如果你愿意是可以拿到的,如果我们将子进程的eip改成main函数的入口不就行了吗)
✳️fork之后父子进程必须保证独立性,必须保证独立性就必须代码和数据独立,数据以写时拷贝的方式独立,而代码是两个共享的,因为代码是只读的,不需要被修改,如果你想修改,你把它改一下也不是不行,你再来一份也可以,但就是两份一样的代码了,白白浪费资源
请添加图片描述

2

❓今天谈论fork创建子进程那么操作系统给我们做了什么?
✳️进程=内核的数据结构 ➕ 进程的代码和数据;
fork创建子进程:无非就是创建子进程的内核数据结构(task_struct + mm_struct + 页表) + 代码继承父进程,数据以写时拷贝的方式,来进行共享或者独立!

fork的常规用法

✳️第一种用法是,我们创建一个子进程来和父进程做类似的事情,但不是指一样的事情,最典型的就是fork之后,用if/else来进行分流,父子进程执行不同的代码块。
第二种就是创建一个子进程做和我父进程不一样的事情,这个会在讲进程程序替换的时候会讲。

fork调用失败的原因

✳️系统进程过多
每个用户创建的进程是有限制的

写时拷贝

通常代码是共享的,父子不再写入,一旦写入了程序也就报错了,当任何一方进行写入,便以写时拷贝写入,各自产生一个副本。

子进程创建之后,父子进程都有地址空间和页表,只不过子进程的各种映射关系都继承于父进程,所以父子的代码是共享的。后面会讲页表里面会包含读写属性
。也表不单单会维护映射关系,也还有一些读写属性的设置。
请添加图片描述

❓写时拷贝是怎么实现的呢??
其实就是我们创建子进程之后,我们把所有页表的映射关系改成只读的,不管是父进程还是子进程,当我读取的时候,就给你正常读,当你写入的时候,当然也会判断当前内存区域是代码还是数据,比如说我们想写入一部分数据时候,父进程自己没有写他不管,它依然指向自己的数据地址,而子进程尝试写入,则操作系统会讲数据拷贝一份到新的内存空间,然后给子进程的映射关系作修改。
所以都没有写入的时候,父子进程页表下建立虚拟地址和物理内存地址的映射关系指向同一块内存,但一旦你写入的时候,操作系统给你拷贝之后,重新会改写子进程页表的映射关系,让子进程虚拟地址指向新的物理内存空间,然后会把父子页表维护的读的属性给去掉,改成正常的属性。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/108152

推荐阅读
相关标签