一个现有进程可以调用fork函数创建一个新进程。

#include <unistd.h>
pid_t fork( void );
返回值:子进程中返回0,父进程中返回子进程ID,出错返回-

由fork创建的新进程被称为子进程(child process)。fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。

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

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

Linux 2.4.22提供了另一种新进程创建函数——clone(2)系统调用。这是一种fork的泛型,它允许调用者控制哪些部分由父、子进程共享。

程序清单8-1中的程序演示了fork函数,从中可以看到子进程对变量所作的改变并不影响父进程中该变量的值。

程序清单8-1 fork函数示例

[root@localhost apue]# cat prog8-.c
#include "apue.h" int glob = ; /* external variable in initialized data */
char buf[] = "a write to stdout\n"; int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid; var = ;
if(write(STDOUT_FILENO, buf, sizeof(buf) - ) != sizeof(buf) -)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */ if((pid = fork()) < )
{
err_sys("fork error");
}
else if(pid == ) /* child */
{
glob++; /* modify variables */
var++;
}
else
{
sleep(); /* parent */
} printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit();
}

如果执行此程序则得到:

[root@localhost apue]# ./prog8-
a write to stdout
before fork
pid = , glob = , var = 子进程的变量值改变了
pid = , glob = , var = 父进程的变量值没有改变
[root@localhost apue]# ./prog8- > tmp.out
[root@localhost apue]# cat tmp.out
a write to stdout
before fork
pid = , glob = , var =
before fork
pid = , glob = , var =

一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。如果要求父、子进程之间相互同步,则要求某种形式的进程间通信。

当写到标准输出时,我们将buf长度减去1作为输出字节数,这是为了避免将终止null字节写出。strlen计算不包括终止null字节的字符串长度,而sizeof则计算包括终止null字节的缓冲区长度。两者之间的另一个差别是,使用strlen需进行一次函数调用,而对于sizeof而言,因为缓冲区已用已知字符串进行了初始化,其长度是固定的,所以sizeof在编译时计算缓冲区长度。

注意程序清单8-1中fork与I/O函数之间的交互关系。write函数是不带缓冲的。因为在fork之前调用write,所以其数据写到标准输出一次。但是标准I/O库是带缓冲的(这里用到了标准I/O库的printf函数)。如果标准输出连到终端设备,则它是行缓冲的,否则它是全缓冲的。当以交互方式运行该程序时(此时是行缓冲的),只得到该printf输出的行一次,其原因是标准输出缓冲区在fork之前已由换行符冲洗。但是当将标准输出重定向到一个文件时(此时是全缓冲的),却得到printf输出行两次。其原因是,在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓冲区中(我们没有用fflush冲洗缓冲区),然后在将父进程数据空间复制到子进程中时,该缓冲区也被复制到子进程中于是那时父、子进程各自有了带该行内容的标准I/O缓冲区。(子进程复制父进程缓冲区对程序的影响实例解析可参考:http://blog.csdn.net/lollipop_jin/article/details/8774057)在exit之前的第二个printf将其数据添加到现有的缓冲区中。当每个进程终止时,最终会冲洗其缓冲区中的副本

文件共享

对程序清单8-1需注意的另一点是:在重定向父进程的标准输出时,子进程的标准输出也被重定向。实际上,fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共享一个文件表项

考虑下述情况,一个进程具有三个不同的打开文件,它们是标准输入、标准输出和标准出错。在从fork返回时,我们有了如图8-1所示的结构。

这种共享文件的方式使父、子进程对同一文件使用了一个文件偏移量。如果父、子进程写到同一描述符文件,但又没有任何形式的同步(例如使父进程等待子进程),那么它们的输出就会相互混合(假定所有的描述符是在fork之前打开的)。

在fork之后处理文件描述符有两种常见的情况

(1)父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已执行了相应的更新。

(2)父、子进程各自执行不同的程序段。在这种情况下,在fork之后,父、子进程各自关闭它们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。

除了打开文件之外,父进程的很多其他属性也由子进程继承(可以理解为共享),包括:

  • 实际用户ID、实际组ID、有效用户ID、有效组ID。
  • 附加组ID。
  • 进程组ID。
  • 会话ID。
  • 控制终端。
  • 设置用户ID标志和设置组ID标志。
  • 当前工作目录。
  • 根目录。
  • 文件模式创建屏蔽字。
  • 信号屏蔽和安排。
  • 针对任一打开文件描述符的在执行时关闭(close-on-exec)标志。
  • 环境。
  • 连接的共享存储段。
  • 存储映射。
  • 资源限制。

父、子进程之间的区别是:

  • fork的返回值。
  • 进程ID不同。
  • 两个进程具有不同的父进程ID:子进程的父进程ID是创建它的进程的ID,而父进程的父进程ID则不变。
  • 子进程的tms_utime、tms_stime、tms_cutime已经tms_ustime均被设置为0.
  • 父进程设置的文件锁不会被子进程继承。
  • 子进程的未处理的闹钟(alarm)被清除。
  • 子进程的未处理信号集设置为空集。

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

fork有下面两种用法:

(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

某些操作系统将(2)中的两个操作(fork之后执行exec)组合成一个,并称其为spawn。UNIX将这两个操作分开,因为在很多场合需要单独使用fork,其后并不跟随exec。另外,将这两个操作分开,使得子进程在fork和exec之间可以更改自己的属性。例如I/O重定向、用户ID、信号安排等。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

进程控制之fork函数的更多相关文章

  1. 【Linux编程】进程标识符与fork函数

    ID为0的进程一般是调度进程.常被称为交换进程(swapper),是内核中的系统进程. ID为1的进程叫做init进程,是一个普通用户进程,不属于内核,由内核调用. 一个现有进程能够调用fork函数创 ...

  2. [Chapter 3 Process]Practice 3.1 相关知识:进程创建、fork函数

    3.1 Using the program shown in the Figure3.30, explain what the output will be at LINE A 答案:LINE A 处 ...

  3. Linux系统编程_8_进程控制之fork_wait_waitpid函数

    fork函数: #include <unistd.h>        pid_t fork(void); fork用来创建一个子进程: 特点: fork调用后会返回两次,子进程返回0,父进 ...

  4. 进程控制之exec函数

    用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序.当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行.因为调用exec并不创 ...

  5. UNIX环境编程学习笔记(19)——进程管理之fork 函数的深入学习

    lienhua342014-10-07 在“进程控制三部曲”中,我们学习到了 fork 是三部曲的第一部,用于创建一个新进程.但是关于 fork 的更深入的一些的东西我们还没有涉及到,例如,fork ...

  6. linux c编程:进程控制(三)_exec函数

    fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间.堆.栈等资源的副本.注意,子进程持有的是上述存储空间的“副本”,这意味着父子进 ...

  7. 进程控制之exit函数

    进程有下面5种正常终止方式: (1)在main函数内执行return语句.这等效于调用exit. (2)调用exit函数.此函数有ISO C定义,其操作包括调用各终止处理程序(终止处理程序在调用ate ...

  8. 进程管理之fork函数

    fork函数的定义 #include <unistd.h> #include <sys/types.h> pid_t fork(void); fork函数在父进程中返回子进程的 ...

  9. 2.1 进程控制之fork创建子进程

    fork()函数 目标:熟悉fork创建一个和多个子进程子线程 函数原型:pid_t fork(void); 返回值:成功返回:① 父进程返回子进程的ID(非负) ②子进程返回 0 : 失败返回-1. ...

随机推荐

  1. mongdb创建自增主键(primary key)的相关讨论 - Jason.Zhi

    根据mongodb官方文档介绍,如果在插入(insert)操作时,没有指定主键id,那么它会自动给插入行自动附上一个主键id.看起来不错,但是详细看看,就会发现这个id值有点复杂. 如下图: mong ...

  2. 演义江湖PC端意见汇总

    写在前面: 1.自己的游戏自己玩玩爽不爽,自己爽了才能说玩家可能会接受,自己都玩不下去玩家凭什么玩你的游戏 2.如果你负责美术,那么你到游戏中看看,你如果不能接受,玩家也会觉得游戏很丑 3.如果你负责 ...

  3. js_sl 无缝切换

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  4. 消除QQ表情小游戏

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  5. 第一百九十五天 how can I 坚持

    晚上回来又肚子疼,拉肚子,咋搞的呢. 小米.华为.感觉虽然现在华为有些许优势,哎,还是不说了,感觉小米手机信号好像有问题. 中午吃的刀削面好像不熟,其实,怎么说呢,像开面馆,做的面顾客都吃不完,很明显 ...

  6. Struts Hello World Example

    In this tutorial we show you how to develop a hello world web application using classic Struts 1.3 f ...

  7. java getEnv不区分大小写 getProperty区分大小写

    System.out.println(System.getenv("JAVA_HOME")); System.out.println(System.getenv("Pat ...

  8. Light oj 1005 - Rooks (找规律)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1005 纸上画一下,找了一下规律,Ank*Cnk. //#pragma comm ...

  9. 关于heritrix安装配置时出现”必须限制口令文件读取访问权限”的解决方法

    转载:http://www.floatinglife.cn/关于heritrix安装配置时出现必须限制口令文件读取访问 最近开始写一个RSS聚合程序,需要爬虫支持,于是就整来heritrix,没想到, ...

  10. 【OpenGL】glFinish()和glFlush()函数详解-[转]

    通常情况下,OpenGL指令不是立即执行的.它们首先被送到指令缓冲区,然后才被送到硬件执行.glFinish和glFlush都是强制将命令缓冲区的内容提交给硬件执行. 一.glFinish()函数   ...