转:浅析fork()和底层实现

Linux system call的来龙去脉浅析 Linux system call的来龙去脉 Application通常都是调用一个C library中一个library库函数,该库函数最后又会通ARM的swi assemble instruction。SWI Instruction执行后,ARM进入Supervisor Mode,之后Linux Kernel会进行一系列的处理,最后调用我们在Linux Kernel中看到那些sys_XXX 阅读详情

浅析fork()和底层实现

阅读目录

记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎。不过,“黄天不负留心人"。哈~ 终于在学习进程和进程创建fork相关知识后,总算是大致摸清了其中的来龙去脉。废话不多讲,下面来谈谈本人的一点小小积累

 

 一个现有的进程可以调用fork函数创建一个新进程。原型如下:

  1. #include<unistd.h>  
  2.   
  3. pid_t fork(void);  
  4.   
  5. 返回值:自进程中返回0,父进程返回进程id,出错返回-1  


fork()系统调用会通过复制一个现有进程来创建一个全新的进程. 进程被存放在一个叫做任务队列的双向循环链表当中.链表当中的每一项都是类型为task_struct成为进程描述符的结构.也就是我们写过的进程PCB. 

 

小知识:内核通过一个位置的进程标识值或PID来标识每一个进程.同时其最大值默认为32768,short int短整型的最大值. 他就是系统中允许同时存在的进程最大的数目.可以去linux下的proc目录中寻找一个 pid_max的文件,并打开它加以验证. 如

 

 

 

点击回顶部

fork()运行时做的事情


首先我们来看一段代码,不过这里会有一点奇怪的现象:

 

复制代码

  1 /*************************************************************************
  2   > File Name: 1.c
  3   > Author: tp
  4   > Mail: 
  5   > Created Time: Mon 07 May 2018 07:57:28 PM CST
  6  ************************************************************************/
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <unistd.h>
 11 int main( void)
 12 {
 13     printf("change world!\n");
 14     pid_t pid = fork();
 15     if( pid == -1) {perror("fork"),exit(1); }
 16 
 17     printf( "pid=%d, returnVal=%d\n", getpid(), pid);
 18     sleep( 1);
 19     exit(0);
 20 }
~     

复制代码

 


这段代码的运行结果,大家如果像我当时不了解fork的时候,一定会以为输出结果是两个"change world!",然后2个printf里面的内容. 因为

 

我们复制出来了两个一模一样的进程,那么他们就应该做同样的事情. But!!! 我们看运行结果:

 

 

结果并非我们想的那样,这个时候我们就需要知道fork出子进程之后,程序的运行细节。可以来一张图帮助我们理解:

 

 

 

 

一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的.这取决于内核所使用的调度算法.如果要求父,子进程之间相互同步.则要求某种形式的进程间通信. 好了我们继续,当进程调用fork后,当控制转移到内核中的fork代码后,内核会做4件事情:

 

  1.分配新的内存块和内核数据结构给子进程

 

  2.将父进程部分数据结构内容(数据空间,堆栈等)拷贝至子进程

 

  3.添加子进程到系统进程列表当中

 

  4.fork返回,开始调度器调度

 

 

为什么fork成功调用后返回两个值? 

 

由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。所以fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值不同

其中父进程返回子进程pid,这是由于一个进程可以有多个子进程,但是却没有一个函数可以让一个进程来获得这些子进程id,那谈何给别人你创建出来的进程。而子进程返回0,这是由于子进程可以调用getppid获得其父进程进程ID,但这个父进程ID却不可能为0,因为进程ID0总是有内核交换进程所用,故返回0就可代表正常返回了。

 

 

从fork函数开始以后的代码父子共享,既父进程要执行这段代码,子进程也要执行这段代码.(子进程获得父进程数据空间,堆和栈的副本. 但是父子进程并不共享这些存储空间部分. (即父,子进程共享代码段.)。现在很多实现并不执行一个父进程数据段,堆和栈的完全复制. 而是采用写时拷贝技术(不懂可以戳进去看一看).这些区域有父子进程共享,而且内核地他们的访问权限改为只读的.如果父子进程中任一个试图修改这些区域,则内核值为修改区域的那块内存制作一个副本, 也就是如果你不修改我们一起用,你修改了之后对于修改的那部分内容我们分开各用个的.

 

 

 

 

点击回顶部

父子进程文件共享问题


 

来看个例子

复制代码

  1 /*************************************************************************
  2   > File Name: 2.c
  3   > Author: tp
  4   > Mail: 
  5   > Created Time: Mon 07 May 2018 12:40:39 PM CST
  6  ************************************************************************/
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <unistd.h>
 11 #include <fcntl.h>
 12 
 13 int set = 110;
 14 int main( void)
 15 {
 16     printf( "before fork\n");
 17     pid_t pid = fork( );
 18     if( pid < 0){ perror(" fork"),exit( 1);}
 19 
 20     if( pid == 0)
 21     {
 22         ++set;
 23         printf( "son pid=%d, %d\n", getpid(), set);
 24     }
 25     else
 26     {
 27         sleep( 1);
 28         printf( "parent pid=%d , %d\n", getpid( ), set);
 29     }
 30     exit( 0);
 31 }

复制代码

看一下结果:

不难注意到  before fork”这句话只是被打印了一次,这个从上面的例子,这不难理解;与此同时子进程中的set的值被改变了。此时再进行一个重定向操作会发生什么

 

 

出现很神奇的现象! 这个时候打印了出了两次“before fork”,不仅仅是如此,上述针对父进程的标准输出执行重定向操作还导致了子进程也执行重定向操作。

透过现象看本质,来细细分析一下。针对打印两次“before fork”,首先,先要知道标准IO库是是带缓冲的,而像printf这种直接输出到标准输出时,这个缓冲区是由换行符刷新的;而当执行了重定向操作,这里就是将标准输出重定向到文件,文件就不会立即去刷新缓冲区(全缓冲的方式);好,由于在fork之前调用了一次printf,但fork之后,该行数据仍存留在缓冲区中,然后父进程数据空间被复制到子进程中,该行数据去也被复制了过去,这样父子进程都各自带有该行内容的缓冲区了,相当于子进程缓冲区添加了一行“before fork”,然后在每个进程exit之后,每个缓冲区的内容就被写到了相应的文件中。

  再一个就是,在重定向父进程的标准输出时,子进程标准输出也被重定向。这就源于父子进程会共享所有的打开文件。 因为fork的特性就是将父进程所有打开文件描述符复制到子进程中。当父进程的标准输出被重定向,子进程本是写到标准输出的时候,此时自然也改写到那个对应的地方;与此同时,在父进程等待子进程执行时,子进程被改写到文件show.out中,然后又更新了与父进程共享的该文件的偏移量;那么在子进程终止后,父进程也写到show.out中,同时其输出还会追加在子进程所写数据之后,这也就解释了上面为什么“before fork”会在一个文件中打印两次。

 

 

在fork之后处理文件描述符一般又以下两种情况:

  1.父进程等待子进程完成。此种情况,父进程无需对其描述符作任何处理。当子进程终止后,它曾进行过读,写操作的任一共享描述符的文件偏移已发生改变。

  2.父子进程各自执行不同的程序段。这样fork之后,父进程和子进程各自关闭它们不再使用的文件描述符,这样就避免干扰对方使用的文件描述符了。这类似于网络服务进程。

 

同时父子进程也是有区别的:它们不仅仅是两个返回值不同;它们各自的父进程也不同,父进程的父进程是ID不变的;还有子进程不继承父进程设置的文件锁,子进程未处理的信号集会设置为空集等不同

 

 

点击回顶部

fork()函数在底层中做了什么?


 

  linux平台通过clone()系统调用实现fork(). fork(),vfork()和clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用

do_fork(). 再然后do_fork()完成了创建中的大部分工作,他定义在kernel/fork.c当中.该函数调用copy_process(). 然后重点来了,我们看看这个

copy_process函数到底做了那些事情?? 我画一张图帮我们理解:

 

 

 

 

点击回顶部

vfork和fork的之间的比较:


vfork()的诞生是在fork()还没有写时拷贝的时候,因为那个时候创建一个子进程的成本太大了,如果一下子创建好多了那么程序的效率一定会下降. 然后就有人提出了vfork(). vfork的实现原理非常简单,就是子进程,父进程完全公用一个资源. 就是是有人修改了内容,甚至main()函数退出了也不会新开辟一个空间. 所以这里里会有问题的,如果你的一个子进程没有使用exit()退出,那么程序就会出现段错误. 不相信可以去试一试~ 

 

为什么会出现段错误? 

 

  在函数栈上面,子进程运行结束了,main的函数栈被子进程释放了,然后父进程在使用的时候,就访问不到了,一旦vfork出子进程,退出的时候需要使用exit来结束.

 

 

vfork和fork之间的区别:

 

1.fork父子进程交替运行,vfork保证子进程先运行,父进程阻塞,直到子进程结束(或子进程调用了exec或exit).

 

2.fork实现了写时拷贝. 而vfork直接让父子进程共用公用资源,避免多开辟空间拷贝,

 

3,vfork必须使用exit或者excl退出.

 

4.就算是fork使用了写时拷贝,也没有vfork性能高.

 

5.每个系统上的vfork都有问题,推荐不要使用.

 

 

Esh!

分类: linux

HALCON 20.11实战:5步搞定工业图像异常检测(附完整代码) 本文提供了一份基于HALCON 20.11的工业图像异常检测实战指南。通过五个核心步骤——环境准备、数据预处理、模型训练、推理解析与工程化部署,详细演示了如何利用深度学习技术,仅使用正常样本快速构建并优化金属表面划痕等缺陷的检测模型,并附有完整可运行的HDevelop代码,助力工程师实现高效、智能的工业视觉质检。 阅读详情

相关推荐

【OpenClaw从入门到精通】第05篇:实战!OpenClaw自动化搞定邮件/日历/文件管理(2026实测避坑版)

本文聚焦OpenClaw核心实用场景,手把手教你实现邮件、日历、文件管理的全自动化。通过安装三大核心技能,配置IMAP/SMTP邮箱、跨平台日历及文件沙盒权限,结合4个虚拟实战案例,演示从邮件自动摘要、会议冲突预警到下载文件夹分类的完整流程。文中包含2026实测的命令代码、配置步骤、排障方案,所有案例需在测试环境验证,代码未上传GitHub。兼顾新手入门与进阶需求,让OpenClaw从“能聊天”升级为“能干活”,帮你节省80%重复工作时间。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 2579

浅析fork()底层实现

记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎。不过,“黄天不负留心人"。哈~ 终于在学习进程进程创建fork相关知识后,总算是大致摸清了其中的来龙去脉。废话不多讲,下面来谈谈本人的一点小小积累 一个现有的进程可以调用fork函数创建一个新进程。原型如下: #include<unistd.h> pid_t...

weixin_30468137的博客 191

基于MATLABANSYS的悬臂梁拓扑优化.rar_ansys梁_penmk3_悬臂梁ANSYS_悬臂梁matlab_拓扑

分别对matlabansys两种平台的拓扑优化结果分析比较,为学习者提供了两种不同优化设计思路,过程详细清晰,易于学习理解

fork()函数的使用与底层原理

fork()函数的使用与底层原理 在我第一次接触到fork函数的时候,那个时候我在牛客网刷题然后呢碰到一个关于fork()的函数的问题.总是没有办法理解那个printf() 为什么输出那 么 多次.终于通过学习完进程的创建明白了fork()的时候到底发生了什么事情. 下面我就来谈一谈我的一点小小的积累. 首 先我们来认识fork()的运用. #include<unistd.h> pid_t fork(void); 返回值:自进程中返回0,父进程返回进程id,出错返回-1 fork()系统调

weixin_42094764的博客 545

验证:fork之后父子进程之间堆不共享

 输出如下: 由此可见,指针位置不发生变化,但是已不在一个存储空间了 ,子进程改变指针所指变量不会对父进程的值造成影响。

znHD的博客 1134

linux中fork() 函数详解

fork入门知识 一个进程,包括代码、数据分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了...

weixin_33804990的博客 2873

fork()函数详解

目录 1.基本了解: 2.fork函数的了解: 3.僵死进程: 1.基本了解: 一个进程,包括代码、数据分配给进程的资源。fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。 为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值: 1)在父进程中,fork返回新创建子进程的进程...

weixin_57133901的博客 4万+

linux 的fork函数原型,浅析fork()底层实现

记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎。不过,“黄天不负留心人"。哈~ 终于在学习进程进程创建fork相关知识后,总算是大致摸清了其中的来龙去脉。废话不多讲,下面来谈谈本人的一点小小积累一个现有的进程可以调用fork函数创建一个新进程。原型如下:#includepid_t fork(void);返回值:自进程中返回0,父进程返回进程id,出错返...

weixin_33819646的博客 421

forkvfork的区别

前文中我们已经介绍了父子进程进行fork时的读时共享 写时复制操作( Linux 进程共享(读时共享 写时复制)是什么.)。现在我们进一步介绍一下vfork并且介绍fork与vfork的区别。 1. 引入vfork 在了解过fork的读时共享 写时复制操作后我们知道,复制出的子进程拥有自己的虚拟空间,但是物理空间是与父进程共享的,直到父子进程对数据进行更改时,再为子进程相应的段分配物理空间。 vfork则直接共享了父进程的虚拟空间,当然了,这种做法就顺水推舟的共享了父进程的物理空间。 vfork系统调用不同

年年年的博客 886

Linux进程控制:从fork、exec到进程间通信的全面解析

进程是操作系统进行资源分配调度的基本单位,理解进程控制是系统编程运维的核心基础。其原理涉及进程的创建、执行、等待终止等生命周期管理,通过系统调用如forkexec实现。在技术价值上,进程控制不仅是程序运行的基石,更是实现并发处理、服务守护、作业调度资源隔离的关键。在实际应用场景中,无论是编写Shell脚本进行批量任务管理,还是开发高性能服务器程序,都需要深入掌握进程创建、进程间通信(IPC)资源回收等机制。本文聚焦Linux进程控制,详细解析fork的写时复制机制、exec的进程映像替换,以及管

weixin_34152820的博客 403

深入Linux进程管理:从fork/exec原理到生产环境实战调试

在操作系统领域,进程是程序执行的基本单元,是系统资源分配调度的核心对象。其本质是操作系统对正在运行程序的一种抽象,每个进程都拥有独立的地址空间、数据栈程序计数器。Linux系统通过fork()系统调用创建新进程,采用写时复制(Copy-On-Write)技术优化内存使用,再通过exec()族函数加载新程序代码,实现高效的进程创建机制。这项技术价值在于,它使得系统能够并发执行多个任务,实现资源隔离与安全控制,是现代服务器高并发、容器化部署的基石。在实际应用场景中,无论是排查服务器高负载问题,还是调试后台服

weixin_34414650的博客 355

PM2源码浅析

PM2工作原理 最近在玩一个游戏,《地平线:黎明时分》,最终Boss是一名叫黑底斯的人,所谓为人,也许不对,黑底斯是一段强大的毁灭进程,破坏了盖娅主进程,从而引发的整个大陆机械兽劣化故事。 为...

QDY5945的博客 217

clone系统调用浅析

本文深入分析了Linux内核的clone系统调用机制。在用户态层面,通过glibc的__clone3函数触发系统调用,但内核并不直接使用用户态传入的入口函数地址,而是通过汇编指令在子线程中调用目标函数。内核态处理流程涉及kernel_clone、copy_process架构相关的copy_thread函数,通过设置cpu_context结构实现线程上下文切换。文章还探讨了线程栈管理、寄存器上下文保存与恢复机制,并对比了x86aarch64架构的实现差异,揭示了用户态线程与内核线程创建的底层原理。

Aruko的博客 2131

测试v_fork以及关闭标准输出后输出结果

#include "apue.h" #include int glob = 6; /* * 测试v_fork的例子,v_fork创建的子进程, 在父进程的空间里面执行, 而v_fork的父进程, 则会等待子进程运行完毕后执行。 * 而fork创建的子进程,则会复制你进程的空间, 分别在各自空间运行,互不影响, 而父子进程谁先运行完,不确定。 * */ int main(void) {

u010875004的专栏 901

fork函数详解_fork()(1),分享PDF高清版

假设我们的main进程pid是1001,注意看左边的1,2,4进程其实都是main进程1001。这个语句的意思是定义了一个pid_t类型的变量pid,fork()函数返回一个进程号,这个进程号赋给了pid。两个判断的代码都执行了,这是非常不可思议的,但fork函数确实实现了这样的功能。第三步: 父进程的代码执行完了,程序又会把fork后面的函数再执行一遍,此时pid的值变为0,所以进入判断2。***注意:**这里的pid_t类似一个类型,就像int型一样,int型定义的变量都是。

m0_60606468的博客 553

【Linux】fork()函数详解 (深入浅出 实例讲解)

文章目录@[toc]一、fork入门知识二、fork进阶知识三、fork高阶知识参考资料 一、fork入门知识          一个进程,包括代码、数据分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。        &nbsp

Sunnyside_ 1万+

fork()函数详解+示例分析(手工画图+代码图解分析)

来看不亏!!! fork()函数是通过系统调用 创建一个与原来进程几乎完全相同的进程,两个进程独立存在且可以同时运行(并发),**调用一次,返回两次!**如果初始参数或者传入的变量不同,两个进程也可以做不同的指令。简单点说,即fork()函数一旦被执行,则该程序就会有两条路可以同时走,先走哪条是不确定的555在举例子之前,先来了解一下(process id)当前进程的id,程序在父节点处pid>0,在子节点处pid=0,程序出错发生异常时pid=-1。

柯桔的博客 3911

Linux——fork()函数

一、fork()函数 一个进程,包括代码、数据分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 复制进程必须由操作系统支持。 ...

qq_44824574的博客 4232

fork()函数详细介绍及C语言实例

fork()函数详细介绍及C语言实例

深耕嵌入式领用多年, 致力于分享嵌入式领域技术! 2436
上一篇: markdown grammar
下一篇: 转:多线程程序中fork导致的一些问题
nedwons
博客等级 码龄9年 311粉丝 572原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值