fork函数讲解及代码分析

2023-11-02

fork()函数

fork()的基础知识

  1. 父进程通过调用fork函数来创建一个新的运行的子进程。
  2. 父进程和子进程之间最大的区别就是PID不同
    1)在父进程中,fork返回新创建子进程的PID
    2)在子进程中,fork返回0
    3)如果出现错误,fork返回一个负值

fork()的特点

  • 调用一次,返回两次
    一次只在调用进程(父进程)中,fork返回子进程的PID。
    一次是在新创建的子进程中,fork返回0。

  • 并发执行
    父进程和子进程是并发运行的独立进程。
    内核能够以任意方式交替执行他们的逻辑控制流中的指令。

  • 相同但是独立的地址空间
    父进程和子进程会有相同的用户栈、相同的本地变量值、相同的堆、全局变量以及代码。
    但是,父进程和子进程都是独立的进程,他们都有自己的私有地址空间。

  • 共享文件
    子进程可以读写父进程中打开的任何文件

有关fork()代码分析

在看代码之前我们要了解一下有关 进程图 的知识:

  • 进程图是刻画程序语句偏序的一种简单的前驱图,每个顶点a对应于一条程序语句的执行
    有向边a—>b代表a发生在语句b之前,边上可以标记一些信息,例如一个变量的当前值。

  • 画出 进程图 方便我们理解fork调用程序的情况

1.让我们看几个最基本的嵌套循环的例子

void fork1()
{
   int x = 1;
   pid_t pid = fork();

   if (pid == 0) {
   printf("Child has x = %d\n", ++x);
   } 
   else {
   printf("Parent has x = %d\n", --x);
   }
   printf("Bye from process %d with x = %d\n", getpid(), x);
}

运行结果和流程图:
在这里插入图片描述

void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

运行结果和流程图:
画的真丑,我要死了
关于输出结果的顺序

对于运行在单处理器上的程序,对应所有原点的拓扑排序表示程序中语句的一个可行全序排列。
排列是 拓扑排序 ,当且仅当画出的每条边的方向都是从左到右的。

例如:

拓扑为:
->x=a
->x=b->x=c

那么输出:
abc,bac,bca都是有可能的

2.调用exit()和return时不同的输出

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void doit()
{
    if(fork()==0){
        fork();
        printf("hello\n");
        exit(0);
    }
    return ;
}
 
int main()
{
    doid();
    printf("hello\n");
    exit(0);
}

运行结果和流程图:
在这里插入图片描述

输出了3个hello,但是当我们doit函数中的 exit 更改为 return 时,让我们再来看下运行结果:
在这里插入图片描述
输出了5个hello,比之前多了2个,为啥会出现这种情况呢?
原因就是:
使用exit函数就结束了doit函数中创建的进程,而没有返回到main函数中,也就执行不了main函数中的printf语句。
而使用return则是退出if语句,返回到main函数中,故doit函数中创建的进程也会继续执行main函数中的printf语句。

3.使用waitpid函数

在介绍waitpid函数之前,我们先来了解一下有关 回收子进程 的相关知识

回收子进程
  • 当一个进程由于某种原因终止时,内核并不是立即把他从系统中清除。相反,进程被保持一种已终止状态中,直到被他的父进程回收。一个终止了但还未被回收的进程称为僵死进程
  • 即使僵死子进程没有运行,他们仍然消耗系统的内存资源。
  • 如果父进程没有回收它的僵死进程就终止了,那么内核会安排init进程去回收他们。
  • 但是,一些长时间运行的程序,例如shell或服务器,总应该回收他们的僵死子进程,不然会对系统造成不小的负担。

所以,如何使我们的父进程来等待它的子进程终止或者停止,来将僵死子进程所占用的内存资源释放掉呢?

这时就需要我们的waitpid函数登场了,首先我们先来看下他的函数结构

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *statusp, int options);

我们这里仅对第一个参数pid进行解释,另外两个请大家自行百度

  • 如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid。
  • 如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。
  • 我们可以通过控制pid的值来指定子进程进行回收,这也是与wait函数的区别。大家可以自行运行一下代码中的fork10fork11观察下输出有何不同。(代码会在文章最后给出)

我们来看一个代码实例

void fork9()
{
    int child_status;

    if (fork() == 0) {
	printf("HC: hello from child\n");
    exit(0);
    } else {
	printf("HP: hello from parent\n");
	waitpid(-1, &child_status, 0);//等价于wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}

运行结果和流程图:

在这里插入图片描述
因为我们在父进程中加入waitpid函数,所以父进程会等待子进程结束再继续运行,所以输出结果只能是HP;HC;CT;Bye或是HC;HP;CT;Bye
突然发现fflush函数忘记说,好像还挺重要的,以后再说吧。

既然说到waitpid 函数就简单讲一下wait函数
wait函数其实就是waitpid函数的简单版本:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *statusp);

调用wait(&status)等价于调用waitpid(-1, &status, 0)。

4.使用fflush清除缓存

我们先来看一下fflush的函数结构

#include<stdio.h>
int fflush(FILE * stream);

函数说明:

  • fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中,如果参数stream为NULL,fflush()会将所有打开的文件数据更新。
    返回值:成功返回0,失败返回EOF,错误代码存于errno中。
  • fflush()也可用于标准输入(stdin)和标准输出(stdout),用来清空标准输入输出缓冲区
    stdin是standard input的缩写,即标准输入,一般是指键盘;标准输入缓冲区即是用来暂存从键盘输入的内容的缓冲区。
    stdout是standard output 的缩写,即标准输出,一般是指显示器;标准输出缓冲区即是用来暂存将要显示的内容的缓冲区。

我们在fork函数中要注意的也就是输入输出缓冲区

来,让我们看一个简单的嵌套循环的代码实例

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    int i;
    for (i=0; i<2; ++i) {
        fork();
        printf("A");
        //fflush(stdout);
        //printf("\n");
    }
    return 0;
}

输出结果:
在这里插入图片描述
嗯?不应该是6个A吗,为什么这里多了两个A?
让我们看一下流程图:在这里插入图片描述
这里多出两个A的原因就是:

printf为标准输出(STDOUT),是有数据缓冲区的,遇到\n就认为一句完成即输出。
当fork()创建子进程时,子进程复制了父进程的数据段和堆栈段,包括printf的数据缓冲区也被复制。

所以fork()之后,printf打印的A放在缓存区中,等i=1时在fork()缓存区中的值被复制到两个子进程中了,就多了两次A打印。
所以流程图应该是这样:
在这里插入图片描述
所以我们可以使用 fflush(stdout) 或者加入 \n 来使缓冲区的数据输出,这样就可以得到我们想要的6个A的结果了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

fork函数讲解及代码分析 的相关文章

  • 等待所有 pid 在 php 中退出

    我的问题是这样的 我正在分叉一个进程 以便可以加快磁盘上文件的访问时间 我将这些文件中的所有数据存储在本地桌面上的 tmp 文件中 理想情况下 在所有进程完成后 我需要访问该 tmp 文件并将该数据放入数组中 然后我取消链接 tmp 文件
  • 使用 sleep 和 wait -n 在 bash 中实现简单的超时,是否存在竞争条件?

    如果我在 bash 脚本中执行此操作 sleep 10 sleep pid some command wait n cmd pid if kill 0 sleep pid 2 gt dev null then all ok kill sle
  • 用C语言创建进程树

    我将如何创建一个看起来像深度为 N 的平衡三元树的流程层次结构 意味着每个进程有 3 个子进程 因此深度 N 的树中将有 3 N 1 2 个进程 要创建新进程 我只想使用 fork 这是我到目前为止所拥有的 但我认为它不起作用 因为我不处理
  • unix fork exec 序列真的像听起来那么昂贵吗?

    我正在读关于fork and exec对于考试 我的书说 每当需要在 UNIX 系统中运行一个新的 不同的 进程时 您都会分叉当前进程 然后是execve 然而 它也说 每当fork被调用时 父进程的整个内存映像被复制到新进程 那么我的问题
  • 与 fork() 共享堆内存

    我正在努力用 C 语言实现一个数据库服务器 它将处理来自多个客户端的请求 我在用fork 处理单个客户端的连接 服务器将数据存储在堆中 堆由指向动态分配记录的哈希表的根指针组成 记录是具有指向各种数据类型的指针的结构 我希望进程能够共享这些
  • 防止 fork() 复制套接字

    我有以下情况 伪代码 function f pid fork if pid 0 exec to another long running executable no communication needed to that process
  • 为什么fork后关闭文件描述符会影响子进程?

    我想通过单击按钮在linux中运行程序 因此我编写了一个函数execute void execute const char program call const char param pid t child vfork if child 0
  • fork 后调试子进程(配置了 follow-fork-mode 子进程)

    我正在开发一个应用程序 父级分叉子级来处理某些任务 我遇到一个问题 我已将 gdb 配置为 follow fork mode 子级 但在 fork 后 到达断点后 它发送 SIGTRAP 但子级以某种方式终止并向父级发送 SIGCHLD 我
  • signal(SIGCHLD, SIG_DFL); 是什么意思?意思是?

    我不处理SIGCHLD在我的代码中 我的进程仍然在终止后立即被删除 我希望它成为僵尸进程 如果我设置SIGCHLD to SIG DFL那么 它会起作用吗 我该如何设置SIGCHLD to SIG DFL 我希望进程成为僵尸 这样我就可以在
  • 管道和流程管理

    我正在开发一个用 C 实现的小型 shell tsh 这是一项作业 作业的一部分属于 PIPING 我必须将一个命令的输出通过管道传输到另一个命令 例如 ls l sort 当我运行 shell 时 我在其上执行的每个命令都由它生成的子进程
  • 没有 fflush(stdout) 则输出不打印

    我不明白为什么有时我需要使用fflush 有时不是 我的程序目前出现段错误 我正在使用 print 语句对其进行调试 当程序出现段错误时 stdout不自动刷新缓冲区 我不明白为什么有时需要使用 fflush 而有时需要使用 不是 有时 s
  • 如何获取子进程的返回值?

    程序计算从 1 到 N 的数字之和 子进程计算偶数之和 父进程计算奇数之和 我想在父进程中获取子进程的返回值 我怎么做 include
  • 多处理:为什么与子进程共享 numpy 数组,而复制列表?

    我用过这个script https stackoverflow com questions 13121790 using multiprocessing manager list instead of a real list makes t
  • 让子进程等待直到收到父进程的信号

    我想从父级创建 N 个子级 我希望所有的孩子同时开始 一个功能 测量时间 因此 我将该函数放入信号处理程序中 当父级完成创建 分叉 所有子级时 它会向所有子级发送信号 使用kill children id 以让 make 开始 代码如下 但
  • bash fork 炸弹的另一个版本是如何工作的?

    我大致了解如何这个通用版本 https stackoverflow com questions 991142 how does this bash fork bomb work bash fork 炸弹的工作原理 然而 我见过另一个版本 特
  • 解释这段代码的工作原理;子进程如何返回值以及在哪里返回值?

    我不明白子进程如何返回该值以及返回给谁 输出为 6 7 问题来源 http www cs utexas edu mwalfish classes s11 cs372h hw sol1 html http www cs utexas edu
  • 无法在 github 上的特定分支上生成 git no such file or directory

    问题出在这个 fork repo 上 https github com RubenWillems CCNet https github com RubenWillems CCNet 我可以在同一台笔记本电脑上安装其他叉子 但不能使用此叉子
  • cat/Xargs/命令 VS for/bash/命令

    Linux 101 Hacks 一书的第 38 页建议 cat url list txt xargs wget c 我通常这样做 for i in cat url list txt do wget c i done 除了长度之外 还有什么东
  • C中的pipe()和fork()

    我需要创建两个子进程 一个子进程需要运行命令 ls al 并将其输出重定向到下一个子进程的输入 而下一个子进程又将对其输入数据运行命令 sort r n k 5 最后 父进程需要读取该数据 已排序的数据 并将其显示在终端中 终端中的最终结果
  • fork系统调用的应用

    fork 用于创建调用它的进程的副本 接下来通常是调用 exec 系列函数 除了这个之外 fork还有其他用途吗 我能想到一个 用管道函数做IPC 是的当然 启动一个进程 进行一些数据初始化 然后生成多个工作进程是很常见的 它们的地址空间中

随机推荐

  • Java内存泄漏的排查总结

    一 内存溢出和内存泄露 一种通俗的说法 1 内存溢出 你申请了10个字节的空间 但是你在这个空间写入11或以上字节的数据 出现溢出 2 内存泄漏 你用new申请了一块内存 后来很长时间都不再使用了 按理应该释放 但是因为一直被某个或某些实例
  • Syntax Error while loading YAML.   did not find expected '-' indicator

    运行剧本出错 ERROR Syntax Error while loading YAML did not find expected indicator The error appears to have been in etc ansib
  • C语言-哈希查找(HASH)-详解(完整代码)

    目录 原理 实例解释 存储逻辑图 需要的知识 附加 完整代码 代码详解 执行结果 1 查找个不存在的 2 查找个存在的 原理 用一个指针数组 来存储 每个链表的头节点 的首地址 如果要从 NUM 个数中查找数据 先对 NUM 0 75 求得
  • 【c++primer第五版】第十一章习题答案

    第十一章 关联容器 练习11 1 描述map和vector的不同 解 顺序容器中的元素是 顺序 存储的 对于vector 元素在其中按顺序存储 每个元素都有唯一对应 的位置编号 所有操作都是按编号进行的 例如 获取元素 头 尾 下标 插入删
  • 继承QDialog的类弹框不阻塞

    继承QDialog的类 如myDialog 在myDialog构造函数设置模态如下 this gt setWindowModality Qt ApplicationModal 使用如下 myDialog pMyDlg new myDialo
  • MariaDB(mysql的替代品)

    原文地址 http www csdn net article 2013 04 25 2815043 lookout oracle they tem up Oracle于09年收购了Sun 其中必不可少的原因就是获得MySQL这个最热门开源D
  • 【计算机网络系列】网络层⑩:路由选择协议——外部网关协议BGP

    外部网关协议BGP 协议BGP的主要特点 在外部网关协议 或边界网关协议 BGP中 现在使用的是第4个版本BGP 4 常简写为BGP 协议BGP对互联网非常重要 前面介绍的路由选择协议RIP和OSPF 都只能在一个自治系统AS内部工作 因此
  • leetcode刷题:三数之和

    题目 分析 这是最容易想到的做法 但是有明显的问题 时间复杂度达到0 n3 并且没有去重 class Solution public vector
  • 华为OD机试 - 模拟消息队列(Python)

    题目描述 让我们来模拟一个消息队列的运作 有一个发布者和若干消费者 发布者会在给定的时刻向消息队列发送消息 若此时消息队列有消费者订阅 这个消息会被发送到订阅的消费者中优先级最高 输入中消费者按优先级升序排列 的一个 若此时没有订阅的消费者
  • asp服务器 首选精图数码稳定,架设游戏服务端选什么云服务器

    架设游戏服务端选什么 DDOS是游戏 1 网络层攻击 YNFlood ACKFlood ICMPFlood UDPFlood NTPFlood SSDPFlood DNSFlood等 2 应用层攻击 CC攻击 3 HTTP的攻击等网络攻击
  • 如何要求用户的密码必须符合一定的复杂度

    如何要求用户的密码必须符合一定的复杂度 我们在使用 linux 系统 设置密码的时候 经常遇到这样的问题 系统提示 您的密码太简单 或者您的密码是字典的一部分 那么系统是如何实现对用户的密码的复杂度的检查的呢 系统对密码的控制是有两部分 我
  • 一种用QT实现即时通信软件表情发送与接收的思路

    一种用QT实现即时通信软件表情发送与接收的思路 最近需要使用QT为项目添加一个表情包发送与接受的功能 虽然之前知道表情发送与接收显示的一个基本原理 但是其中涉及到例如表情包插入到QTextEdit如何显示 如何保证从文本框发送出去的是表情编
  • Android Studio中创建java项目

    转自 https www cnblogs com jpfss p 9875402 html 1 创建普通的android工程 2 创建一个module 3 module类型选择java library 4 填写libary和class的名字
  • Sality病毒分析

    Sality病毒分析 基本信息 MD5 E100C2C3F93CABF695256362E7DE4443 样本来源 https www 52pojie cn thread 537381 1 1 html 样本报告 微步在线云沙箱 threa
  • 位运算符(一):C/C++位运算符

    位运算是指按二进制进行的运算 在程序中 常常需要处理二进制位的问题 C C 语言提供了6个位操作运算符 这些运算符只能用于整型操作数 即只能用于带符号或无符号的char short int与long类型 在实际应用中 建议用unsigned
  • C语言实现顺时针螺旋的顺序输出矩阵中元素

    给定一个 m 行 n 列的矩阵 请按照顺时针螺旋的顺序输出矩阵中所有的元素 从 0 0 位置开始 具体请参见下图 include
  • c++运算符、继承、多继承中构造函数和析构函数顺序、内部类和局部类

    一 自增自减运算符 单目运算符 正号 负号 开始代码 include
  • 万向肖风最新演讲:区块链应用模式的终极猜想

    互联网的应用解放了消费者 区块链的应用解放了开发者 解放开发者就是解放创业者 本文谨代表作者个人观点 不代表火星财经立场 该内容旨在传递更多市场信息 不构成任何投资建议 火星财经APP 微信 hxcj24h 一线消息 10月27日 由万向区
  • 5G时代三兄弟,NB-IoT到底有多牛逼

    5G时代的诱惑 犹如隔壁家厨房的气味 间歇性地飘过 刺激着大家的神经 然而对于工业而言 这个气味的信号实在是太微弱了 在2020年以前 5G的大规模应用 大家都不抱希望 没有设备制造商会认为5G能够迅速布置下去 即使是相关标准进展神速 6
  • fork函数讲解及代码分析

    fork 函数 fork 的基础知识 父进程通过调用fork函数来创建一个新的运行的子进程 父进程和子进程之间最大的区别就是PID不同 1 在父进程中 fork返回新创建子进程的PID 2 在子进程中 fork返回0 3 如果出现错误 fo