linux进程——父子进程层面的PID,fork的原理与理解

        前言:本篇内容主要讲解进程中系统调用fork和父子进程的概念与原理, 想要系统学习linux进程的友友们只管看本篇文章是不行的。 还要学习一些linux进程的周边知识以及linux进程其他方面的知识,博主的linux专栏中已经加入了这些文章方便友友们进行学习。 感兴趣或者想要学习的深入的友友可以一篇文章一篇文章地进行观看与理解。

        ps:本节内容非常不好理解, 需要了解linux进程的PCB, 以及操作系统如何对进程做管理

为了后面实验的方便, 我么先来学习一下如何杀进程——也就是使用指令终止掉进程

首先还是运行一个程序——其实运行程序, 本质上就是./程序变成了进程, 如图是一个程序(注意, 后续都将以process-7-11为程序名进程实验)

然后我们运行后, 再使用ps 和管道查看进程的PID。 也就是进程的标识符。

然后, 我们利用kill -9 PID就能杀掉对应的PID进程

如下图:

知道了如何杀程序后, 我们既然已经知道了每一个进程创建时, 里面都有一个自己的PID,而使用ps是查看所有的PID,但是如何在一个程序运行时,获取这个程序的PID呢?

        现在先来看下面这张图:

        假如说上面一个操作系统, 这个操作系统里面上层是系统调用, 下层是内存缓冲区, 这个时候内存中已经缓存了两个进程。 

        我们通过上面的学习已经知道, PCB对象里面含有PID,而我们可以使用ps axj和管道来获取进程的PID。 但是通过上面的学习我们已经知道, 操作系统不相信我们用户, 所以我们就不能直接访问PCB也就是task_struct里面的PID, 状态等字段。 而想要获取这些字段就必须使用系统调用接口。 

        那么获取当前进程的PID的系统调用接口是什么呢? 这里有一个系统调用接口, 叫做getpid(), 这个函数在哪个进程里被调用, 就会返回哪个进程的PID。

        这里需要注意的是pid是一个整形, 下面是我们自己定义的一个获取系统调用接口的程序。

        我们打开这个程序, 当前进程的PID是如下:

然后使用ps axj 加管道取头如下:

        上面的这个就是一遍运行程序, 一遍ps axj 加管道    

        可以发现,我们的进程里面系统调用和ps打印出来的PID是一样的。 这就是PID, 也就是进程的标识符, 程序每次运行, 都会生成不同的进程, 为什么说是不同的进程, 因为生成的代码虽然没有改变, 但是进程里面的PCB还有数据已经发生了变化。 而PCB不同, 用来标识一个进程的PID就不同。 

        对上面的概念进行试验之后, 我们再来看一下父进程, 也就是PPID。        

        那么, 这个父进程是谁呢? 我们使用ps 加管道过滤操作就可以查看:

从这里面, 我们就可以看懂, 这个8881是bash命令行的PID。

        那么这里的第一个问题就是——为什么额bash命令行也有PCB? 

        其实这也就证明了bash命令行也是一个进程, 不过吃这个进程偏向于管理与交互。 bash命令行在终端启动的时候就已经被cpu计算并执行。 并且一直执行。 这个就是bash命令行, 其特点有点像单例模式里面的懒汉。 

        第二个问题就是——为什么我们写的process-7-11程序的父进程会是bash呢? 

        我们知道, bash命令行是一个解释器, 而命令行解释器的核心工作就是获取用户的输入。 帮助用户进行命令行解释。 

        对于一个进程来说, 就按照process-7-11这个程序来说, 我们在命令行解释器上面输入./process-7-11.exe, 然后回车。 然后命令行就会解释这个程序,命令行解释这个程序就会生成一个子进程, 然后这个子进程被cpu计算, 计算结果再返回, 这个过程, 就是process-7-11.exe进程的产生过程。 

        其实, bash下面的运行的程序, 他们的父进程, 都是bash。 

        bash命令行的PID不变, 我们不管运行多少次程序, 他们的父进程都是bash命令行的PID, 如图:

如上图红框框是第一次启动process-7-11程序, 绿框框是第二次, 蓝框框是第三次。 

而当我们重启一下xshell, 并且重新登陆的时候, 就连bash都会改变PID。 

上图是两次登录普通用户时启动点bash的PID。

父进程的PID也可以被获取。 系统调用接口是getppid, 如下图:

        上图是定义的一个简单应用getppid的代码, 然后运行时,就会这样:

        由上面我们可以发现, 系统调用接口和我们平时使用的c语言接口区别不大。 并没有太大的学习使用方法上学习成本。 

        以上, 就是关于父子进程PID的知识点内容。

现在, 我们来学习如何创建父子进程——这个需要使用fork函数, 这个函数会让函数后面的语句被执行两次, 一个是父进程执行, 一个是子进程执行。 

        首先我们先来看一下没有fork的情况, 如下图:
 

下图是我们使用fork函数的情况:

        我们可以看到, 这里的第二行内容被执行了两次, 这个上面我们已经提到过了, fork相当于创建一个子进程, 这个子进程的代码和数据就是fork后面的内容。 也就是说, 原本只有一个执行流,现在加了fork之后, fork之前的代码相当于只属于父进程一个人, 只有一个执行流, 但是fork之后的代码就会属于子进程和父进程共同所有。

        这里为了验证上面的说法, 我们可以使用man手册查看fork的用法。        

上面这句话的意思就是以父进程为模板, 再创建一个子进程。 

        现在我们在man手册的第行输入return value, 可以查找我们想要看的返回值的相关信息。如下图:

        并且, 如果fork函数成功了, 那么给父进程返回子进程的pid, 0返回给子进程。 如果失败了, 就返回 -1 给父进程。 并且没有子进程被创建。

        那么, 也就是说, fork有两个返回值, 并且这两个返回值的类型都是pid_t, 也就是有符号整形。 那么有两个返回值是什么意思呢, 我们可以试验一下。

下面是运行结果:

        上面的运行结果,就是每一秒打印一条父进程, 打印一条子进程。 这说明父进程和子进程是同时进行的。 并且id > 0,   和 id == 0同时成立。 如果在以前的代码中, 不可能有两个id > 0, id == 0同时存在, 更不可能有两个死循环一起跑。 但是今天调用的fork下就可以。

        由上面打印的pid我们就可以发现, bash的pid就是父进程的ppid也就是27451, process-7-11.exe的pid是10920, 所以子进程的ppid就是如图的10920。 而且子进程pid是10921. 也就是说, 这个程序的执行顺序是命令行bash调用了process-7-11.exe进程, process-7-11.exe又以自身为模板调用了一个子进程。 

        现在又有另外一个问题, 对于上面的程序来说,为什么打印出来的会是一个父进程, 一个子进程, 一个父进程一个子进程的呢?

        这里我们可以这么理解, 正常情况下, 执行流是从上往下的:

        但是对于现在使用了fork来说, 它会返回两个值, 一个返回给父进程。 一个返回给子进程。 也就是说, 我们fork执行之后,它就变成了两个执行流, 只是我们肉眼看不到,但是它真实存在。所以只能通过感知感觉出来。其中一个执行流就是父进程, id接受了父进程的返回值, 符合else if 执行里面的循环。 另一个执行流是子进程。 id接收了子进程的返回值, 符合if里面的循环。

        如图:

        

知道了这些执行逻辑后, 现在就又有了三个问题:

第一个问题——为什么fork函数要返回两个返回值? / 为什么fork要给子进程返回0, 给父进程返回子进程的pid?

第二个问题——一个函数是怎么做到返回两次的? / 如何理解呢?

第三个问题——一个变量怎么会有不同的内容呢? 

第四个问题——fork函数, 究竟在干什么? 干了什么?

        首先来看一下第一个问题, 为什么要给子进程返回0, 给父进程返回子进程pid?

首先这样这要我们知道就可以通过返回不同的值, 然后让子进程和父进程分别进入不同的if else if判断之中, 执行不同的逻辑或者代码块。 

        但是,这里我们要明白, 是因为有if else if的存在, 不同的执行流才会执行不同的代码块, 但是一般而言, fork之后的代码, 父子共享。 

        但是为什么偏偏子进程返回0, 父进程返回的是子进程的pid呢? 

        这里我们可以想一下, 如果父进程里面不是fork一次, 而是fork多次。 或者直接来个循环, 循环fork, 那么是不是就有多个子进程, 而对于每一个子进程, 当父进程要进行控制的时候, 如果没有标识符, 就会变得很麻烦很麻烦。 而有了子进程的pid, 就可以直接找到某个子进程进程管理。 而对于子进程来说, 他们只有一个父进程, 只需要直接找到自己的父进程就可以, 不再需要记住父进程的标识符。

在进行理解第二个问题之前,我们先来理解一下第四个问题——fork函数, 究竟在干什么? 干了什么?

回忆一下我们以前学到的进程的概念——进程 = 内核数据结构(也就是PCB) + 你自己的代码和数据。 如下图为简单的进程图:

现在, 但我们创建一个子进程后, 就是如下图:

这个时候, 子进程的代码就是父进程的代码, 但是子进程的数据却是自己的数据。

        那么, 为什么会这样呢? 从上面的讲解我们知道, 子进程和父进程的代码是共享的, 他们两个公用同一块数据块, 两者指向同一块空间没有问题。 但是里面的数据是不一样的, 就像我们子进程性质的是if里面的数据模块, 而父进程执行的的是else if里面的数据模块。两个模块产生的进程的数据是不一样的, 当一个进程进行修改时, 那么势必会影响到另一个进程。 所以也就不能指向同一块空间, 也就不是一块代码块。

         知道了这些, 就可以拿过来上面的问题——fork函数是如何做到返回两次的——也就是第二个问题。

        首先要知道fork函数是一个系统调用, 它的本质也是一个函数!

        然后, 假设我们的fork实现如下:

        那么, 这里思考一下, 当这个函数走到return的时候, 有没有执行完呢? 这里要知道的是,当函数走到这里, 就已经把要做的工作做完了。 

        这里我们还记不记得上面说过, fork函数创建子进程后, 函数后面的代码就会被子进程和父进程所共享。 其实, 这里的本质就是fork函数里面创建好子进程后, 也就是上图红框框的部分完成后。 子进程也就被创建出来了。 然后执行流就变成了两个, 一个子进程的执行流, 一个父进程的执行流。 也就是说, 这个时候的return语句, 其实就是由两个执行流会执行它。 一个是父进程的执行流。 这两个执行流都会返回一个值。 这就是为什么fork会有两个返回值, 并且返回值给一个给子进程, 一个给父进程的原因。 

解析来理解第三个问题, 那么我们要知道首先在任何一个平台。 进程运行的时候都具有独立性。 

        就比如我们打开一个qq, 一个微信, 然而当我们的qq崩溃的时候, 我们的微信会受到影响吗?或者说我们的微信崩溃的时候, 我们的qq会受到影响吗?

        答案是不会, 那么如果子进程和父进程公用一个数据块, 当子进程改变数据的时候, 父进程也会改变数据。因为数据可能被修改, 不能让父进程和子进程共享一份数据!

        所以, 对于子进程, 数据是独立的。 那么就势必当创建子进程的时候要拷贝一份父进程的数据独立出来。 也就是我们上面画的那张图:

        这个时候, 父进程和子进程的数据就割裂起来了, 父进程崩溃或者子进程崩溃不影响对方。

那么这个时候就有了另外一个小问题, 我们知道, 父进程和子进程的数据不是同一个, 两者互不影响。 当我们的父进程访问某一数据, 是在父进程的数据块访问数据,当我们的子进程访问某个数据, 是在子进程的数据块访问数据。 

        但是, 这里的小问题是, 我们的子进程可能对于父进程拷贝的数据不会全部访问。 只会访问某一小部分的数据。 这里就有了多拷贝的问题。 存在了空间的浪费。 那么,操作系统呢, 对于这个问题, 就不去将父进程的数据全部拷贝过一份了。 而是等子进程访问父进程的数据的时候, 想要修改时, 那么操作系统就将这部分要被修改的数据拷贝一份, 然后管理子进程,让它不去改父进程中的数据了, 而是去修改拷贝出来的数据。 

那么就是说, 凡是对于父子进程中子进程被创建的时候, 父子进程的代码和数据都是共享的。 只有当子进程要修改父进程的数据的时候, 才会拷贝这一部分要修改的数据出来交给子进程进程处理。 之后, 有多少要修改, 就给子进程拷贝多少, 需要拷贝多, 就拷贝多少。——这种技术成为数据层面的写实拷贝。

        那么, 回到我们的第三个问题, 一个变量为什么有不同的内容。 首先我们知道,对于id来说, 它是不是父进程里面的数据?那么当fork返回的时候, 当返回的是父进程的返回值时, 那么写入的就是父进程的数据。 当返回的是子进程的返回值时, 那么就会发生写诗拷贝!!所以, 操作系统对于id变量拷贝了一份, 现在操作系统里面就有两份id变脸, 这就是为什么一个变量会有不同的内容。 

        至于为什么同一个id会去访问不同的内存空间, 这就涉及到了进程地址空间的问题!这些在后续的进程地址空间会讲到。

        现在来看最后一个问题, 对于一个父子进程来说, 哪个进程限制性呢?——这个答案是不确定的, 这个是由调度器决定的。 不同的环境是不一样的。 所以无法确定。 

        对于bash来说, 我们在bash命令行运行指令的时候, 那么我们就知道了, bash一定调用了fork, 然后原本的bash命令行继续进程命令行读取, 而fork生成的子进程, 则执行命令。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3246867.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

连锁零售门店分析思路-人货场 数据分析

连锁零售门店分析思路 以下是一个连锁零售门店的分析思路: 一、市场与竞争分析 二、门店运营分析(销售分析) 三、销售与财务分析 四、客户分析 五、数字化与营销分析 最近帮一个大学生培训,就门店销售分析 ,说到门店…

记录些MySQL题集(8)

ACID原则、事务隔离级别及事务机制原理 一、事务的ACID原则 什么是事务呢?事务通常是由一个或一组SQL组成的,组成一个事务的SQL一般都是一个业务操作,例如聊到的下单:「扣库存数量、增加订单详情记录、插入物流信息」&#xff0…

Css布局-伸缩盒笔记

前言 伸缩盒作为css3中的布局标准,不得不学呀,跟着b站yu神走一遍,yushen牛逼! 伸缩盒子布局的优势 当然是伸缩了 伸缩容器与伸缩项目 display: flex display: inline-flex (用的少) 一个html元素既可以是…

我们距离通用人工智能还有多远?当它诞生后,会给社会发展带来哪些变革?

当我们谈论通用人工智能(AGI),我们指的是一种能够像人类一样执行各种认知任务的人工智能系统。目前,我们所拥有的人工智能技术主要是狭义人工智能(ANI),专注于特定任务,如语音识别、…

老司机减分宝典助手-学法减分扣分题目及答案 #经验分享#经验分享#职场发展

学法减分其实就是把我们驾驶证上面的分数一分一分地找回来,为什么说是一分一分地找回来呢?因为必须先把违章处理完才可以,无论这辆车是不是你的,无论这辆车挂靠在谁的公司名下或者是单位名下,你都可以把这个分找回来&a…

卷积神经网络图像识别车辆类型

卷积神经网络图像识别车辆类型 1、图像 自行车: 汽车: 摩托车: 2、数据集目录 3、流程 1、获取数据,把图像转成矩阵,并随机划分训练集、测试集 2、把标签转为数值,将标签向量转换为二值矩阵 3、图像数据归一化,0-1之间的值 4、构造卷积神经网络 5、设置图像输入…

3.RabbitMQ安装-Centos7

官方网址:gInstalling RabbitMQ | RabbitMQ 安装前提,需要一个erlang语言环境。 下载 erlang: Releases rabbitmq/erlang-rpm GitHub rabbitmq-server: 3.8.8 Releases rabbitmq/rabbitmq-server GitHub 安装命令 (说明i表示安装&#xff…

FPGA FIR fdatool filter designer MATLAB

位数问题 fdatool 先确定输入信号的位宽,比如17位在fdatool中,选set quantization parameters 选input/output 设置input word length 为17bit(not confirmed) fir compiler implementation 注意: 当设置输入位宽为16位时,ip核…

深入解析HNSW:Faiss中的层次化可导航小世界图

层次化可导航小世界(HNSW)图是向量相似性搜索中表现最佳的索引之一。HNSW 技术以其超级快速的搜索速度和出色的召回率,在近似最近邻(ANN)搜索中表现卓越。尽管 HNSW 是近似最近邻搜索中强大且受欢迎的算法,…

具有I2S输出的多模数字麦克风ICS-43434咪头

外观和丝印 ICS-43434麦克风3.50 mm x 2.65 mm,丝印为434(图片不好拍,隐约可见434) 一般描述 ICS-43434 是一款数字 IS 输出底部收音孔麦克风。完整的 ICS-43434 解决方案包括 MEMS 传感器、信号调理、模数转换器、抽取和抗混叠滤…

智能手术新时代:Apple Vision Pro在医疗领域的突破性应用

无人驾驶的未来:AI如何重塑我们的出行世界-CSDN博客文章浏览阅读2.2k次,点赞109次,收藏64次。无人驾驶汽车的发展是AI技术应用的一次伟大尝试。特斯拉与百度“萝卜快跑”在这个领域的竞争与合作,不仅展示了AI技术的强大潜力&#…

Heterophilous Distribution Propagation for Graph Neural Networks

推荐指数:2颗星 HDP不是聚集所有邻居信息,而是根据训练期间的伪标签自适应的将邻居分为同配和异配.并通过原型对比,垂直约束异配邻居分布 前人的问题 1.邻居划分的不足.已存在的方法要不不能区分同配和异配,要不简单的采用阈值去划分同配异配 2.以往的异配GNN仅仅是简单的邻居…

【C++】拷贝构造函数及析构函数

📢博客主页:https://blog.csdn.net/2301_779549673 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 CSDN🙉 📢未来很长&#…

嵌入式Linux:文件属主和属组

目录 1、修改文件所有者和组 2、chown函数 3、fchown函数 4、lchown函数 在Linux系统中,每个文件都有一个属主(owner)和一个属组(group)。文件权限系统根据这些信息来决定哪些用户和组可以访问和操作文件。 文件属…

简单爬虫案例

准备工作: 1. 安装好python3 最低为3.6以上, 并成功运行pyhthon3 程序 2. 了解python 多进程原理 3. 了解 python HTTP 请求库 requests 的基本使用 4. 了解正则表达式的用法和python 中 re 库的基本使用 爬取目标 目标网站: https://…

【C++】类和对象的基本概念与使用

本文通过面向对象的概念以及通俗易懂的例子介绍面向对象引出类和对象。最后通过与之有相似之处的C语言中的struct一步步引出C中的类的定义方式,并提出了一些注意事项,最后描述了类的大小的计算方法。 一、什么是面向对象? 1.面向对象的概念 …

CH390H+STM32F1+LWIP

文章目录 1、CH390芯片介绍2、电路部分3、LWIP调试3.1修改点13.2 修改点2 4、结果展示参考 1、CH390芯片介绍 官网地址: 南京沁恒微电子股份有限公司 特点: 2、电路部分 CH390及接口: STM32F1引脚: 不含LWIP的demo及LWIP…

vue3+ts 封装echarts,根据tabs切换展示

<div class"bottom"><div class"topli"><p>用电统计</p><div class"tabs"><div class"tab" :class"{ active: active.tab1 index }"v-for"(item, index) in tabsList1" :key&q…

Pikachu SQL注入训练实例

1 数字类型注入 打开Burp Suit工具&#xff0c;选择Proxy&#xff0c;之后点击Open Browser打开浏览器&#xff0c;在浏览器中输入http://localhost:8080/pikachu-master打开Pikachu漏洞练习平台。 选择“数字型注入”&#xff0c;之后点击下拉框随便选择一个ID&#xff0c;…

Apple Vision Pro 和其商业未来

机器人、人工智能相关领域 news/events &#xff08;专栏目录&#xff09; 本文目录 一、Vision Pro 生态系统二、Apple Vision Pro 的营销用例 随着苹果公司备受期待的进军可穿戴计算领域&#xff0c;新款 Apple Vision Pro 承载着巨大的期望。 苹果公司推出的 Vision Pro 售…