宝贝做网站,株洲网络科技有限公司,綦江建设银行网站,WordPress实现评论表情前言
上一篇博客当中#xff0c;对 冯诺依曼体系结构 和 操作系统 进行了简要概述#xff0c;本篇博客将会从上一篇博客的基础之上进行展开#xff0c;如果你有些不了解的话#xff0c;建议先看上一篇博客再看本篇博客#xff1a;
冯诺依曼体结构 - 为什么要有操作系统-…前言
上一篇博客当中对 冯诺依曼体系结构 和 操作系统 进行了简要概述本篇博客将会从上一篇博客的基础之上进行展开如果你有些不了解的话建议先看上一篇博客再看本篇博客
冯诺依曼体结构 - 为什么要有操作系统-CSDN博客
进程概念 就算你不知道 操作系统一大概率听说过进程其实进程就是 已经加载到内存当中程序正在运行的程序 进程是一定要被加载到内存的 这些程序就是又程序员编写的 一个一个软件。其实 一个进程本质上也可以称之为是一个 任务这样获取你会有更好的理解。
在windows 当中按下 ctrl shift esc 就可以的打开任务管理器或者邮件任务栏也可以打开当我们打开 任务管理器之后就可以的看到当前你的计算机正在执行哪一些进程 在 linux 当中可以使用 以下命令来查看当前 那些进程正在被执行
ps axj其中的 PID 就是这个进程专属的 进程编号。
PPID parent process ID就是 当前进程的 父进程 PID。
像上述的 ps ajx | head -1 ps ajx | grep myprocess 这个指令当中使用了 这个符号这个符号表示要左右两边的指令都运行成功才能输出结果同样的在同一位置我们可以使用 ; 分号来代替他这个 ; 分号的意思就是 左右两边的指令都要执行。
我们发现上述多过滤出一个 进程 这个其实是 grep 自己的进程以为 grep 要过滤就要先把自己给 调入内存自己运行起来然后才能查找。因为 上述命令当中 grep myprocess 本身就到了 myprocess 这个关键词所以就把自己给查找出来了。
其中还有一个 COMMAND 这个属性代表的是这个进程当前是 使用了哪一个命令调用的进程 或者使用 Top 命令来查看当前正在运行的进程信息同时你还可以实时的看到当前运行所有进程有多少个 进程处于什么 进程状态 进程的理解 首先进程再被加载到 内存之前是一个程序那么一个程序就又代码文件和一些附带文件所存储既然是文件那么一个程序在被加载之前就是在磁盘上存储的
也就是说进程再被加载到 内存之前实在磁盘上进行存储的所以对于 冯诺依曼体系结构当中的 输入设备对于进程来说就是磁盘了。 此时在 磁盘当中的 程序要想被cpu执行就要先加载到 内存当中而 程序文件本质上也就是存储一些二进制的文件由 代码 和 数据构成的程序二进制文件。都是二进制的数据只是有些二进制数据 表示代码有些表示数据。
那么 是代码的二进制数据就交给 控制器去执行是 数据的二进制数据就交给 运算器去执行。
归根结底还是 把 磁盘的当中的程序的二进制数据 读取到了内存当中。
那么问题来了虽然cpu 当中一次只能执行一个任务但是一个操作系统不可能值运行一个进程可以同时运行多个进程。比如我既可以使用 QQ音乐听歌又可以使用 word 写文档还可以打开浏览器 看 B站。
但是在上述说的不同的 进程当中我可能先写 word 文档在看 B站当B站的当前视频在写完 word 之前就播放完了此时我想就不看 B站视频了那么 浏览器就被我关掉结束了但是此时我还是再写 word 文档。
也就是说各个进程之间是什么时候开始的什么时候执行结束的我们不能完全去控制他们在同一时间片当中有各自进程的执行状态。那么在上述情况下操作系统必须管理好各个进程如何让操作系统合理的运行就是需要解决的问题。 如何管理进程 在上一篇博客对操作系统的描述当中就说到了要想让计算机帮我们处理数据就得前把数据描述起来用 struct 或者 class 把各个对象的属性包装起来。
那么接下来的工作就是管理好由各个进程 包装成的一个一个对象那么无非就是 把各个进程增删查改就得用 某种数据结构来对这些个对象进行某种逻辑上的链接。 任何一个进程在加载到内存时在由 程序形成 真正 的进程之前要对 这个程序当中 属性 和 管理这个进程需要的 属性 包装到一个 结构体或者类因为操作系统是由C实现的所以这里统称为结构体当中用于描述这个进程。我们把这个结构体对象称之为 -- PCBprocess ctrl block- 进程控制块 其实计算机来辨别新事物 的方式 和 人是一样的任何在辨别新事物的时候其实很难的看到这个事物的本身我们认识到 这个事物 知道这个动物叫做 老虎是它表现出来的特征让我们认为他是一个老虎人是通过 属性 来认识事物的。计算机也是一样。 当某个事物表现的属性够多时这些属性的集合对象就是目标对象。 在描述进程也是一样的当描述一个进程的属性足够多的时候那么就可以认为这是一个进程可以理解为 进程 就是属性的集合。这本质上就是一种 面相对象。 在用C实现的操作系统当中一个属性的集合 其实就是一个 struct结构体。 那么进程之前的属性千奇百怪操作系统可以创建很多个 结构体操作系统如何分辨哪一个是哪一个进程的结构体呢
其实很简单在学校当中假设是新生老师不知道名字长相等各个属性的情况下可以如何分辨这些学生呢
就是学号新生的名字可能是一个生僻字老师不会读各个新生可能有自己的爱好和特长老师一时半会也分辨不出来。但是数字老师总得认识吧那么他只需要从 学校教务处拿到一个新生名单就可以分按照学号来分辨新生了。
同样计算机可能不会认识哪一个变量代表的是什么意思但是如果给每一个进程都编上属于这个进程唯一的编号那么计算机总得是可以分辨数字的所以就可以分辨出进程。 除了进程编号之外还会有 优先级进程的状态等等 共同属性多少可以帮助 操作系统识别进程的。 总结
当一个程序被加载到内存当中时操作系统首先要干的事情就是根据这个 程序的PCB类型为这个进程开辟一个 PCB对象对这个对象当中的各个属性进行初始化。
在上述的过程当中操作系统可以选择先不加载 程序当中的 数据 和 代码的二进制数据到内存当中可以先为 这个进程构造一个 PCB 对象。
但是程序的当中的二进制数据总是要加载到 内存当中的那一个PCB 当中只是关于这个对象的属性的存储但是 进程的 二进制数据还是要开空间来存储就好比是你养了一只狗你用笔记录了 这只狗的 属性特征这个的存储大小是可能是一张纸或者是一个笔记本但是你要想在家当中养活这只狗那么得为这个狗买狗窝给他空间作家。 什么是进程理解 管理进程的过程
所以一个 内核PCB数据结构对象 不叫进程一个 code data 文件二进制数据代码和数据 也不叫进程这两个 加起来 才叫一个进程。
操作系统会把 一个 PCB 和 一个 code data文件二进制数据 构建成一个 结构体对象这个结构体对象是由操作系统自己生成的。 所以操作系统要管理这个进程根本就不看 这个进程的 代码和数据只看 PCB对象当中的属性就行了。 我可以使用数据结构把 各个进程当中的 PCB 对戏那个按照这个数据结构的链接方式进行链接此时我们要想对这个进程进行管理就只需要对这个 存储 各个 PCB 对象数据结构的增删查改进行 操作即可。
比如在我们 PCB对象当中增加一个 PCB* next 指针指向下一个 PCB对象那么现在各个PCB 就是一个 单链表了此时在操作系统当中对进程进行管理变成了对 这个 PCB对象的单链表进行增删查改的操作了。 所以我们可能会看到某一个进程正在排队等待运行不是这个进程的 二进制文件数据 在排序而是这个 进程的 PCB对象正在排队。
就像在公司当中投简历排队一样面试官是按字符串查找它需要的什么技能的人然后去查找简历当中符合要求的人此时如果你等不及了打电话问公司人力资源部咨询人员他会告诉你你正在排队或者你已经被录取或者你被淘汰。而不是 公司的 leader 一个一个人打电话去问这就太挫了。
如果你此时正在排队是你的 投进去的简历在公司当中的队列当中排队而不是你这个人在公司当中排队。 总结一个进程包括 属性的和数据数据是这个进程运行自己的算法逻辑和一个需要的数据属性是 操作系统管理这个 进程。什么时候等待什么时候运行什么时候结束死亡等等操作所需的 PCB对象当中的属性。
操作系统管理 进程是他通过属性进行管理而不是通过进程当中的 数据。
Linux 是怎么做到 管理进程的
各个操作系统之间虽然都是按照 先描述在组织的方式来实现的但是对于操作系统的实现细节还是很大差异的。
比如各个操作系统之间的 关于 PCB 的数据结构的实现就不同。
描述 Linux进程的 - PCB
进程信息 被放在一个叫做 进程控制块 的 数据结构 中可以理解为 进程属性的集合 。
课本上称之为PCBprocess control blockLinux操作系统下的PCB是: task_struct
这个 task_struct 是一个大型的结构体这里面包含了Linux 内核当中 描述进程 所以需要用到的属性。 首先应该理解的是task_struct 是 PCB 的一种属于 PCB。在windows macOS 当中都有自己各有的 PCB。
在Linux中描述进程的结构体叫做 task_struct。task_struct是Linux内核的一种数据结构它会被装载到RAM(内存)里并且包含着进程的信息
而在Linux 当中 链接 各个 PCB对象注意只是PCB对象而不是 二进制数据 和 PCB对象最基本组织进程 tash_struct 的方式 一般是使用 双向链表来 链接的。
而且在对于一个 进程 PCB 对象不仅仅会放在的一个 双链表当中还可能放在一个 队列当中。其实想做到这个并不难在 C语言当中只需要多创建一个 数据结构的 PCB* 指针就行。
比如有等待队列运行队列等等数据结构我们想把某一个进程 当前状态进行修改就只需要把这个 进程 的PCB 对象放到 某一个 在组织的数据结构 当中就行至于操作系统怎么运行是按照这个数据结构当中的算法来执行的。
数据结构背后是配套的算法而算法背后又是具体的应用场景。 task_struct 的内容分类 标示符: 描述本进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。比如当前进程执行的状态怎样的是再等待运行代码还是正在运行代码代码运行后它的状态码是什么进程是新建的正在运行的正在休眠的死亡的等等状态优先级: 相对于其他进程的优先级。操作系统当中是有很多个进程在同时运行的那么进程和进程之间就存在这竞争关系程序计数器: 程序中即将被执行的下一条指令的地址。(程序一般是 在栈帧当中顺序执行的但是程序一般情况下不是从上往下顺序跑完的可能还调用函数循环等等往返着调用语句这时我们如何在在调用完函数知道下一个应该执行完哪一个语句就需要 PC指针或者程序计数器来记录跳转执行指令下一个语句的指针。这个程序计数器是 cpu 的一个寄存器这个寄存器就用来存储下一个语句的地址)内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。比如某某个进程累计在cpu 当中运行了多少时间其他信息
在 Linux 当中有一个 目录/procprocess 的简写当中就放了 现在执行的 动态运行的 所以进程 信息: 我们发现此时由很多蓝色的数字目录正在运行 而且这些目录是以数字命名的这个数字其实就是 某一个进程的 PID。所以我们就理解了如果某一个进程开始运行了那么 操作系统就会在 /proc 这个目录当中以这个进程的 PID 创建一个 目录这个目录当中保存了这个进程的大部分属性。
当这个进程结束执行结束之后在 /proc 目录当中创建的 以 PID 为名字的文件夹就会被自动销毁。
我们现在可以查看某一个进程当中的 各个属性的文件 其中有一个 exe 文件夹这个文件当中指向的就是 这个可执行文件的 绝对目录地址。
还有一个 cwd当前进程的工作目录。比如我们使用 C当中的 fopen函数自动帮我们创建一个文件的时候是在当前目录下创建的而这个 cwd 所指的目录就是这里所指的当前目录。
比如现在使用 touch 命令创建文件的时候意思就是在当前目录下创建文件你说这不是废话吗但是touch指令是写的一个软件实现的那么touch 指令要运行就要在提取到内存当中进行运行这时就需要 cwd了。所以cwd的意思就是当前进程是在哪一个目录下运行的那么 cwd 就指向哪一个目录。 我们可以直接使用 kill -9 进程编号 命令 来直接干掉一个进程这个命令的意思就是给 这个进程发送 9 号信号
kill -9 xxxxx 有时进程可能会 奔溃卡死等等情况我们可以使用 ctrl c 来结束但是有时候 ctrl C 都不能结束就可以使用 上述指令来强制结束一个进程。 在上篇博客对 为什么要有操作系统当中我们而已提到了操作系统在链接 各个 进程的过程中本质是对 各个进程的 PCB对象进行 链接而链接方式可能是各种的数据结构算法比如在Linux 当中就是以 双链表的方式来连接各个 PCB 对象的。
那么 像我们在开始使用的 类似 ps axu 指令查看 当前运行的各个进程的指令。本质上就是用 C/C 写的 遍历在操作系统当中 存储链接各个 PCB 的数据结构。 所以如果现在用户想要访问到当前某个进程的 PID 进程编号的话不期望直接使用 C 中 访问结构体的方式来访问到 某一个结构体对象当中的 成员属性。而是像C 当中访问类的私有成员一样给出一个接口来访问。
所以我们在系统上想访问到 某一个 进程的信息就要使用 类似 ps 这样的指令也就是要用要用 系统调用接口 的方式来调用。 我们可以用 一个简单的 命令来实时监控某一个进程就是用命令当中的一个 一直执行循环来执行
# 下述 grep 当中的 -v 选项是反向选择
while :; do ps axj | head -1 ps axj | grep text | grep -v grep; echo -------------------------------- sleep 3; done 此时他就会实时监控 text 这个程序是否成为一个进程像上述因为text可执行程序没有执行所以上述什么都没有检测到当我们运行了 text 之后就可以检测到了我们在 text 可执行程序当中使用 getpid这个函数来获取到 本程序生成的 进程的 PID这个函数的返回值是 pid_t 是有符号整形 当 text 进程运行时就会一直打印 本进程的 PID 此时我们采用上述的检测 进程的 命令再次执行 先执行的 监视命令。然后在执行 text 进程发现原本没有的 进程信息就出现了。 而且我们发现同一个 程序每一次运行操作系统自动生成的 PID 可能是不一样的但是这是相当正常的。
我们需要注意的是PID 只在当前进程 允许的过程当中是有效的如果是这个进程被重新执行了或者是已经结束执行了这个原本这个进程的 PID 就不在适用了。 如上所示两次执行的text程序生成的 PID 都是不一样的 。 同样的你还可以使用 getppid这个函数来获取到 当前程序变成进程 系统生成的 PPID 执行结果 但是发现不管怎么运行text这个程序这个 由 text 生成的进程 的 PPID 都是不变的 此时我们就查看一下 此时这个 28785 到底是什么
ps axj | head -1 ps axj | grep 28785 | grep -v grep打印 我们发现 只有一个 PID 为 28785 的进程。
相信你已经猜到了这个进程就是 bash -- 命令行解释器这个解释器就可以 解释我们输入的命令从而指令对应命令的软件。
我们在 Linux 当中所有执行的指令都是 由经过命令行解释器的所有的 命令都是 bash 的子进程。所以当其中的某一个子进程奔溃了或者是报错 了执行错误都不会影响到 bash 这个进程。只会影响到 子进程。
fork 手动创建进程 我们之前都是使用类似于 ./ 的方式直接调用某一个 可执行程序这样的话操作系统就会自动帮我们创建一个 管理这个进程的 PCB文件然后再在 /proc 这个目录之下创建一个 和这个 进程的编号PID相同的 文件夹目录 这个目录当中就存放了 关于这个 进程的属性。
但是上述都是操作系统 自己帮助我们创建的如果想要手动创建一个进程的话可以使用 fork。
这个是一个函数我们先使用 man forc 使用手册来查看关于 fork 的介绍 fork 是一个函数用于 创建一个子进程。
此时我们在 Linux 当中编写一个 小程序来验证 在这个进程当中 end:~! 在最后只打印了一遍但是输出却输出了两遍 在前面两个打印的 begin: this is a process! 和 第一个end:~! 是原本第一个进程执行的结果其实在 fork函数之后后面的代码语句已经被新生成了一个进程所以当我们运行 text 这个可执行文件的时候相当于是运行了 两个进程后一个进程就是 printf(end:~!\n); 这个语句 forc函数的作用就是在 调用的进程为模版新创建一个 进程。这个新进程就是调用 fork函数的这个进程 的 子进程。 fork函数的返回值是 pid_t 类型
如果 fork函数创建进程成功了那么将会给父进程返回一个当前新产生的子进程的 PID同时返回 0 给子进程。如果 fork函数创建进程失败了返回 -1 给父进程此时没有 子进程被创建给出error 错误码。
相信你已经发现了当 fork函数创建进程成功之后我们看上去返回了两个返回值一个给 父进程一个给子进程。
要探究上面的问题我们下来看这个例子我们直接在 fork函数之后写一个 if 判断一个 fork函数的返回值。那么此时如果fork返回的是 0 说明当前执行的是父进程如果返回的是一个 0 的数那么说明当前进行的是 子进程如果返回的是 0 的数返回的是一个 error 如上所示为了方便观察在三种情况之下都 sleep一下。
输出 首先在进程卡开始执行之后先执行的是子进程然后执行的是父进程而且子进程 和 父进程 之间是交替执行的。但是我们写的 不是 子进程 和 父进程 分开的两个死循环吗怎么会 两个交替执行呢怎么会出现 两个死循环同时再跑呢 而且上述的输出结果是不是就证明了我们用 Mypid 这个变量来接收的 fork函数的返回值等于0 和 大于0 两个条件同时成立了。 这就是因为我们使用了 fork函数在 fork函数之后变成了两个进程一个是原本的进程我们此时称之为 父进程另一个是 fork函数新 生成的 子进程。这两个是实实在在的父子关系 此时你就要注意区分了
使用 ./ 方式是在指令层面 帮我们创建进程。使用 fork函数是在 代码层面 帮我们创建进程。 我们现在来梳理一下 关于此时 text 这个程序执行的顺序
刚开始执行 text 进程的时候操作系统就给这个 text 分配了一个 PID 为 18375此时这个进程也就是 父进程。
在 执行 fork函数之后就有 fork函数一分为二形成两个分支一个是之前的进程 --- 父进程另一个就是 --- 子进程。
之前我们单独 执行 text 进程就是一个单进程的当时执行的但是因为这个 text 当中有一个 fork函数这个函数帮助我们创建一个子进程此时就是多进程的执行方式了。
为什么fork函数要给 子进程返回 0 给父进程返回 子进程 的 PID
我们先来看为什么 给 子进程 和 父进程 的返回值要不同 首先返回不同的返回值是为了区分让不同的执行流执行不同的代码块。其次fork之后的代码父子共享。像之前给的死循环那个例子其实 fork之后的代码父进程 和 子进程 都是一样的只不过我们在上述以 fork函数返回值进行 区分而已。
上述只是解决了 为什么返回值要不同但是为什么 给子进程返回的是 0 而 给父进程 返回的是 子进程的 PID 在现实当中一个父亲可能以后多个孩子但是一个孩子只能有一个亲生父亲父进程 可能要对 子进程做一些控制。所以父进程要拿到 子进程的 PID用来管理子进程标识子进程的唯一性而子进程不一样他只有一个父亲他只需要找到 PPID 就可以找到父进程。
fork函数干了什么事 在 fork函数 创建新 子进程之前在系统当中就已经 创建了原本的进程 --- 父进程了之前说过进程的本质实际上就是 PCB内核数据结构 代码和数据 。
在调用 fork函数之后系统当中创建一个子进程创建一个子进程本质上不就是 在系统当中多一个进程吗
所以此时在 父进程的PCB 创建的基础之上由多了一个 子进程 PCB在子进程当中的各个属性其实是按照父进程 为 模版来创建的
但是此时有一个问题父进程有 自己的 PCB 和 代码数据但是子进程是系统创建的在创建之前没有 像 父进程一样从 磁盘当中获取到 代码和数据所以子进程只能无奈的 和父进程 一样调用同样的代码。 所以此处需要注意的是fork之后父子进程访问的代码是共享的。在 C/C 当中代码被加载到内存之后代码是不能被修改的能改的只能是代码当中的数据
为什么要创建子进程呢如果 子进程和 父进程 执行的是一样的内容那么直接让 父进程去干不就可以了为什么要 子进程呢所以我们就是要让 父 和子进程 执行不同的事情像办法让 子进程 和 父进程 执行不同的代码块。这样让父子做不同的事情让 父子协同起来。
所以 fork函数才有了不同的返回值。 fork函数是如何做到返回两个返回值的
我们还是那上述的例子来所说明。
我们需要注意的是fork也是一个函数那么既然是一个函数那么这个 fork就具有自己的函数体也就是 fork函数自己的代码块。
此时就有个一个问题了当一个函数执行到 return 语句的时候这个函数的主要功能有没有实现呢 在 fork函数当中是已经做完了的。
在fork函数的当中会做很多事情但是根本上都是在创建一个子进程这里面 最主要的就是 创建和拷贝数据 PCB给PCB 的属性值初始化。当 父 子 都有了各自的 PCB 之后CPU 就可以调度这两个进程了。
但是我们刚刚说了执行到 return语句之后fork函数的主要功能已经实现完了而且 子进程 和 父进程是共用一个代码但是return 语句是代码吗答案肯定是的 。所以也就意味着 return语句也是父子共享的。
所以当子进程调度 fork函数时候就return返回一个值父进程调度 fork函数时候也返回一个值。所以这个 fork函数就被返回了两次。 因为当你fork 函数准备return之前关于子进程的调度早就执行完了此时子进程也允许被CPU调度了。所以再往后这个 return语句就是被两个 进程所调用因为 return 语句是代码被父子进程所共有当父子进程运行时就会调用两次return语句。从而实现返回不同的值。 上述的返回值 Mypid 是如何接受两个返回值的
在上述描述过父进程因为 在 使用 ./ 调用父进程的时候。就会带上父进程的 数据和代码所以父进程是天生就有数据和代码的但是子进程却不是子进程是在父进程当中在创建的才创建之时子进程是没有自己的 数据和代码 的子进程和父进程共用一个代码。
那么问题来了子进程的数据从何而来Mypid 变量用于接收 fork函数的返回值但是这个 Mypid 变量只有一个啊。子进程运行也是需要数据和代码的。 所以如果你有上述的疑问那么你应该首先明确一点
在任何平台进程在运行之时是具有独立性的 什么是独立性呢
比如在 windows 当中创建了 QQ 这个进程 和 微信这个进程如果其中 QQ 这个进程崩溃了他不会影响到 微信这个进程如果 微信崩溃了不会影响到 QQ 这个进程这两者之间是没有耦合关系的也就是我们说的 独立性。
独立的本质是一种割裂关系各个独立的进程各自就能做自己的事情自己就能运行除非他某项功能需要和其他进程来联系。 所以上述 父进程 和 子进程都是 一个独立的进程那么他们就可以自主运行不需要 相互之间的依靠。既然是自主运行因为数据可能被修改那么两者之间肯定是不能用 相同的数据的肯定是独立的数据。 读到这里需要区分的是代码是共享的因为代码是不能被进程所修改的而 数据不是共享的是独立的因为数据可能会被修改肯定不能让父子进程访问同一份数据。 子进程要想独立一份数据就需要拷贝一份数据拷贝的数据是和 父进程共有的数据拷贝一份给自己用。除此之外子进程还有各自 专属数据是父进程没有的那么在这个份数据当中也应该创建出来。这个为子进程拷贝和创建数据工作是由操作系统完成的。 此时子进程和父进程就拥有了各自独立的数据不再冲突。
而在访问不同数据就是通过 父子进程各自的PCB来完成调用不同的PCB就访问各自的进程不管这个代码是不是共享的但是只要数据不是共享的就行。 那么上述也提到了 子进程会拷贝 父进程当中数据但是有一种情况拷贝下来的父进程数据对于子进程当中大部分数据都是没用的。或者说是拷贝下来的数据有些是子进程使用的。
所以操作系统在此处进行了优化
首先在创建子进程之时不以上上来就拷贝一份父进程的数据而是先让子进程和父进程共享父进程的数据。当子进程像访问的时候如果子进程要对这个数据进行修改那么就在内存当中重新开辟一块空间用于存储子进程要修改的变量的值。
这时操作系统就告诉子进程你要修改变量数据不要在父进程的数据区当中直接修改这个变量的数据而是在我给你新开辟的一块空间当中修改这个变量的数据。
如此往后只要是子进程想要修改 父进程 数据区当中数据操作系统就会给 子进程在内存当中新开辟一块空间用于存储要修改的变量的值。
我们把上述的过程称之为 --父子进程之间的 数据层面的写时拷贝。 在用的时候在给你开辟一块新的空间不用就不开辟。 此时我们再来理解上述代码 fork函数由父进程调用返回的值就直接赋值给 父进程的 Mypid变量如果是子进程调用 fork函数那么此时就会发生写时拷贝相当于在此时有两个Mypid 变量一个是子进程的一个是父进程。让父子进程在访问 Mypid 这个变量之时看到是不一样的值。
父进程在访问 Mypid 变量之时访问的是父进程数据区当中 Mypid 的值而 子进程在访问 Mypid 之时访问的是 操作系统写时拷贝 的新空间当中的数据。 如果父子进程被创建好之后fork往后谁先运行呢 首先当我们开始运行一个程序那么这个进程什么时候开始运行我是不能管理的。比如在windows当中打开了 QQ那么我们只是告诉了操作系统此时要运行 QQ了。但是实际上QQ这个进程什么时候开始是由操作系统决定的。
因为用户只需要使用这个软件即可什么时候运行进程是由操作系统自己处理的。
所以谁先运行是由 调度器决定的。
调度器的工作
操作系统管理进程是管理 用数据结构存储的各个进程的PCB那么此时如果用户想运行某一进程可能就会把这个进程放到 就绪队列当中打比方因为在此之前可能还有很多的进程在排队。那么此时CPU应该挑选哪一个进程开始执行这个工作就是有 调度器决定。 因为 cpu 在计算机当中是少量的资源而进程在运行之时都是要 跑到 cpu 之上去运行的所以如何更好的利用好 cpu让我们感受到 很多个进程在同时被调度调度器 起了很大作用。 理解 bash 解析命令的过程
在上述理解了 fork函数之后我们来了解一下 bash 。bash 是命令解释器所以的命令都是要通过 bash 解释来运行的。
像上述的使用的 fork创建子进程的例子其中的父进程本身就是一个子进程 我们查看这个 进程pid 发现其父进程是 bash 。
所以bash 在解析命令然后运行对应进程的过程当中为了这个进程在运行之后不用影响到我 bash 的运行解释其他命令。所以在 bash 当中也是采用 fork函数来创建子进程的方式来创建 bash 命令解析的运行的子进程。
也就是说bash 在解析命令之后通过调用 fork函数创建子进程至此会后bash 就会继续进行解释命令的作用而 bash 创建的子进程就会去执行 bash 解释出的命令的进程。