进程的创建:fork()

引入

创建进程的方式我们已经学习了一个!在我们运行指令(或者运行我们自己写的可执行程序)的时候不就是创建了一个进程嘛?那个创建进程的方式称为指令级别的创建子进程!
那如果我们想要在代码中创建进程该怎么办呢?

fork()

fork 函数的使用,见见猪跑

这是一个系统调用函数,我们可以使用 man 指令来查看函数的说明文档!
在这里插入图片描述
在这里插入图片描述

介绍:这个函数可以为调用这个函数的进程创建一个进程,我们把这个新创建出来的进程叫做子进程,调用这个函数的进程称为父进程!
返回值:如果成功创建子进程,子进程的 PID 将被返回给父进程,0 将被返回给子进程;如果创建子进程失败,-1 将返回给父进程,错误码将被设置!

好的,我们不管这么多,先来用一用!

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{pid_t id = fork();if(id == 0) //子进程{while(1){printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}else if(id > 0) //父进程{while(1){printf("我是父进程, pid: %d\n", getpid());sleep(1);}}else // 子进程创建失败{perror("fork():");}return 0;
}

在上面的代码中,我们创建了一个子进程,让子进程循环打印自己的 pidppid,让父进程循环打印自己的 pid 我们来验证一下通过 fork 函数创建出来的进程到底是不是调用该函数进程的子进程。
在这里插入图片描述

我们看到子进程的 ppid 是 4176994,父进程的 pid 是4176994。说明我们的结论没有问题呢!

看到这里,你可能会有很多问题🤔~不着急我们一个一个来解决!

问题一:

为什么 fork 要给子进程返回 0,给父进程返回子进程的 pid

你想啊!一个进程只能调用一次 fork 函数嘛?显然不是的!我循环调用 fork 一百次,那么父进程应该如何区分这么多的子进程呢?那还不得靠返回值啦!
因此,fork 函数返回不同的值就是为了让父进程能够区分自己创建的子进程,从而让不同的执行流执行不同的代码!

问题二:

fork 函数究竟在干什么?干了什么?

我们在进程的概念部分知道了:进程 = PCB (进程控制块, Linux 环境下叫 task_struct) + 代码和数据。这也就意味着,task_struct 中必然维护着指针信息,能够通过 task_struct 找到进程的代码和数据!因为 linux 操作系统对进程的管理,本质上是对 task_struct 的管理。CPU 要执行进程的代码必须能通过 task_struct 找到进程的代码和数据!


fork 创建子进程的时候,操作系统首先为子进程创建 task_struct 结构体,并初始化结构体中的属性~但是,在初始化指向子进程代码和数据的指针的时候,应该怎么办呢?因为子进程并没有自己的代码和数据哇!那操作系统就说啦,子进程不是父进程创建的嘛,就让这个指针指向父进程的代码和数据吧!


于是,我们得出了一个重要的结论:fork 之后,父子进程的代码共享
在这里插入图片描述

父进程为什么要创建子进程,不就是想让子进程来帮忙的嘛!因此,为了让父子进程执行不同的代码,就需要通过 fork 不同的返回值来实现!

问题三:

一个变量怎么会有不同的内容?如何理解?

在任何操作系统中,进程在运行的时候具有独立性!
其实根据常识也能证明:你的电脑上同时运行着 QQ 和 微型这两个进程!突然 QQ 这个进程挂掉了!QQ 挂掉了会影响微信这个进程的运行嘛?显然是不会的!

在这里插入图片描述
在来看这张图,我们说 fork 之后,父子进程的代码和数据是共享的,我们又说进程之间是互相独立的!假设我们的子进程想要修改父进程中的数据怎么办呢?这种操作会被允许嘛?
我们先来写一个代码看看结论!

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int g_val = 100;int main()
{pid_t id = fork();if(id == 0) //子进程{int cnt = 0;while(1){printf("我是子进程, g_val = %d\n", g_val);cnt++;if(cnt == 3){printf("change g_val\n");g_val = 200;}sleep(1);}}else if(id > 0) //父进程{while(1){printf("我是父进程, g_val: %d\n", g_val);sleep(1);}}else // 子进程创建失败{perror("fork():");}return 0;
}

在上面的代码中我们定义了一个全局变量 g_val 在父子进程中每隔一秒打印 g_val 的值。在子进程中 3 秒之后将 g_val 修改了,我们观察父子进程打印 g_val 的结果有什么变化!
在这里插入图片描述
我们看到在子进程中,g_val 变成了 200,父进程中 g_val 还是 100。这是为什么呢?我们知道进程之间是具有独立性的!因为数据可能会被修改,这就注定了父子进程之间的数据是不能共享的
那怎么办呢?在创建子进程的时候将父进程的数据拷贝一份给子进程?这样做的确没有任何问题!但是如果子进程都不对父进程的数据做修改,这不就白白给子进程拷贝了一份数据嘛!造成内存负担


于是操作系统说:当子进程要修改父进程的数据时,我再给你子进程拷贝数据吧!这个行为被称为:父子进程数据层面的写时拷贝。当操作系统检测到子进程要修改父进程的数据时,会为子进程重新分配一块内存空间!


因为代码不可能被修改,父子进程代码共享并不影响进程之间的独立性!

问题四:

一个函数是如何做到返回两次的?怎么理解?

首先,fork 是一个函数,在这个函数中负责为调用他的进程创建子进程,这个函数体的实现一定包含但不限于以下操作:

  • 创建子进程的 task_struct
  • 填充 task_struct 的内容。
  • 父子进程指向相同的代码。
  • 修改子进程的状态。等等

fork 这个函数执行到 return 语句的时候,此时子进程一定已经被创建出来了!并且父子进程指向了相同的代码!而 return 本身也是代码哇!我们的代码:pid_t id = fork()return 的本质不就是在向 id 这个变量中写入吗 (return 返回时,先把返回值写到 cpu 中的寄存器中,最后再把寄存器中的值拷贝到你接收到的变量中!)?子进程此时要修改 id 中的内容,是不是就得发生写时拷贝!因此,同一个 id 变量会有两个不同的值。

问题五:

如果父子进程被创建好,谁先运行?

答案是:不清楚,谁先运行由调度器决定!

问题六:

同一个变量名存储不同的数据,如何做到?

这个问题仙子阿没打讲解,我们等到学习进程地址空间的时候再说吧!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法通关村】链表基础经典问题解析

【算法通关村】链表基础&经典问题解析 一.什么是链表 链表是一种通过指针将多个节点串联在一起的线性结构&#xff0c;每一个节点&#xff08;结点&#xff09;都由两部分组成&#xff0c;一个是数据域&#xff08;用来存储数据&#xff09;&#xff0c;一个是指针域&…

第一百八十六回 DropdownMenu组件

文章目录 1. 概念介绍2. 使用方法2.1 DropdownMenu2.1 DropdownMenuEntry 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何禁止页面跟随手机自动旋转"相关的内容&#xff0c;本章回中将介 绍DropdownMenu组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 …

python+pytest接口自动化(6)-请求参数格式的确定

我们在做接口测试之前&#xff0c;先需要根据接口文档或抓包接口数据&#xff0c;搞清楚被测接口的详细内容&#xff0c;其中就包含请求参数的编码格式&#xff0c;从而使用对应的参数格式发送请求。例如某个接口规定的请求主体的编码方式为 application/json&#xff0c;那么在…

Redis面试题:redis做为缓存,数据的持久化是怎么做的?两种持久化方式有什么区别呢?这两种方式,哪种恢复的比较快呢?

目录 面试官&#xff1a;redis做为缓存&#xff0c;数据的持久化是怎么做的&#xff1f; 面试官&#xff1a;这两种持久化方式有什么区别呢&#xff1f; 面试官&#xff1a;这两种方式&#xff0c;哪种恢复的比较快呢&#xff1f; 面试官&#xff1a;redis做为缓存&#xff…

基于YOLOv8深度学习的钢材表面缺陷检测系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

JVM虚拟机:JVM参数之标配参数

本文重点 本文我们将学习JVM中的标配参数 标配参数 从jdk刚开始就有的参数&#xff0c;比如&#xff1a; -version -help -showversion

Vivado版本控制

Vivado版本控制 如果您有幸进入FPGA领域&#xff0c;那么会遇到版本控制问题&#xff0c;本文讲解的是如何用git进行Vivado进行版本控制。 搭建Git环境 一 首先需要一个git环境&#xff0c;并选择一个托管平台&#xff08;github,gitlab,gitee&#xff09; Git下载地址&…

JavaSE自定义验证码图片生成器

设计项目的时候打算在原有的功能上补充验证码功能&#xff0c;在实现了邮箱验证码之后想着顺便把一个简单的图片验证码生成器也实现一下&#xff0c;用作分享。 注意&#xff0c;实际开发中验证码往往采用各种组件&#xff0c;通过导入依赖来在后端开发时使用相关功能&#xf…

9.ROS的TF坐标变换(三):坐标系关系查看与一个案例

1 查看目前的坐标系变化 我们先安装功能包&#xff1a; sudo apt install ros-melodic-tf2-tools安装成功&#xff01; 我们先启动上次的发布坐标变换的节点&#xff1a; liuhongweiliuhongwei-Legion-Y9000P-IRX8H:~/Desktop/final/my_catkin$ source devel/setup.bash liuho…

亚马逊云科技re:Invent大会,助力安全构建规模化生成式AI应用

2023亚马逊云科技re:Invent全球大会进入第三天&#xff0c;亚马逊云科技数据和人工智能副总裁Swami Sivasubramanian博士在周三的主题演讲中&#xff0c;为大家带来了关于亚马逊云科技生成式AI的最新能力、面向生成式AI时代的数据战略以及借助生成式AI应用提高生产效率的精彩分…

MMseqs2蛋白质序列快速高效比对工具

先看仓库&#xff1a;soedinglab/MMseqs2: MMseqs2: ultra fast and sensitive search and clustering suite (github.com) 无论哪个工具软件&#xff0c;无论你是否熟悉&#xff0c;都推荐你看一下作者原文&#xff0c;这样后面的步骤以及怎么使用头脑里会更清晰。 Fast an…

C语言-预处理与库

预处理、动态库、静态库 1. 声明与定义分离 一个源文件对应一个头文件 注意&#xff1a; 头文件名以 .h 作为后缀头文件名要与对应的原文件名 一致 例&#xff1a; 源文件&#xff1a;01_code.c #include <stdio.h> int num01 10; int num02 20; void add(int a, in…

国标GBT 27930关键点梳理

1、充电总流程 整个充电过程包括六个阶段:物理连接完成、低压辅助上电、充电握手阶段、充电参数配置阶段、充电阶段和充电结束阶段。 在各个阶段,充电机和 BMS 如果在规定的时间内没有收到对方报文或没有收到正确报文,即判定为超时(超时指在规定时间内没有收到对方的完整数据包…

PyLMKit(4):基于本地知识库的检索增强生成RAG

基于本地知识库的检索增强生成RAG 0.项目信息 日期&#xff1a; 2023-12-2作者&#xff1a;小知课题: RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种利用知识库检索的方法&#xff0c;提供与用户查询相关的内容&#xff0c;从而…

750mA Linear Charger with Power Path Management

一、General Description YHM2711 is a highly integrated, single-cell Li-ion battery charger with system power path management for space-limited portable applications. The full charger function features Trickle-charge, constant current fast charge and const…

Leetcode144. 二叉树的前序遍历-C语言

文章目录 题目介绍题目分析解题思路1.创建一个数组来储存二叉树节点的值2.根据二叉树的大小来开辟数组的大小3.边前序遍历边向创建的数组中存入二叉树节点的值 完整代码 题目介绍 题目分析 题目要求我们输出二叉树按前序遍历排列的每个节点的值。 解题思路 1.创建一个数组来…

TCA9548A I2C 多路复用器 Arduino 使用相同地址 I2C 设备

在本教程中&#xff0c;我们将学习如何将 TCA9548A I2C 多路复用器与 Arduino 结合使用。我们将讨论如何通过整合硬件解决方案来使用多个具有相同地址的 Arduino 的 I2C 设备。通过使用 TCA9548A I2C 多路复用器&#xff0c;我们将能够增加 Arduino 的 I2C 地址范围&#xff0c…

大模型的开源闭源

文章目录 开源&闭源开源和闭源的优劣势比较开源和闭源对大模型技术发展的影响开源与闭源的商业模式比较国内的大模型开源和闭源的现状和趋势 开源和闭源&#xff0c;两种截然不同的开发模式&#xff0c;对于大模型的发展有着重要影响。 开源让技术共享&#xff0c;吸引了众…

【LVS实战】04 LVS+Keepalived实现负载均衡高可用

一、介绍 Keepalived 是一个用于 Linux 平台的高可用性软件。它实现了虚拟路由器冗余协议 (VRRP) 和健康检查功能&#xff0c;可以用于确保在多台服务器之间提供服务的高可用性。Keepalived 可以检测服务器的故障&#xff0c;并在主服务器宕机时&#xff0c;自动将备份服务器提…

Golang数据类型(字符串)

字符串重要概念 根据Go语言官方的定义&#xff1a; In Go, a string is in effect a read-only slice of bytes. 意思是Go中的字符串是一组只读的字节切片&#xff08;slice of bytes&#xff09;&#xff0c;每个字符串都使用一个或多个字节表示&#xff08;当字符为 ASCII 码…