文章目录

  • 一、fork是什么及使用方法
  • 二、fork使用实例及过程解析
  • 三、缓冲区问题
  • 四、fork使用实例
    • 实例1:或的情况
    • 实例2:与的情况
    • 实例3:循环嵌套中加了\n(没有if情况)
    • 实例4:循环嵌套中没有加\n(没有if情况)
    • 实例5:循环嵌套中加了\n(有if情况) 3A3B
    • 实例6:循环嵌套中没有加\n(有if情况) 4A4B
    • 实例7:缓冲区问题

一、fork是什么及使用方法

fork是什么:fork为创建进程的系统调用函数,用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。
 
       fork作用: fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己
 
       fork函数原型:pid_t fork( void);(pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中)

调用fork()函数需要包含的头文件:
    #include <sys/types.h>
    #include <unistd.h>

二、fork使用实例及过程解析

fork使用实例:
    如图所示:为一个使用fork来创建一个子进程的简单代码(本博客上所示代码均在红帽企业版Linux 6.3版本上操作)

那么在这段简单代码使用过程中发生了什么?
①在语句pid_t pid = fork();之前,只有一父进程在执行这段代码。printf输出:hahaha该字符串。
②但在执行到pid_t pid = fork();时,我们之前提到fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,相当于克隆了一个自己,两个进程同时在执行。一个为子进程,另一个为父进程。
③父进程fork返回新创建子进程的进程ID,一般为非0值,因此在if判断语句中,输出world。
④子进程fork返回0,在if判断语句中,输出hello。
其过程如图所示:

因此,最后结果输出如图:

三、缓冲区问题

学习这几个实例之前,我们还需要清楚一个东西,那就是缓冲区的问题。
缓冲区这个缓冲区既不是内核中的缓冲区,也不是用户分配的缓冲区,而是有编译器维护的用户进程空间中的缓冲区。分为全缓冲(大部分缓冲都是这类型)、行缓冲、无缓冲。
全缓冲:全缓冲指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。
行缓冲:在这种情况下,标准IO在输入和输出中遇到换行符时执行IO操作;注意,当流涉及终端的时候,通常使用的是行缓冲。
无缓冲:无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的
        printf默认是行缓冲,没有遇到换行符所以内容没有被冲出来。所以我们会是先延迟才打印。flush(stdout) 和\n 效果相同。

为什么要了解它,它有什么用?
       因为在Linux中存在着缓冲区的问题。当我们使用printf函数时,如果没有换行的话,它是不会输出的,而是先将要输出的内容存放再缓冲区中,当碰到换行时,将缓冲区中的内容再一起输出所以fork后子进程会复制父进程的缓冲区,因此也将待输出内容复制到自己“与父进程独立的缓冲区中”,因此当子进程执行遇到换行,就将缓冲区中的内容全都输出来。
下面是一个使用过程中的对比:
1.使用printf时没有加\n,如图:

我们来看看结果:

打印结果为两行:helloworld。因为printf不加\n是有输出缓冲区的,不使用\n时,数据不刷新。fork()创建子进程时,复制了父进程的数据段和堆栈段,包括上面的缓冲区,再执行后面的printf ,直到子进程结束输出打印,就打印了两遍hello world。

2.使用printf时添加\n,这次我们在hello的后面加上了world,如图:

我们来看看结果:

根据我们前面了解到:使用换行符\n后,会刷新我们的缓冲区,在执行到第一个printf时,会将hello输出,缓冲区数据被清洗。执行到fork时,fork从该处复制一个与父进程完成相同的子进程且前面的代码不会执行,父子进程都再执行最后一个printf,输出2个world。所以输出了1个hello,2个world。

四、fork使用实例

实例1:或的情况

int main()
{fork() || fork();printf("A\n");exit(0);
}

这段代码的输出结果为什么呢?
①首先我们来看,这段代码里有一个逻辑判断语句:||
对于A||B这种情况:
      A为1时,就不需要再判断B,则表达式值为1;
      A为0时,需要再判断B,如果B为1,则表达式值为0;
②明白了逻辑判断,我们来看:前面我们提到fork再子进程中会返回0,父进程中返回非0的子进程pid,因此程序在执行正确的情况下,当执行第一个fork时,产生进程1:父进程,它返回非0的子进程的pid,非0为真,不再继续判断;此时,由第一个fork产生了另外进程2:子进程,该进程返回0,需要再次执行第二个fork,于是产生进程3,所以输出3了次A。
打印结果为:AAA
测试结果如图所示:

为了更清楚看到执行过程,我们也可以可以采用一颗树来表示:

实例2:与的情况

int main()
{if(fork() && fork()){printf("A\n");}else{printf("B\n");}exit(0);
}

同理:这段代码里有一个逻辑判断语句:&&;对于A&&B这种情况:
      A为1时,继续判断B,如果都为1,则表达式为1,否则为0;
      A为0时,不在判断B,表达式为0;
其过程如图所示:
①父进程执行到fork时,产生一个子进程1,返回子进程1的pid(非0)为真;
②父进程继续执行第二个fork产生子进程2,返回子进程2的pid(非0)为真;因此,父进程中2个fork都为真,输出一个A。
③子进程1第一次调用fork后开始,它的fork()返回值为0,因此为假,子进程1输出一个B。
④子进程2第一次调用fork返回值也为0,因此为假,子进程输出一个B。
打印结果为:ABB

测试结果如图所示:

实例3:循环嵌套中加了\n(没有if情况)

int main()
{int i = 0;for (; i < 2; i++){fork();printf("A\n");}exit(0);
}

如图所示:
父进程会输出2个中划线(总计:2个)
①进程1会输出2个自己的A,无主进程缓冲区拷贝输出(总计:2个)
②进程2会输出1个自己的A,无主进程缓冲区拷贝输出(总计:1个)
③进程3会输出1个自己的A,无first进程缓冲区拷贝输出(总计:1个)
打印结果为:6个A。

测试结果如图所示:

实例4:循环嵌套中没有加\n(没有if情况)

int main()
{int i = 0;for (; i < 2; i++){fork();printf("A");}exit(0);
}

①父进程会输出2个中划线(总计:2个)
②进程1会输出2个自己的A,无主进程缓冲区拷贝输出(总计:2个)
③进程2会输出1个自己的A,考虑到注意要点2,会输出从父进程缓冲区拷贝过来的1个A(总计:2个)
④进程3会输出1个自己的A,考虑到注意要点2,会输出从进程1缓冲区拷贝过来的1个A (总计:2个)
打印结果为:8个A。

测试结果如图所示:

实例5:循环嵌套中加了\n(有if情况) 3A3B

int main()
{int i = 0;for (; i < 2; i++){if (fork()){printf("A\n");}else{printf("B\n");}}exit(0);
}

①i=0时,第一次调用fork生成子进程1,打印"B";此时生成的子进程和第一个父进程各自运行;i=1时,第二次调用fork,生成子进程2,打印"B",i=2程序退出。
②子进程1状态和父进程创建时一样为i=0,它的返回值是0为假,打印"A";子进程1继续运行,创建子进程3。相对于子进程3时子进程1为父进程,返回值为非0,为真打印"B",i=2时循环结束。
③子进程3为子进程1创建的子程序,与子进程1状态一致为i=1时,fork返回值为0,打印"A",i=2时循环结束。
④子进程2为父进程第二次调用fork时,生成的第二个子进程,状态和父进程一致为i=1时。返回值为0,打印"A"。i=2时循环结束。
打印结果为:3A3B(由于不同系统环境和进程调度算法顺序可能不一样)
其过程如图所示:

测试结果如图所示:

实例6:循环嵌套中没有加\n(有if情况) 4A4B

int main()
{int i = 0;for (; i < 2; i++){if (fork()){printf("A");}else{printf("B");}}exit(0);
}

结果存储在缓冲区,只有当缓冲区遇到\n才会将结果显示在界面上,否则就等程序运行结束或缓冲区满再显示到界面上。与实例4同理,会存在缓冲区,因此会有拷贝情况。
最后打印出来4A4B(顺序可能不一定)。

实例7:缓冲区问题

int main()
{printf("A");write(1, "B", 1);fork();exit(0);
}

①执行到printf时,printf()为库函数,没有\n时printf执行以后的A被放入缓冲区中,不能直接打到屏幕上。
②write为系统调用,代码中write往1上面写,而1是一个文件描述符:标准输出屏幕,因此B直接被打到屏幕上。
③再执行到fork,由于前面printf中无\n被放入缓冲区中,因此执行fork时,父进程的A会在子进程中再被打印一次。程序结束时,打印两个A,一个为父进程被放入缓冲区的A,一个为子进程从缓冲区fork来的A。所以,打印出来BAA。
测试结果如图所示:

参考链接:
关于fork()||fork();
操作系统—fork函数解析与例题详解

Linux:fork是什么、使用方法、缓冲区问题、frok使用实例相关推荐

  1. linux手动释放内存的方法

    Linux手动释放缓存的方法 Linux释放内存的命令: sync echo 1 > /proc/sys/vm/drop_caches drop_caches的值可以是0-3之间的数字,代表不同 ...

  2. linux+守护进程+php,【转载】Linux 守护进程的编程方法

    [转载]Linux 守护进程的编程方法 原文见: http://www.linuxdevelop.org/tingxx/show.php?table=c&id=3 Linux 守护进程的编程方 ...

  3. Linux手动释放缓存的方法

    Linux手动释放缓存的方法 1. 错误状态 2. 解决办法 1. 错误状态 这一年为什么文章少了呢,因为开发一直没停过,开发遇到的问题经常让人头大. 比如今天遇到个问题,启动一个服务去编译文件,直接 ...

  4. linux查看日志的多种方法集合

    linux查看日志的多种方法集合 Linux查看日志的命令有多种: tail.cat.tac.head.echo等 tail 参数 命令格式: tail[必要参数][选择参数][文件] -f 循环读取 ...

  5. Linux fork隐藏的开销-过时的fork(正传)

    本文来自<Linux fork那些隐藏的开销> fork是一个拥有50年历史的陈年系统调用,它是一个传奇!时至今日,它依旧灿烂. 一个程序员可以永远不用read/write,也可以不懂mm ...

  6. Unix/Linux fork/exec的前世今生

    本文是<Linux fork那些隐藏的开销>的前传<Unix/Linix fork前传>.转载注明来自公众号"Linux阅码场". 昨天(好像是上周的事了, ...

  7. Linux fork那些隐藏的开销

    fork是一个拥有50年历史的陈年系统调用,它是一个传奇!时至今日,它依旧灿烂. 一个程序员可以永远不用read/write,也可以不懂mmap,但必须懂fork.这是一种格调! fork没有参数,它 ...

  8. 嵌入式Linux kernel LOGO的更换方法

    http://blog.csdn.net/dong_zhihong/article/details/8651119 嵌入式Linux kernel LOGO的更换方法 标签: LINUX 2013-0 ...

  9. 清理linux服务器缓存,详解Linux手动释放缓存的方法

    详解Linux手动释放缓存的方法 发布时间:2020-08-20 07:53:27 来源:脚本之家 阅读:87 作者:闪电王国 栏目:服务器 Linux释放内存的命令: sync echo 1 > ...

  10. Linux下环境变量配置方法梳理(.bash_profile和.bashrc的区别)

    博客园 首页 新随笔 联系 管理 订阅 <div class="blogStats"><!--done--> 随笔- 556  文章- 38  评论- 77 ...

最新文章

  1. 【 全干货 】5 分钟带你看懂 Docker !
  2. JavaScript学习(八)
  3. I2C原理及特性总结
  4. github流程图_10月份Github上最热门的JavaScript开源项目
  5. 《算法设计与分析基础》Chapt 2 算法效率分析基础
  6. Winform 窗体淡出淡入效果
  7. 如何使用Elixir和Phoenix快速入门构建CRUD REST API
  8. Will not attempt to authenticate using SASL | dubbo项目启动特别慢,拉取 zookeeper 服务日志打印特别慢
  9. 1024x1024 分辨率,效果惊人!InsetGAN:全身图像生成 (CVPR 2022)
  10. Cesium学习系列汇总
  11. NB-IOT技术以及物联网安全问题简述
  12. Python实现代码行数统计工具
  13. 使用FFMPEG类库分离出多媒体文件中的H.264码流
  14. LeetCode详细题解-Java版
  15. python操作腾讯文档_Python操作Excel文档
  16. ableton 中文_Ableton live 中文 PDF.pdf
  17. ArcGIS Portal发布slpk失败,Error999999
  18. 高等数学笔记-苏德矿-第十章-曲线积分和曲面积分-第七节-高斯公式与斯托克斯公式
  19. 如何做好项目管理任务分配
  20. Kubespray安装kubernetes

热门文章

  1. 利用python-docx批量处理Word文件—图片
  2. Facebook专页粉丝增加需要考虑哪些问题
  3. 冥想于无限之上,融心智于无限
  4. Micro-Outlier Removal: 一种Kaggle快速提分的小技巧
  5. 迅雷/旋风地址转换原理分析(转)
  6. java学期总结_JavaWeb学期总结
  7. 0 csdn博客编辑教程目录
  8. 2023山东玉米加工展,助力乡村振兴·产业融合发展3月在济南召开
  9. NR 5G 边缘计算中关于UPF网元的分流技术
  10. 利用Fiddler给手机设置代理