Unix/Linux中的fork函数
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("-");这个语句,我们就可以很清楚的知道,哪个子进程复制了父进程标准输出缓中区里的的内容,而导致了多次输出了。如下图所示,就是阴影并双边框了那两个子进程:

其他可参考资料
本文参考资料
《UNIX环境高级编程 第二版》
Unix/Linux中的fork函数的更多相关文章
- [fork]Linux中的fork函数详解
---------------------------------------------------------------------------------------------------- ...
- 深入解析Linux中的fork函数
1.定义 #include <unistd.h> #include<sys/types.h> pid_t fork( void ); pid_t 是一个宏定义,其实质是int, ...
- linux中的fork()函数以及标准I/O缓冲
1. fork()创建的新进程成为子进程.一次调用,两次返回,子进程的返回值是0,而父进程的返回值是新子进程的进程ID,如果出现错误,fork返回一个负值. 2. 可以通过fork返回的值来判断当前进 ...
- linux中的fork函数的基本用法
代码: #include <iostream> #include <string> #include <cstdio> #include <unistd.h& ...
- Linux编程之fork函数
在Linux中,fork函数的功能就是在一个进程中创建一个新的进程,当前调用fork函数的进程就是产生的新进程的父进程,新进程在以下也称为子进程.在新进程生成之后就会在系统中开始执行. 函数原型:pi ...
- [转]unix/linux中的dup()系统调用
[转]unix/linux中的dup()系统调用 在linux纷繁复杂的内核代码中,sys_dup()的代码也许称得上是最简单的之一了,但是就是这么一个简单的系统调用,却成就了unix/linu ...
- 【Linux 进程】fork函数详解
一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...
- 浅析linux中的fork、vfork和clone
各种大神的混合,做个笔记. http://blog.sina.com.cn/s/blog_7598036901019fcg.html http://blog.csdn.net/kennyrose/ar ...
- Linux 环境下 fork 函数和 exec 函数族的使用
前言 接触 Linux 已经有几个月了,以前在网上看各路大神均表示 Windows 是最烂的开发平台,我总是不以为然,但是经过这段时间琢磨,确实觉得 Linux 开发给我带来不少的便利.下面总结一下学 ...
随机推荐
- Android的5层平台架构
Android 是一种基于 Linux 的开放源代码软件栈,为广泛的设备和机型而创建.下图所示为 Android 平台的主要组件. Android 软件栈 Linux 内核 Android 平台的基础 ...
- pycallgraph 追踪Python函数内部调用
安装 安装pycallgraph 安装依赖 使用 待测脚本 追踪脚本 追踪结果 高级篇 隐藏私密函数 控制最大追踪深度 总结 GitHub上好代码真的是太多了,名副其实的一个宝藏.但是最近自己也反思了 ...
- ejabberd mod_echo 解析
ejabberd mod_echo 解析(金庆的专栏 2016.8)按开发入门的说明,mod_echo是最简单的模块之一.https://docs.ejabberd.im/developer/当然 m ...
- 设子数组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)的辅助 ...
- TCP协议的性能评测工具 — Tcpdive开源啦
Github地址:https://github.com/fastos/tcpdive 为什么要开发Tcpdive 在过去的几年里,随着移动互联网的飞速发展,整个基础网络已经发生了翻天覆地的变化. 用户 ...
- svn(subversion)代码版本管理在linux下的一些常见使用命令
以下的操作都是默认你的服务器安装有svn的大前提下进行的. 一.创建版本库 我的版本库存放路径为: /var/svn : 下面我们来创建一个名为 svntet 的版本库 注释: svnadmin ...
- 21 viewPager--- hzScrollView ----llContainer
结构: MainActivity.java package com.qf.day21_hsviewpagerfragment_demo5; import java.util.ArrayList; im ...
- 指令汇B新闻客户端开发(四) 自动轮播条
在这个新闻客户端,我们可以看到有一个轮播页面,在这个项目中,用Handler和一个定时器来做更容易一些, 我们定义一个Handler: private Handler mHandler; 定时器的代码 ...
- java虚拟机 jvm java堆 方法区 java栈
java堆是java应用程序最密切的内存空间.几乎所有的对象都存在堆中.java堆完全自动化管理,通过垃圾回收机制,垃圾对象会自动清理,不需要显式释放. 根据java垃圾回收机制的不同,java堆可能 ...
- 2.1、Android Studio通过Lint提升你的代码
为了测试你的Android应用符合功能需求.最重要的是确保你的代码没有结构性问题.结构差的代码影响你的Android应用的可靠性,让你的代码难以维护.比如,如果你的XML资源文件包含未使用的明明空间, ...