【Linux】四、Linux 进程概念(上篇)

简介: 目录前言一、冯诺依曼体系结构1.1 冯诺依曼体系结构是什么1.2 冯诺依曼体系结构为什么这么设计1.2.1 思考1.2.2 了解一下计算机的存储分级1.2.3 解释1.3 往下要明确几点1.4 冯诺依曼实例二、操作系统(Operating System)2.1 操作系统概念2.2 为什么要有操作系统

目录

前言

一、冯诺依曼体系结构

1.1 冯诺依曼体系结构是什么

1.2 冯诺依曼体系结构为什么这么设计

1.2.1 思考

1.2.2 了解一下计算机的存储分级

1.2.3 解释

1.3 往下要明确几点

1.4 冯诺依曼实例

二、操作系统(Operating System)

2.1 操作系统概念

2.2 为什么要有操作系统

2.3 计算机体系及操作系统的定位

2.4 如何理解 "管理"

2.4.1 学校例子(用于理解概念)

2.4.2 银行例子(类比系统)

2.5 系统调用和库函数概念(总结)

三、进程(process)

3.1 基本概念

3.2 描述进程 - PCB

3.2.1 task_struct - PCB的一种

3.2.2 task_ struct内容分类

3.3 组织进程

3.4 查看进程

3.5 通过系统调用获取进程标示符

3.5.1 查看进程ID(PID)

3.5.2 查看父进程ID(PPID)

四、通过系统调用创建进程 - fork 初识

4.1 创建子进程 - fork

4.2 测试 fork

4.3 一个感性的问题

4.4 为什么会有两个返回值


前言

本节主要学习以下内容:

  • 认识冯诺依曼系统
  • 操作系统概念与定位
  • 深入理解进程概念,了解PCB学习进程状态,学会创建进程,掌握僵尸进程和孤儿进程,及其形成原因和危害
  • 了解进程调度, Linux进程优先级,理解进程竞争性与独立性,理解并行与并发
  • 理解环境变量,熟悉常见环境变量及相关指令, getenv/setenv函数
  • 理解C内存空间分配规律,了解进程内存映像和应用程序区别, 认识地址空间

一、冯诺依曼体系结构

1.1 冯诺依曼体系结构是什么

       冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器控制器存储器输入设备输出设备),这套理论被称为冯·诺依曼体系结构。

       我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

冯诺依曼体系结构如图:

image.png

  1. 输入设备:键盘、摄像头、话筒、磁盘、网卡...
  2. 输出设备:显示器、音响、磁盘、网卡...
  3. 存储器:内存
  4. 中央处理器(CPU)由运算器和控制器组成
  5. 运算器:集成于 CPU,用于实现数据加工处理等功能的部件。
  6. 控制器:集成于 CPU,用于控制着整个 CPU 的工作

1.2 冯诺依曼体系结构为什么这么设计

1.2.1 思考

有没有想过冯诺依曼体系结构为什么不这么设计?而是设计多了一个储存器,也就是内存?

想要了解清楚,请往下阅读!


image.png

1.2.2 了解一下计算机的存储分级

image.png

在此之前先了解一下计算机的存储分级,其中寄存器离 CPU 最近;L1、L2、L3 是对应的三级缓存;主存通常指的是内存;本地存储(硬盘)和远程储存通常指的是外设。

储存器的特点:离 CPU 更近的,存储容量更小、速度更快、成本更高,如主存往上的;离 CPU 更远的,则相反,如本地磁盘。如上图,它们呈现如金字塔形状。

1.2.3 解释

我们对木桶原理都有了解,要去衡量木桶能装多少水,并不是由最高的木片决定的,而是由最短的木片决定的。

image.png

而我们的中央处理器CPU 就是如木桶原理一样。

假设 CPU 直接访问磁盘,也就是如下图,那么它的效率可太低了。假设这里 CPU 是速率是纳秒级别的,则磁盘是速率毫秒级别的,这两个之间的速率就相差了 100 万倍,(1s=10^3ms(毫秒)=10^6μs(微秒)=10^9ns(纳秒)),当一个快的设备和一个慢的设备一起协同时,最终的效率肯定是以慢的设备为主。所以硬盘的存在严重拖慢了 CPU 的速率,整个计算机体系的效率就一定会被拖累。

image.png

所以,这时候就需要有一个介质来联通 CPU 和 硬盘之间的通道,这个介质就是冯诺依曼体系结构中的存储器,也就是我们的内存。这下理解冯诺依曼体系结构为什么这么设计了吧。

有了内存的存在,计算机的整体效率就会大大的提高。依旧假设这里 CPU 是速率是纳秒级别的,则磁盘是速率毫秒级别的(1s=10^3ms(毫秒)=10^6μs(微秒)=10^9ns(纳秒),CPU 与内存的速率就只是相差了 1000 倍左右,内存与硬盘之间的速率也是差了 1000 倍左右,CPU 只与内存进行交互,不与外设(磁盘之类)进行交互,这里计算机的短板就不再是磁盘了,所以计算机的整体速率就会大大提升

----------------我是分割线---------------

那有人又问了,既然这个存储器(内存)这么好,为什么不把计算机的磁盘内存全部换成内存或者是寄存器?

首先明确内存是一直需要通电的,没有电的供应它里面存储的所有数据都会丢失,而像磁盘之类的没电了数据依旧存储在磁盘里面,放上个十年也没有事。第二,要明白内存是要钱的,越靠近 CPU 的内存价格越昂贵。假设把计算机的磁盘内存全部换成内存或寄存器,从技术角度可以的,但是生产出来的计算机就会特别昂贵十几万起步那种,这种计算机的价格普通人接受不了,这种产品就不会进行大量的传播。要知道:凡是被广泛传播的产品,它的价格一定是便宜的,质量是可以的,价格普通人可以接受。就好比几千万的车,你身边的人为什么不开呢?原因就是它价格昂贵,普通人接受不了,那么这种产品就不会被大范围的传播。

而冯诺依曼的这种体系结构通过添加一块内存,几乎可以达到和你全部换成内存的计算机速率一致,还能以较低的成本生产出来,这样的产品就会被世界范围内的人所接受。换句话说,就是因为它便宜,你才愿意用它、买得起它,冯诺依曼体系结构就是这个道理。

1.3 往下要明确几点

  1. CPU 读取数据(数据+代码),都是要从内存读取。站在数据的角度我们认为 CPU 不和外设直接进行交互。
  2. CPU 要处理数据,要先将外设的数据加载到内存。站在数据的角度,外设直接和内存打交道。
  3. 我们把数据搬到内存的过程叫做 input。CPU 完成数据的运算,不能直接把计算结果直接打到外设上,而是把计算结果刷新到内存中,再有内存把数据刷新到外设中。内存把数据刷新到外设中这个过程叫做 outputoutput
  4. 我们将数据输入再到输出这个过程就叫做 IO
  5. 程序要运行,必须要加载到内存中。程序就是文件,而文件就在磁盘中。
  6. 那为什么程序要运行必须要加载到内存中呢?因为冯诺依曼体系结构的特点决定的!!

这里暂且简单认识,后面会详细讲。

关于冯诺依曼,必须强调几点:

  • 这里的存储器指的是内存
  • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
  • 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
  • 一句话,所有设备都只能直接和内存打交道

1.4 冯诺依曼实例

       对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程

从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发送文件呢?(注意这里的计算机都遵循冯 • 诺依曼体系结构,且这里不谈网络,不考虑细节,只谈数据流向)

(1)假设你给qq朋友发送 hello,你是发送数据的一端,请解释数据是如何流向的?(不考虑细节,只谈数据流向

解释:你此时从键盘上输入 “hello”,此时 “hello” 会回显到你的显示器上(不考虑细节)此时“hello” 会储存在寄存器(内存)中,然后内存传给 CPU ,经过 CPU 处理写回你的内存里,然后由内存刷新到外设(这里不在是磁盘,而是网卡),然后上传到网络上,经过网络你朋友的设备中的输入设备(网卡)先识别到这条消息,传到内存,内存再传给 CPU 计算,计算完成写回内存里,再由内存刷新到你朋友的输出设备(这里是显示屏),到这一条数据的流向就完成了。

image.png

(2)发送的是文件呢?

文件依旧是数据,与上面类似,不在解释。计算机的数据流向基本都是这样的,进行类比即可理解

(3)数据为什么这么流向?

是由你的硬件和冯诺依曼体系结构决定的

----------------我是分割线---------------

二、操作系统(Operating System)

2.1操作系统概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

  1. 内核(进程管理,内存管理,文件管理,驱动管理)
  2. 其他程序(例如函数库, shell程序等等)

2.2 为什么要有操作系统

  1. 与硬件交互,管理所有的软硬件资源
  2. 为用户程序(应用程序)提供一个良好的执行环境

总的来说就一句话:对下管理好所有的软硬件,对上给用户提供一个稳定高效的运行环境。

2.3 计算机体系及操作系统的定位

计算机体系:

image.png

操作系统的定位:

在整个计算机软硬件架构中,操作系统的定位是: 一款纯正的“搞管理”的软件

那问题来了,什么是“管理”呢,如何理解“管理”?

----------------我是分割线---------------

2.4 如何理解 "管理"

2.4.1 学校例子(用于理解概念)

       现实中我们做事情无非是 a、做决策 b、做执行。比如你想吃一碗粉,首先你进行做决策去哪家餐馆,做了决定后你走向你想去的餐馆,走这个过程就是做执行。

假设在学校里一般有这三种角色(进行了简化)

  1. 校长(管理者)
  2. 辅导员(执行者)
  3. 学生(被管理者)

这里还要明白一点:管理者和被管理者可以不直接沟通。比如,你的大学校长(管理者)没有和你直接见面,却可以随时管理你。

比如拿奖学金与否、挂科与否,表现如何,学生信息如何,校长都可以随时管理。比如说评选奖学金,校长在系统中筛选好某系某级综合成绩排名前 3 的学生来发奖学金,这时校长把 3 位同学的辅导员叫过来,并要求开一个表彰大会来奖励 3 位同学,然后辅导员就开始着手执行工作。再比如,校长要开除一名学生,则校长只要知道这个学生挂了多少科,有没有达到学校的标准,没达到标准就可以开除他了,开除就找辅导员执行这项工作。

校长为什么能管理你,为什么连你的面都没见过就能管理你,因为你的个人信息在学校的系统里面,也就是说校长管理学生的本质是通过 “ 数据 ” 来进行管理的

这说明:拿到被管理者的核心数据,来进行支持管理决策,这才是核心。

  • 这里的校长(管理员)——就相当于操作系统
  • 辅导员(执行者)——相当于驱动程序
  • 学生(被管理者)——相当于软硬件

上述说明:管理是对管理对象的数据的管理

----------------我是分割线---------------

理解了上面,那又有一个问题来了,校长是如何拿到数据并对它进行管理呢 ?

假设只有一个学生,这个学生产生的各种数据,校长都可以随便对这些进行管理。

那如果有5万学生呢,这些学生每天产生的数据全部扔给校长,校长又怎么管理呢?直接对数据管理明显就是不可能,这种无序的数据是没有意义的。校长就定义一个结构体,这个结构体包含了一名学生的所有信息,有多少学生就定义这个结构体数组有多大。

创建这个结构体就是对被管理者进行描述,再根据描述类型定义对象,可以把对象组织成数组,那么对学生数据的管理工作就变成了对数组的增删查改核心就是:先描述,再组织(先描述就是用C语言用结构体进行封装,C++的类就是一切皆对象)

如果把数组改成链表再组织起来,对学生的管理就转化成了对链表的增删查改, 如果把数组改成二叉树,红黑树...等等各种数据结构再组织起来,就变成了对某种数据结构的管理。

管理的核心思路就是: 先描述,再组织(简称六字真言)

同理,操作系统对软硬件的管理, 就变成了对数据结构的管理,反过来也说明操作系统内部一定存在大量的数据结构和算法。

这里我解释有点绕,水平有限,hhh...

----------------我是分割线---------------

2.4.2 银行例子(类比系统)

假设有一个银行系统,类比如上。

银行工作的时候,整个封闭的银行就放出了几个柜台,供业务工作使用,要办理业务的人来了就到柜台去办理。那为什么银行给你提供服务是以柜台窗口的形式进行?为什么柜台前还有一块很厚的玻璃?答案是:不相信你呗。这也说明银行这套体系预先把你当坏人,把所有人都预先当做坏人,有这种保护措施同时也将银行的风险降到了最低,还能给银行提供了一个稳定安全的服务。

进行类比,如果计算机把操作系统中的各种东西(内存管理,文件系统,进程管理,驱动管理...等等)暴露给用户,就好比银行门户大开,工作人员没有玻璃阻隔跟你面对面交流,银行的钱直接就放到桌面上,上层的人(客户)想拿钱就随时拿,这就给银行带来很大危险(随时遭受抢劫..)。这说明不把操作系统暴露给用户就是为了保证操作系统的安全性,操作系统也把所有人当做坏人。用户就直接去柜台办理。类比,计算机用户也需要办理业务,操作系统也需要给用户提供各种服务,提供的服务也是通过柜台来办理,这里的柜台就是各种系统接口(system call)

这里的各种系统接口就是操作系统提供的各种函数。

银行用户有需求想办理业务,银行提供各种服务,就如LInux是用C语言写的,系统接口的本质:就是用C语言提供的函数(操作系统相关的,不是我们C语言的库函数)。我们把这种系统接口(system call)就叫做系统调用。

到此结束,总结一下:

  1. 操作系统不信任任何人
  2. 对外提供服务是以接口的形式提供服务
  3. 只能使用系统接口访问操作系统,其他任何方式都不行

直接使用操作系统的接口成本会比较高,所有在操作系统之上还提供了一个服务层,这个服务层就好比 shell外壳、图形化界面,这个服务层用于方便小白用户使用操作系统。操作系统之上还提供了一个第三方库,第三方库就是对系统接口的封装,如C库、C++库...,第三方库方便开发人员使用。

2.5 系统调用和库函数概念(总结)

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发

----------------我是分割线---------------

三、进程(process)

3.1 基本概念

在Windows下我们查看进程在任务管理器中

image.png

  • 我们自己启动一个软件,本质上就是启动了一个进程。
  • 在 Linux 下,执行一个命令,运行一个程序 ./xxx ,其实就是在系统层面上创建了一个进程
  • 一个被加载到内存中的程序,就已经不能叫做程序了,是叫一个进程

操作系统里面是存在大量进程的,也可以同时加载多个进程,Linux 也是如此。那存在这些大量的进程要不要被管理呢?答案是必须的

Linux 是如何管理大量的进程的呢?

答案是六字真言:先描述,再组织

程序加载到了内存,操作系统该怎么调度呢?操作系统怎么知道哪个执行完了,怎么知道哪个的优先级需要调整,怎么知道哪个执行到一半需要切换?操作系统完全不知道。

image.png

操作系统目前没有能力管理这些进程,因为这些加载进来的程序只有代码和数据,没有任何描述自身结构,所以操作系统需要对进程管理就需要对进程先描述,再组织管理。

先描述:操作系统为了管理进程,对加载到内存的每一个程序,也就是对每一个进程申请了一个PCB的结构体,(PCB下面讲),每一个PCB结构体都保存了每一个进程的所有属性

什么是属性呢?

C++中的类一切皆对象,C语言的 struct 封装,C/C++对一个事物的封装一定包含了该事物的所有属性。

我们人认识世界,是通过“属性”来认识的,比如我对一个事物进行描述:有一个事物他会汪汪叫,会帮人们看家护院,这时我们潜意识就会想到狗,是不是呢?只要是被人认识的东西,就一定会被人抽象换成各种属性,然后就能用计算机语言描述出来。所以PCB这个结构体一定包含了进程所有的属性

image.png

那问题又来了,属性是数据吗?

答案是属性也是数据。

属性和程序内的代码和数据有关系吗?

其实它是两套概念,曾经在Linux 指令中讲过:文件 = 内容+属性,可执行程序本质就是一个文件,把可执行程序加载到内存中本质只是加载了可执行程序的内容加载到内存里就叫进程而不再叫程序了,对进程管理还需要大量的数据结构,大量的数据结构叫做PCB结构,用PCB结构来描述进程所有的属性,其中PCB结构的属性和进程所有的内容是没有太大关系的,这个PCB结构体的属性包括:这个进程在

哪里,是谁启动的,什么时间启动的,有没有唯一的标识,有没有唯一的ID,优先级是什么....这就是进程的属性

因为PCB结构,一个个PCB结构体连接起来,所以对进程的管理到最后,变成了对PCB结构体链表的增删查改 。

每加载进来一个程序,也就是每增加一个进程,都会添加一个新的PCB结构体

到最后解释一下,什么是进程呢?

进程 = 对应的代码和数组 + 进程对应的PCB结构

为什么存在PCB结构体?

操作系统要管理进程,要管理进程就要:先描述,再组织

什么是PCB结构体?

没有谈,下面解释

----------------我是分割线---------------

3.2 描述进程 - PCB

什么是PCB?

课本上称之为PCB(process control block),PCB就是一个结构体,不同的操作系统中PCB的名字叫法不同。

在 Linux 操作系统下的PCB结构体叫做是: task_struct

task_struct 跟PCB的关系就跟王婆和媒婆的关系一样,王婆是一个具体的媒婆,task_struct 也是一个具体的PCB结构体

3.2.1 task_struct - PCB的一种

  • 在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

3.2.2 task_ struct内容分类

这里的简单了解,先有个概念,后面详细讲

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

3.3 组织进程

可以在内核源代码里找到它。Linux 所有运行在系统里的进程都以 task_struct 链表的形式存在内核里,这个链表是以双链表呈现

----------------我是分割线---------------  

3.4 查看进程

默认查看自己终端的进程

ps

image.png

查看系统所有的进程

ps axj

image.png

这里我写了个死循环,方便观察进程

#include<stdio.h>#include<unistd.h>intmain()
{
while(1)
  {
printf("hello Linux\n");
sleep(2);
  }
return0;
}

运行程序

image.gif

查看我们想要查看的程序

1. ps axj | grep '查看进程名'
2. //例如:
3. ps axj | grep 'mytest'

image.png

把头部标签带上

ps axj | head -1 && ps axj | grep 'mytest'

image.png

其中,PID就是进程ID(process ID),其他后面解释。

image.png

那有人好奇了,你说 10983 是 mytest 的进程ID,那下面的13696 进程又是什么?

别忘了你执行了 grep 命令,一个命令的执行也是一个进程,所以 13696 进程是 grep 命令的进程ID

image.png

如果结束 mytest 这个进程,当然你查就查不到啦

进程的信息可以通过 /proc 系统文件夹查看,把所有进程以文件系统的形式展示

ls /proc

image.png

查看单个进程详细信息

1. ls /proc/[进程ID] -al
2. //例如
3. ls /proc/[20190] -al

其中的 cwd 为当前工作目录(每个进程的都会有一个属性,来保存自己所在的工作路径),exe 则是该进程的可执行程序所处的位置

image.png

查看单个进程简略信息(所有属性),上面是详细的,这里简略

ls /proc/进程ID

image.png

要查看更多 ps 选项用法,直接 man 就行了

man ps

----------------我是分割线---------------

3.5 通过系统调用获取进程标示符

3.5.1 查看进程ID(PID)

通过系统调用获取 PID 标示符为:getpid

man查看一下详细信息,其中的头文件与C语言无关,pid_t 是无符号整数,相当于用于定义一个变量的类型,直接使用它即可(如:定义一个变量id,pit_t  id = 0)

image.png

修改代码,通过系统调用查看pid

#include<stdio.h>#include<unistd.h>#include<sys/types.h>intmain()
{
while(1)
  {
printf("pid: %d\n", getpid());
printf("hello Linux\n");
sleep(2);
  }
return0;
}

运行一下,可以查看PID

image.png

进行比对,命令行与系统调用的PID一样

image.png

除了在自己的终端关闭进程,在别的终端也可以杀掉进程,命令 kill,-9为发送九号信号(暂且不讲,后面学)

1. kill -9 [PID]
2. //例如
3. kill -9 18530

回车后,就发现右边终端的进程被杀掉了

image.gif

每个进程都会有自己独立的 PID,不会出现重复的PID

----------------我是分割线---------------

3.5.2 查看父进程ID(PPID)

进程是有父子关系的,PID 的父进程叫做 PPID

image.png

通过系统调用获取父进程ID标示符为:getppid

同样 man查看一下详细信息,其中的头文件与C语言无关,pid_t 是无符号整数,直接使用它即可

image.png

修改代码,通过系统调用查看ppid

image.png

进行比对,一致

image.png

父进程PPID永远为 bash,子进程PID都为bash 创建的 。类比:父进程相当于王婆,子进程相当于王婆手下的一个实习生

比如,把自己终端的 bash (PPID)杀掉,自己所处的终端就不能执行命令了,每个终端都有自己的 bash,为shell 外壳程序创建

这里我在自己的终端杀掉这个bash终端它直接强制退出了,我就无法演示了。如果在另一个终端杀掉自己终端的bash,之后自己终端所有命令都无法执行。

image.png

四、通过系统调用创建进程 - fork 初识

4.1 创建子进程 - fork

man 查看一下 fork 函数,头文件是<unistd.h>,fork的作用是创建一个子进程(child process)

image.png

fork 的返回值有两个

1、创建子进程失败返回 -1

2、成功:a.给父进程返回子进程的PID     b.给子进程返回 0

先记住 fork 有两个返回值

image.png

----------------我是分割线---------------

4.2 测试 fork

测试代码

#include<stdio.h>#include<unistd.h>#include<sys/types.h>intmain()
{
printf("I am parent process\n");
fork();
printf("you can see me\n");
sleep(2);
return0;
}

把 fork 注释掉,运行结果,只执行一次 printf

image.png

不注释 fork ,运行结果, printf 被执行了两次

image.png

打印了两次,这是为什么?它只有一条语句,可是在 fork 之后被执行了两次,这是怎么回事?

其实是 fork 函数的原因,fore 给进程创建了一个子进程,fork 之后的代码是父子共享的,父进程和子进程各执行一次打印

再进行测试

#include<stdio.h>#include<unistd.h>#include<sys/types.h>intmain()
{
printf("I am parent process\n");
pid_tret=fork();
printf("ret: %d\n", ret);
sleep(1);
return0;
}

结果打印了两次,一个变量怎么可能是两个值?

image.png

实是创建子进程成功:a.给父进程返回子进程的PID b.给子进程返回 0(其他后面解释)

在以前学C/C++的时候,我们没见过两个死循环同时执行,也没见过 if 和 else 同时执行

测试代码

#include<stdio.h>#include<unistd.h>#include<sys/types.h>intmain()
{
pid_tid=fork();
if(id<0)
  {
//创建子进程失败perror("fork");
  }
elseif(id==0)
  {
//child process(task)while(1)
    {
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
    }
  }
else  {
while(1)
    {
//parent processprintf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
    }
  }
return0;
}

fork 之后有两个不同的执行流 ,两个进程的返回值不同从而决定了执行不同条件的代码,子进程的ppid 是父进程的 PID

image.png

这里的现象仅仅是因为使用 fork 产生的特殊情况

   ----------------我是分割线---------------

4.3 一个感性的问题

fork 为什么给子进程返回 0,给父进程返回子进程的PID?

这里没有官方的解释,这里只给出这个问题的一个感性认识。

一个子进程永远只有一个父进程,但父进程可以拥有多个子进程。比如,一个孩子只有一个父亲,而父亲可以有多个孩子。

进程多了就要有进程的标识符,没有事不行的。就好比一个父亲他有三个孩子,父亲想叫其中的一个孩子,得叫孩子的名字吧,不叫孩子怎么知道叫哪一个孩子,总不能说:孩子,你过来一下。这样叫哪知道是哪一个,同比进程也是如此,得有一个认得出你的标识符。给子进程返回 0,给父进程返回子进程的PID就是类似情况

    ----------------我是分割线---------------

4.4 为什么会有两个返回值

image.png

创建子进程也新建了一个 task_struct 结构体,这个 task_struct 以父进程的task_struct 为模范创建,但又不完全相同。

CPU 想要运行一个进程,要经过复杂的数据结构和算法,形成一个运行队列,从运行队列里面选择进程执行。

CPU 运行一个进程,本质是从 task_struct 形成的队列中挑选一个task_struct 来执行它的代码。

一个函数已经准备 return,核心代码已经执行完了。那也说明子进程也已经加载到运行队列里面了,CPU可以随时调度,父进程与子进程已经同时存在运行队列里面了,父进程执行完成子进程也差不多完成了,父子各自执行自己的返回值,父进程和子进程执行完当然就有两个返回值了。

返回两次不代表一个变量会记录两个值

   ----------------我是分割线---------------

父子进程被创建出来,哪一个程序先运行?

答案不一定,都有可能,由操作系统调度器决定

到这里只 fork 的基本概念就差不多完成了

   ----------------我是分割线---------------

文章先到这,下篇即将更新

相关文章
|
1月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
38 0
|
3月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
675 2
|
21天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
88 4
linux进程管理万字详解!!!
|
12天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
54 8
|
9天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
21天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
60 4
|
22天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
23天前
|
消息中间件 存储 Linux
|
30天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
39 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
26 1
下一篇
无影云桌面