fork函数的学习
1、介绍
父进程可以通过调用fork函数创建一一个新的运行的子进程。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
//返回:子进程返回0,父进程返回子进程的PID,如果出错,则为-1。
新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork 时,子进程可以读写父,进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
fork函数是有趣的(也常常令人迷惑),因为它只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的PID。在子进程中,fork返回0。 因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
代码框1 展示了一个使用fork 创建子进程的父进程的示例。当fork调用在第6行返回时,在父进程和子进程中x的值都为1。子进程在第8行加一-并输出它的x的副本。相似地,父进程在第13行减- -并输出它的x的副本。
int main()
{
pid_t pid;
int x=1;
pid = Fork() ;
if(pid == 0){
/*Child*/
printf("child : x=%d\n", ++x) ;
exit(0) ;
}
/* Parent */
printf ("parent: x=%d\n", --x);
exit(0) ;
}
代码框1
当在Unix系统上运行这个程序时,我们得到代码框2中的结果:
linux> . /fork
parent: x=0
child : x=2
代码框2
这个简单的例子有一些微妙的方面。
●调用一次,返回两次。fork 函数被父进程调用一次,但是却返回两次——一次是返回到父进程,一次是返回到新创建的子进程。对于只创建一个子进程的程序来说,这还是相当简单直接的。但是具有多个fork 实例的程序可能就会令人迷惑,需要仔细地推敲了。
●并发执行。父进程和子进程是并发运行的独立进程。内核能够以任意方式交替执行它们的逻辑控制流中的指令。在我们的系统上运行这个程序时,父进程先完成它的printf语句,然后是子进程。然而,在另一个系统上可能正好相反。一般而言,作为程序员,我们决不能对不同进程中指令的交替执行做任何假设。
●相同但是独立的地址空间。如果能够在fork函数在父进程和子进程中返回后立即暂停这两个进程,我们会看到两个进程的地址空间都是相同的。每个进程有相同的用户栈、相同的本地变量值、相同的堆、相同的全局变量值,以及相同的代码。因此,在我们的示例程序中,当fork函数在第6行返回时,本地变量x在父进程和子进程中都为1。然而,因为父进程和子进程是独立的进程,它们都有自己的私有地址空间。后面,父进程和子进程对x所做的任何改变都是独立的,不会反映在另一个进程的内存中。这就是为什么当父进程和子进程调用它们各自的printf语句时,它们中的变量x会有不同的值。
●共享文件。当运行这个示例程序时,我们注意到父进程和子进程都把它们的输出显示在屏幕.上。原因是子进程继承了父进程所有的打开文件。当父进程调用fork时,stdout文件是打开的,并指向屏幕。子进程继承了这个文件,因此它的输出也是指向屏幕的。
如果你是第一次学习fork函数,画进程图通常会有所帮助,进程图是刻画程序语句的偏序的一种简单的前趋图。每个顶点a对应于一条程序语句的执行。有向边a-→b表示语句a发生在语句b之前。边上可以标记出一些信息,例如一个变量的当前值。对应于printf语句的顶点可以标记上printf的输出。每张图从一个顶点开始,对应于调用main的父进程。这个顶点没有人边,并且只有一个出边。每个进程的顶点序列结束于一个对应于exit调用的顶点。这个顶点只有一条人边,没有出边。
例如,图1展示了代码框1中示例程序的进程图。初始时,父进程将变量x设置为1。父进程调用fork, 创建一个子进程,它在自己的私有地址空间中与父进程并发执行。对于运行在单处理器上的程序,对应进程图中所有顶点的拓扑排序(topological sort)表 示程序中语句的一个可行的全序排列。下面是一个理解拓扑排序概念的简单方法:给定进程图中顶点的一个排列,把顶点序列从左到右写成-行,然后画出每条有向边。排列是一个拓扑排序,当且仅当画出的每条边的方向都是从左往右的。因此,在代码框1的示例程序中,父进程和子进程的printf语句可以以任意先后顺序执行,因为每种顺序都对应于图顶点的某种拓扑排序。
图1 代码框1中示例程序的进程图
进程图特别有助于理解带有嵌套fork调用的程序。例如,代码框3中的程序源码中两次调用了fork。 对应的进程图(图2)可帮助我们看清这个程序运行了四个进程,每个都调用了一次printf,这些printf可以以任意顺序执行。
int main()
{
Fork();
Fork();
printf ("hel1o\n");
exit(0);
}
代码框3
图2 代码框2中嵌套fork的进程图
2、实例
1、fork0
/*
* fork0 - The simplest fork example
* Call once, return twice
* Creates child that is identical to parent
* Returns 0 to child process
* Returns child PID to parent process
*/
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
2、fork1
/*
* fork1 - Simple fork example
* Parent and child both run same code
* Child starts with identical private state
*/
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
3、fork2
/*
* fork2 - Two consecutive forks
* Both parent and child can continue forking
* Ordering undetermined
*/
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork