fork函数介绍

  一个现有进程可以调用fork函数创建一个新进程。该函数定义如下:

#include <unistd.h>
pid_t fork(void);
// 返回:若成功则在子进程中返回0,在父进程中返回子进程ID,若出错则返回-1

  fork函数调用一次,返回两次。它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号;在子进程中返回一次,返回值为0。因此,返回值本身告知当前进程是子进程还是父进程。

  fork在子进程返回0而不是父进程的进程ID的原因在于:任何子进程只有一个父进程,而且子进程总是可以通过调用getppid取得父进程的进程ID。相反,父进程可以有很多子进程,而且无法获取各个子进程的进程ID。如果父进程想要跟踪所有子进程的进程ID,那么它必须记录每次调用fork的返回值。另外,进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0。

  子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。父、子进程并不共享这些存储空间部分。父、子进程共享代码段。

  由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全复制。作为替代,使用了写时复制(Copy-On-Write, COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统的一“页”。

  使fork失败的两个主要原因是:1)系统中已经有太多的进程(通常意味着某个方面出了问题);2)实际用户的进程总数超过了系统限制。

附:exec函数

  用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec函数只是用一个全新的程序替换了当前进程的正文、数据、堆和栈端。

  关于exec函数详细的请参考:

  APUE 8.10节

  进程控制

一个简单的例子

  简单示例程序如下:

 #include <unistd.h>
#include <stdio.h> int main()
{
pid_t fpid;
fpid = fork(); if(fpid < )
{
printf("error in fork!");
}
else if(fpid == )
{
printf("\n=================================================\n");
printf("I am the child process, my process ID is %d\n", getpid());
printf("My parent process ID is %d\n", getppid());
printf("=================================================\n");
}
else
{
printf("\n=================================================\n");
printf("I am the parent process, my process ID is %d\n", getpid());
printf("=================================================\n");
sleep();
} return ;
}

  程序的运行结果是:

  

  按照惯常,程序按顺序执行,最终输出应该只有if...else if...else中一个条件下的结果,但很明显我们这边输出了两个条件下的结果。具体原因在于通过fork函数创建的子进程也会复制父进程的存储空间(数据、堆、栈等,包括程序计数器),创建了属于自己的存储空间,并从fork函数后开始执行。利用pstree命令可以看到子进程(ID 18406)确实继承自父进程(ID 18405):

  

  一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。在上述程序中,父进程先执行,子进程在其之后执行。

关于fork面试题

题目及答案  

  下边的一道题摘自陈皓的博文

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

  请问上述程序会输出几个‘-’?6个还是8个?

  如果我们不考虑printf函数的缓存效果,程序的最终输出是6个‘-’。但因为printf函数有缓存的效用,最终导致输出了8个‘-’。具体原因可参照陈皓的博文

不考虑printf函数缓存效用

  为了去除printf函数缓存效用,我们稍微改动一下程序:

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

  这下输出就正确了。下边两图是程序输出结果和相应的进程(forktest3)树状图:

  

  

更直观的解析

  为了更直观,我们可以修改程序如下:

 #include <stdio.h>
#include <sys/types.h>
#include <unistd.h> int main()
{
int i;
for(i = ; i < ; i++)
{
fork();
printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i);
} sleep(); //让进程停留十秒,这样我们可以用pstree查看一下进程树
return ;
}

  输出结果如下:

  

  陈皓还给出了图示解释:

  

  上图中,相同颜色的是同一个进程。

  而对于printf("-");这个语句,我们就可以很清楚的知道,哪个子进程复制了父进程标准输出缓中区里的的内容,而导致了多次输出了。如下图所示,就是阴影并双边框了那两个子进程:

  

其他可参考资料

  Forking vs Threading

  孤儿进程与僵尸进程[总结]

  从一道面试题谈linux下fork的运行机制

  经典的 Fork 炸弹解析

本文参考资料

  《UNIX环境高级编程 第二版》

  一个fork的面试题

Unix/Linux中的fork函数的更多相关文章

  1. [fork]Linux中的fork函数详解

    ---------------------------------------------------------------------------------------------------- ...

  2. 深入解析Linux中的fork函数

    1.定义 #include <unistd.h> #include<sys/types.h> pid_t fork( void ); pid_t 是一个宏定义,其实质是int, ...

  3. linux中的fork()函数以及标准I/O缓冲

    1. fork()创建的新进程成为子进程.一次调用,两次返回,子进程的返回值是0,而父进程的返回值是新子进程的进程ID,如果出现错误,fork返回一个负值. 2. 可以通过fork返回的值来判断当前进 ...

  4. linux中的fork函数的基本用法

    代码: #include <iostream> #include <string> #include <cstdio> #include <unistd.h& ...

  5. Linux编程之fork函数

    在Linux中,fork函数的功能就是在一个进程中创建一个新的进程,当前调用fork函数的进程就是产生的新进程的父进程,新进程在以下也称为子进程.在新进程生成之后就会在系统中开始执行. 函数原型:pi ...

  6. [转]unix/linux中的dup()系统调用

    [转]unix/linux中的dup()系统调用    在linux纷繁复杂的内核代码中,sys_dup()的代码也许称得上是最简单的之一了,但是就是这么一个简单的系统调用,却成就了unix/linu ...

  7. 【Linux 进程】fork函数详解

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

  8. 浅析linux中的fork、vfork和clone

    各种大神的混合,做个笔记. http://blog.sina.com.cn/s/blog_7598036901019fcg.html http://blog.csdn.net/kennyrose/ar ...

  9. Linux 环境下 fork 函数和 exec 函数族的使用

    前言 接触 Linux 已经有几个月了,以前在网上看各路大神均表示 Windows 是最烂的开发平台,我总是不以为然,但是经过这段时间琢磨,确实觉得 Linux 开发给我带来不少的便利.下面总结一下学 ...

随机推荐

  1. Android的5层平台架构

    Android 是一种基于 Linux 的开放源代码软件栈,为广泛的设备和机型而创建.下图所示为 Android 平台的主要组件. Android 软件栈 Linux 内核 Android 平台的基础 ...

  2. pycallgraph 追踪Python函数内部调用

    安装 安装pycallgraph 安装依赖 使用 待测脚本 追踪脚本 追踪结果 高级篇 隐藏私密函数 控制最大追踪深度 总结 GitHub上好代码真的是太多了,名副其实的一个宝藏.但是最近自己也反思了 ...

  3. ejabberd mod_echo 解析

    ejabberd mod_echo 解析(金庆的专栏 2016.8)按开发入门的说明,mod_echo是最简单的模块之一.https://docs.ejabberd.im/developer/当然 m ...

  4. 设子数组A[0:k]和A[k+1:N-1]已排好序(0≤K≤N-1)。试设计一个合并这2个子数组为排好序的数组A[0:N-1]的算法。

    设子数组A[0:k]和A[k+1:N-1]已排好序(0≤K≤N-1).试设计一个合并这2个子数组为排好序的数组A[0:N-1]的算法.要求算法在最坏情况下所用的计算时间为O(N),只用到O(1)的辅助 ...

  5. TCP协议的性能评测工具 — Tcpdive开源啦

    Github地址:https://github.com/fastos/tcpdive 为什么要开发Tcpdive 在过去的几年里,随着移动互联网的飞速发展,整个基础网络已经发生了翻天覆地的变化. 用户 ...

  6. svn(subversion)代码版本管理在linux下的一些常见使用命令

    以下的操作都是默认你的服务器安装有svn的大前提下进行的. 一.创建版本库 我的版本库存放路径为: /var/svn : 下面我们来创建一个名为 svntet 的版本库    注释: svnadmin ...

  7. 21 viewPager--- hzScrollView ----llContainer

    结构: MainActivity.java package com.qf.day21_hsviewpagerfragment_demo5; import java.util.ArrayList; im ...

  8. 指令汇B新闻客户端开发(四) 自动轮播条

    在这个新闻客户端,我们可以看到有一个轮播页面,在这个项目中,用Handler和一个定时器来做更容易一些, 我们定义一个Handler: private Handler mHandler; 定时器的代码 ...

  9. java虚拟机 jvm java堆 方法区 java栈

    java堆是java应用程序最密切的内存空间.几乎所有的对象都存在堆中.java堆完全自动化管理,通过垃圾回收机制,垃圾对象会自动清理,不需要显式释放. 根据java垃圾回收机制的不同,java堆可能 ...

  10. 2.1、Android Studio通过Lint提升你的代码

    为了测试你的Android应用符合功能需求.最重要的是确保你的代码没有结构性问题.结构差的代码影响你的Android应用的可靠性,让你的代码难以维护.比如,如果你的XML资源文件包含未使用的明明空间, ...