首页 > 系统相关 >LInux: fork()究竟是如何工作的?为何一个变量能够接受两个返回值?

LInux: fork()究竟是如何工作的?为何一个变量能够接受两个返回值?

时间:2024-03-30 19:59:01浏览次数:18  
标签:fork 创建 代码 pid LInux 进程 返回值

LInux: fork函数究竟是如何工作的?为何一个变量能够接受两个返回值?

前言

 在Linux中,创建进程有两种方式:

  1. 在命令行中直接启动进程。例如在命令行中输入pwd、tar等命令后,操作系统会直接加载,运行相应的进程。
  2. 通过代码创建进程 —— fork()函数。

 在Linux中,查看进程最常用的两种方式:

  1. 使用ps axj 或 ps aux指令查看进程。
  2. 使用ls /proc指令查看当前已加载到内存中所有的进程。

一、fork()用法

 fork()是一个系统调用接口函数

在这里插入图片描述

 fork()的原型是pid_t fork(void),其中pid_t是一个整型int的别名
 使用fork(),系统会以当前进程为模板,创建子进程(创建子进程时,OS会将当前进程的大部分属性来初始化子进程,而对于子进程的pid、ppid等属性则是单独实现)。同时对于父进程,fork()函数会返回子进程的pid;而对于子进程来说,fork()函数直接返回0!并且文档中明确表示fork()函数创建进程通常是成功的,所有返回值为负数一般忽略。

二 、fork()应用实例展示

 下面我们在process.c中有这样一段代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int main()
{//getpid()、getppid()都是系统调用,分别返回当前进程的pid和ppid
     printf("我是一个进程,我的pid:%d, ppid:%d\n", getpid(), getppid());
	 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, ppid:%d\n", getpid(), getppid());
             sleep(1);
         }
     }
     else
     {//返回失败
     	return 1:
     }
     return 0;
}

 我们用Makefile、make对上述代码进行编译、运行看看会发生什么?

【动画展示】:
请添加图片描述

 我们发现fork()前的代码只执行了一次(一定是父进程执行),但fork()后的两个判断条件中的代码都执行了(即两个while循环)这也进一步说明了fork()后,操作系统会创建一个子进程!!

tips:

  • 上述展示视频中左边是一个监视进程的脚本,可以不断循环打印目标进程的相关属性信息。脚本如下:(如果需要监视其他进程信息,只需将脚本中的myprocess改成目标可执行程序即可!
while :; do ps axj | head -1 && ps axj | grep myprocess | grep -v "grep"; sleep 1; echo "#########################################################"; done

三、fork()工作原理

3.1 为什么要创建子进程?

父进程创建子进程通常是希望子进程来协助父进程来完成一些工作,这些工作是单进程无法实现的。

 例如:用户在下载某一款软件时,同时存在播放该款软件的官方宣传视频的需求。这就需要子进程来协助父进程来达到边下载边播放的目的。即通过一定手段(通常时fork()的返回值),让父子进程分别执行不同的代码!

3.2 fork()究竟干了些什么?

 fork()创建进程后,OS中会多一个进程(子进程)。在创建过程中,Linux操作系统会为子进程创建对应的PCB,并用父进程的大部分属性来初始化子进程的相关属性(如子进程的pid、ppid、所在路径等属性信息则是单独实现)。最后将该进程链入到运行队列中,等待CPU的调度!!

 而进程 = 代码 + 内核数据结构和数据,并且进程间时是相互独立的。而进程中的代码和数据等是操作系统在创建该进程时,从磁盘上加载拷贝到内存中的。但创建的子进程是直接在内存中创建的,子进程并没有相应的代码和数据,那要怎么解决这个问题呢?

 实际上,代码只是用于读取的。所以Linux中fork()创建的子进程和父进程共用同一段代码。但对于数据来说,父/子进程间必然会存在差异(比如pid、ppid等)。同时为了保证父/子进程间的独立性,必须在父/子进程中各自独立私有一份。而在Linux中采用了写时拷贝的方式来解决这个问题

 下一个问题就是为啥我们在前面动画展示中,fork()创建出的子进程只执行fork()后的代码?在fork()前的代码子进程能否“看见”呢?

 答案是子进程能看见fork()前的所有代码!至于为啥子进程只执行fork()后的代码,这是由于代码运行过程中,存在诸如epi、pc等寄存器。这些寄存器中会保存当前指令要执行的下一条指令的地址。而fork()创建子进程过程中,父进程中pc、epi等寄存器的结果也“继承”给了子进程。所以才出现子进程只运行执行fork()函数后的代码!

3.3 fork为什么会存在两个返回值?

 fork()是一个函数,其存在返回值。fork()在执行是,操作系统内核做了如下工作:分配新的内存块和数据结构给子进程、将父进程的部分内核数据结构拷贝给子进程、添加子进程到系统进程列表中,调度器开始调度。

fork()函数执行完后,已经完成了创建子进程、将子进程添加到调度队列中等工作。当父进程和子进程在调度队列中被调度时,两个进程都需要执行return语句,都需要返回一个值!所以fork存在两个返回值!(操作系统通过寄存器来实现返回值返回两次)(真正原因其实在于地址空间的实现)

3.5 为何fork函数中父进程返回子进程的pid、子进程返回0?

 对于一个进程,其父进程是唯一确定的,但其子进程可能存在多个。就像我们生活中,一个孩子的爹是唯一确定的;但对于一个父亲,其可能存在多个孩子。
 而父进程和子进程之间是管理和被管理的关系。父进程为了更好的管理好子进程,所以fork函数在创建子进程后返回子进程pid;对于子进程来说,其只需管理好自身即可,所以返回0。

3.5 父进程和子进程谁先运行?

 我们已经看到了fork()函数会创建一个子进程。创建完子进程后,子进程会被加载链接到系统进程列表中等待CPU调度运行。
 至于父进程和子进程谁先被CPU调度是不确定的。进程被调度的先后顺序由各自的PCB中的调度信息(时间片 + 优先级等)+ 调度器算法确定。换句话说,父进程和子进程谁先运行是由操作系统决定的!

3.6 为何同一个变量接收两个返回值

 我们前面已经提到过了进程是相互独立的,为例保存fork()创建出的子进程和父进程之间的独立性,我们所采用的解决办法是:代码共享,数据采用写时拷贝的方式在父进程和子进程中各自维护一份。
 我们知道在执行pid_t id = fork();语句时,本质上是将fork()的返回值写入变量id。而变量id是父进程创建的,而fork()返回时发生了写时拷贝,所以同一个变量会有两个返回值!

标签:fork,创建,代码,pid,LInux,进程,返回值
From: https://blog.csdn.net/Zhenyu_Coder/article/details/136673091

相关文章

  • linux CentOS 7.9 安装 ffmpeg 6.0 教程【亲测成功】
    查看当前系统版本[[email protected]]#lsb_release-aLSBVersion::core-4.1-amd64:core-4.1-noarchDistributorID:CentOSDescription:CentOSLinuxrelease7.9.2009(Core)Release:7.9.2009Codename:Corewgethttp://www.ffmpeg.org/......
  • Acunetix v24.3 (Linux, Windows) - Web 应用程序安全测试
    Acunetixv24.3(Linux,Windows)-Web应用程序安全测试Acunetix|WebApplicationSecurityScanner请访问原文链接:https://sysin.org/blog/acunetix/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org重要提示AcunetixPremium现在使用日历化版本命名。请注意,从......
  • linux进程状态
    目录1.进程状态分类1.1进程状态查看  2.Z(zombie)-僵尸进程 2.1pid 2.2 僵尸进程危害 3. 孤儿进程4.理论4.1运行态 4.2阻塞态(S,D)4.3挂起态 4.4进程的切换1.进程状态分类CPU内存在很多寄存器看看下面的状态在kernel源代码里定义:/**The......
  • Linux服务器安装openJdk8
    环境说明linux系统版本:lsb_release-a  不同的操作系统以及软件版本,可能会遇到不一样的问题,一定要注意版本问题。 .1.查看服务器是否已经安装JDK。  创建目录mkdiropenJdk8&&mkdirmaven  .2.下载JDK,版本参照自己本地的版本,至少要大于等于1.8.121 ......
  • Linux服务器安装Maven
    环境说明linux系统版本:lsb_release-a  JDK版本:1.8  不同的操作系统以及软件版本,可能会遇到不一样的问题,一定要注意版本问题。 .1.Maven版本选择。自己本地使用的事3.9.6,因此服务器同样用此版本,如果有问题在更改版本。  按照如下操作找到Maven的下载地址。......
  • Linux常用快捷键
    必用必记1、命令或路径的补全键Tab使用:命令补全;文件名或者路径补全连续按2次Tab键,显示以已输入字符开头的所有命令、文件名或路径2、光标回到命令首行ctrl+a3、光标回到命令行尾ctrl+e4、中断终端正在执行的任务或者删除整行ctrl+c使用:结束目前正在运行的程......
  • Linux使用PulseAudio录取声音
    PulseAudio介绍PulseAudio是一个音频服务器,它充当了你的应用程序和硬件设备之间的中间件。简单来说就是你可以调用api,获取到系统捕获到的声音,可以录音。安装PulseAudiosudoaptinstalllibpulse-dev#不过一般都安装好了,Linux系统上使用有两种api,一种是简单的,同步的,Simp......
  • linux正则表达式之*
    1.*含义linux正则表达式*表示重复0个或多个前一个重复字符2.样例正则表达式*样例命令:grep-n"min*"anaconda-ks.cfg#找出含有mi、min、minn等字符串的行。注:因为*可以是0个,所以mi也是符合搜索字符串,另外,因为*为重复前一个字符的符号,因此,在*之前必须要紧挨着一个重复字......
  • linux 通过nvm安装node.js
    我的博客原文:linux通过nvm安装node前言 nvm是一个node版本控制的工具,他可以查看可以安装的node版本,安装node,以及切换node版本,传统的node安装,我们是下载压缩包,然后指定环境变量,当我们需要升级node的时候,需要重新下载node压缩包,更新或者回退版本显得有些麻烦,而使用nvm可以安......
  • FFmpeg开发笔记(九)Linux交叉编译Android的x265库
    ​《FFmpeg开发实战:从零基础到短视频上线》一书的“12.1.2 交叉编译Android需要的so库”介绍了如何在Windows环境交叉编译Android所需FFmpeg的so库,前文又介绍了如何在Linux环境交叉编译Android所需FFmpeg的so库,接下来介绍如何在Linux环境交叉编译Android所需x265的so库。1、安......