fork的基本知识

函数原型:pid_t fork( void);
   返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID。

如果想知道具体实现和介绍,戳这里->进程创建

注意要点:

1、 子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
此处先简要介绍下COW(Copy-on-write)机制,大致原理如下:

在复制一个对象的时候并不是真正的把原先的对象复制到内存的另外一个位置上,而是在新对象的内存映射表中设置一个指针,指向源对象的位置,并把那块内存的Copy-On-Write位设置为1.这样,在对新的对象执行读操作的时候,内存数据不发生任何变动,直接执行读操作;

而在对新的对象执行写操作时,将真正的对象复制到新的内存地址中,并修改新对象的内存映射表指向这个新的位置,并在新的内存位置上执行写操作。

linux内核下fork使用COW机制工作原理

进程0(父进程)创建进程1(子进程)后,进程0和进程1同时使用着共享代码区内相同的代码和数据内存页面, 只是执行代码不在一处,因此他们也同时使用着相同的用户堆栈区。在为进程1(子进程)复制其父进程(进程0)的页目录和页表项时,进程0的640KB页表项的属性没有改动过(仍然可读写),但是进程1的640KB对应的页表项却被设置成只读。因此当进程1(子进程)开始执行时,对用户堆栈的入栈操作将导致页面写保护异常,从而使得内核的内存管理程序为进程1在主内存区中分配一内存页面,并把进程0中的页面内容复制到新的页面上。从此时开始,进程1开始有自己独立的内存页面,
由于此时的内存页面在主内存区,因此进程1中继续创建新的子进程时也可以采用COW技术。

内核调度进程运行时次序是随机的,进程0创建进程1后,可能先于进程1修改共享区,进程0是可读写的,在未分开前,进程1是只读的,由于两个进程共享内存空间,
为了不出现冲突问题,就必须要求进程0在进程1执行堆栈操作(进程1的堆栈操作会导致页面保护异常,从而使得进程1在主内存区得到新的用户页面区,此时进程1和进程0才算是真正独立,
如前面所述)之前禁止使用用户堆栈区。所以进程0在执行了fork(创建了进程1)之后的pause使用内嵌的方式,保证进程0(主进程)不会弄乱堆栈。

fork()后立即执行exec(),地址空间就无需被复制了,一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的 数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

fork()的实际开销就是复制父进程的页表以及给子进程创建一个进程描述符。在一般情况下,进程创建后都为马上运行一个可执行的文件,采用COW这种优化,
可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。

2、在linux中存在缓冲区的问题。当写printf函数,但是没有换行的话,它是不会输出的,而是先将要输出的内容存放再缓冲区中,当碰到换行时,将缓冲区中的内容再一起输出。

所以fork后子进程会复制父进程的缓冲区,因此也将待输出内容复制到自己“与父进程独立的缓冲区中”,因此当子进程执行遇到换行,就将缓冲区中的内容全都输出来。

代码1:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>  int main(){  int i;  pid_t pid;  for(i = 0; i < 2; ++i){  printf("-");  pid = fork();  }  return 0;
}  

输出:- - - - - - - -
8个中划线

分析:
原因分析:如果不清楚fork的深拷贝机制,估计看到上述代码的第一感觉是:死循环,永远fork下去。一定要仔细理解上面的注意要点1。在整个执行周期内,一共会产生4个进程,由于是遍历所以主进程会产生两个子进程(假设i=0时fork出的子进程记为:first,i=1时fork出的子进程记为:second),first进程在遍历时会再产生一个自己的子进程,记录为:third,而second进程,i的值已经为1,不会再继续执行下去,其它进程同理,所以此处不会形成死循环。

主进程会输出2个中划线(总计:2个)
first进程会输出1个自己的中划线,考虑到注意要点2,还会输出从主进程缓冲区拷贝过来的1个中划线(总计:2个)
second进程没有自己的中划线输出,考虑到注意要点2,会输出从主进程缓冲区拷贝过来的2个中划线(总计:2个)
third进程没有自己的中划线输出,考虑到注意要点2,会输出从first进程缓冲区拷贝过来的2个中划线 (总计:2个)

所以一共会输出8个,怎么确定会产生4个进程呢,只要把进程的id号给打印出来,就非常清晰明了了,具体代码参考代码5

代码2:

int main(){  int i;  pid_t pid;  for(i = 0; i < 2; ++i){  pid = fork();  printf("-");  }  return 0;
} 

输出:——–
8个中划线

解析:虽然同代码1一样,也是输出8个中划线,但是内部运行机制相差很远,具体原因分析参考代码1原因分析

主进程会输出2个中划线(总计:2个)
first进程会输出2个自己的中划线,无主进程缓冲区拷贝输出(总计:2个)
second进程会输出1个自己的中划线,考虑到注意要点2,会输出从主进程缓冲区拷贝过来的1个中划线(总计:2个)
third进程会输出1个自己的中划线,考虑到注意要点2,会输出从first进程缓冲区拷贝过来的1个中划线 (总计:2个)

代码3:

int main(){  int i;  pid_t pid;  for(i = 0; i < 2; ++i){  printf("-\n");  pid = fork();  }  return 0;
}  

输出:
-
-
-
3个中划线

解析:具体原因分析参考代码1原因分析

主进程会输出2个中划线(总计:2个)
first进程会输出1个自己的中划线,无主进程程缓冲区拷贝输出(总计:1个)
second进程没有自己的中划线输出,无主进程缓冲区拷贝输出(总计:0个)
third进程会没有自己的中划线输出,无first进程缓冲区拷贝输出(总计:0个)

代码4:

int main(){  int i;  pid_t pid;  for(i = 0; i < 2; ++i){  pid = fork();  printf("-\n");  }  return 0;
}  
输出:
-
-
-
-
-
-
6个中划线  

解析:具体原因分析参考代码1原因分析

主进程会输出2个中划线(总计:2个)
first进程会输出2个自己的中划线,无主进程缓冲区拷贝输出(总计:2个)
second进程会输出1个自己的中划线,无主进程缓冲区拷贝输出(总计:1个)
third进程会输出1个自己的中划线,无first进程缓冲区拷贝输出(总计:1个)

代码5:

int main(){  int i;  pid_t pid;  for(i = 0; i < 2; ++i){  pid = fork();  if(pid == 0)  printf("child process:%d", getpid());  else if(pid > 0)  printf("parent process:%d", getpid());  else  printf("error");  }  printf("\n");  return 0;
}

输出:
parent process:5141parent process:5141
child process:5142parent process:5142
child process:5142child process:5144

操作系统---fork函数解析与例题详解相关推荐

  1. php解析torrent文件,PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解

    本文实例讲述了PHP基于闭包思想实现的torrent文件解析工具.分享给大家供大家参考,具体如下: PHP对静态词法域的支持有点奇怪,内部匿名函数必须在参数列表后面加上use关键字,显式的说明想要使用 ...

  2. 分治算法小结(附例题详解)

    分治算法小结(附例题详解) 我的理解: 分治算法我的理解就是看人下菜碟,我们要解决的问题就好像一群人构成的集体,要我们解决这个问题,那我们就要满足这群人里面每个人不同的需求,也就是写出解决的代码,把每 ...

  3. php解析bt,PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解

    PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解 发布于 2017-09-08 20:05:36 | 124 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP( ...

  4. 字符串拷贝函数:strcpy的详解及模拟实现

    字符串拷贝函数:strcpy的详解及模拟实现!!! 对于字符串拷贝函数,之前在学习字符串时候,就已经学习过,但那只是片面的学习了一下,并没有经过系统的分析!只是大概的学习了一下!在关键的地方有时候还不 ...

  5. C语言(函数指针数组)详解

    要了解函数指针数组,可以从三个角度来分析.所谓函数指针数组,从字面意思上来解析,函数指针数组的组成有三个点,函数,指针,数组.首先我们知道,函数指针数组,是一个数组,数组的每个元素是函数指针,也就是一 ...

  6. Python bs4解析库使用详解

    今天继续给大家介绍Python 爬虫相关知识,本文主要内容是Python bs4解析库使用详解. 一.Python bs4库简介与安装 bs4是Python的一个第三方库,主要用于从HTML或者是XM ...

  7. 互斥量、条件变量与pthread_cond_wait()函数的使用,详解(二)

    互斥量.条件变量与pthread_cond_wait()函数的使用,详解(二) 1.Linux"线程" 进程与线程之间是有区别的,不过linux内核只提供了轻量进程的支持,未实现线 ...

  8. 在python中使用关键字define定义函数_python自定义函数def的应用详解

    这里是三岁,来和大家唠唠自定义函数,这一个神奇的东西,带大家白话玩转自定义函数 自定义函数,编程里面的精髓! def 自定义函数的必要函数:def 使用方法:def 函数名(参数1,参数2,参数-): ...

  9. 函数assert()详解

    函数assert()详解: 断言assert是一个宏,该宏在<assert>中,,当使用assert时候,给他个参数,即一个判读为真的表达式.预处理器产生测试该断言的代码,如果断言不为真, ...

最新文章

  1. JDBC:Java世界中的ODBC
  2. mujava 软件测试实验报告
  3. jQuery面试题-区别mouseover和mouseenter的不同之处(看了也许对你有好处)
  4. 【pmcaff】一个微信创业者的吐血总结,这样才能做好营销,实用!
  5. 你的元器件为什么会无缘无故地失效了?
  6. Entity Framework在WCF中序列化的问题(转)
  7. mvc:annotation-driven/浅析
  8. 机器学习:分类(Classification)算法
  9. ELK收集日志到mysql
  10. 单基因gsea_JTO:日本Smoker基因组综合分析
  11. 软考中级-数据库系统工程师复习知识点汇总
  12. 独家对话行癫:最详解密阿里云顶层设计和底层逻辑
  13. 手把手写C++服务器(0):专栏文章-汇总导航【持续更新】
  14. 网闸标底--网神G6150-C022
  15. chrome浏览器解除网页右键点击屏蔽方法
  16. 另一只眼看软件研发效能提升,软件研发效能的“人性”与“物性”
  17. android10和11,安卓10与安卓11究竟差异在哪里?我们拿这两台新机试了一下
  18. ADF单位根检验方法
  19. C# NPIO导出Excel设置宽度背景色
  20. POJO/DTO/DO/EO/VO/BO/PO/AO的含义和使用

热门文章

  1. 网易云音乐开发--通过网易云音乐api,游客登录页面验证效果实现
  2. 大数据周会-本周学习内容总结012
  3. 用完美主义的标准开始做一件事
  4. ArcGIS教程之DEM(高程)的应用(坡度坡向、提等高线)
  5. python之界面案例
  6. 三参数或七参数计算工具使用帮助
  7. VM15.5虚拟机安装openwrt系统作为旁路由
  8. 一、任务调度中心构建示例
  9. FEBE恢复Firefox配置方法[Z]
  10. Lattice Planner从学习到放弃(二):二次规划的应用与调试