# fork函数

[参考原文](http://blog.csdn.net/jason314/article/details/5640969)

## fork()函数函数介绍

**所需头文件：**

```c
#include<unistd.h>
#include<sys/types.h>
```

**函数定义 :** `pid_t fork( void );`

pid\_t 是一个宏定义，其实质是int 被定义在`#include<sys/types.h>`中

**返回值:** 若成功调用一次则返回两个值，子进程返回0，父进程返回子进程ID；否则，出错返回-1

**函数说明：**

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

子进程是父进程的副本，它将获得父进程数据空间、堆、栈等资源的副本。父子进程间共享的存储空间只有代码段。

示例：

```c
#include<sys/types.h> 
#include<unistd.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 

int main() 
{ 

  pid_t pid;/*pid 进程id号*/ 

  pid=fork();/*创建一个新进程*/ 

  if(pid==0) /*返回0为子进程*/ 
  { 
   printf("Return pid is %d\n",pid); 
   printf("This is son process!  pid is:%d\n",getpid()); 

  } 
  else if(pid>0)/*返回大于0为父进程*/ 
  { 
    printf("Return pid is %d\n",pid); 
    printf("This is parent process!  pid is:%d\n",getpid()); 
     waitpid(pid,NULL,0);/*等待子进程退出*/
  } 
  else 
  { 
     perror("fork() error!"); 
     exit; 
  } 
}
```

## **一、fork入门知识**

一个进程，包括代码、数据和分配给进程的资源。fork（）函数通过系统调用创建一个与原来进程几乎完全相同的进程，也就是两个进程可以做完全相同的事，但如果初始参数或者传入的变量不同，两个进程也可以做不同的事。\
一个进程调用fork（）函数后，系统先给新的进程分配资源，例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中，只有少数值与原来的进程的值不同。相当于克隆了一个自己。

例子：

```c
/*
 *  fork_test.c
 */
#include <unistd.h>
#include <stdio.h> 
int main () 
{ 
    pid_t fpid; //fpid表示fork函数返回的值
    int count=0;
    fpid=fork(); 
    if (fpid < 0) 
        printf("error in fork!"); 
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d/n",getpid()); 
        printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d/n",getpid()); 
        printf("我是孩子他爹/n");
        count++;
    }
    printf("统计结果是: %d/n",count);
    return 0;
}
```

运行结果是：

```
    i am the child process, my process id is 5574
    我是爹的儿子
    统计结果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    统计结果是: 1
```

在语句fpid=fork()之前，只有一个进程在执行这段代码，但在这条语句之后，就变成两个进程在执行了，这两个进程的几乎完全相同，将要执行的下一条语句都是`if(fpid<0)……`

为什么两个进程的fpid不同呢，这与fork函数的特性有关。

fork调用的一个奇妙之处就是它仅仅被调用一次，**却能够返回两次，它可能有三种不同的返回值**：

* 1）在父进程中，fork返回新创建子进程的进程ID；
* 2）在子进程中，fork返回0；
* 3）如果出现错误，fork返回一个负值；

如图：

![](https://github.com/xiaoxiami/linux-server/tree/a9632b5f3a7d35ae98afdcd85a4604852c8aba0d/assets/fork_p.jpg)

验证一下,修改例子代码:

```c
pid=fork(); // 此行后加入
printf("pid:%d\n",pid);
```

结果:

```
pid:19007
This is in the father process,here write a string to the pipe.
pid:0
This is in the child process,here read a string from the pipe.
Hello world , this is write by pipe.
```

在fork函数执行完毕后，如果创建新进程成功，则出现两个进程，一个是子进程，一个是父进程。在子进程中，fork函数返回0，在父进程中，fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

**引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表，进程形成了链表，父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程，所以其fpid为0.**\
fork出错可能有两种原因：

1）当前的进程数已经达到了系统规定的上限，这时errno的值被设置为EAGAIN。

2）系统内存不足，这时errno的值被设置为ENOMEM。

```
创建新进程成功后，系统中出现两个基本完全相同的进程，**这两个进程执行没有固定的先后顺序，哪个进程先执行要看系统的进程调度策略。**
```

每个进程都有一个独特（互不相同）的进程标识符（process ID），可以通过`getpid()`函数获得，还有一个记录父进程pid的变量，可以通过`getppid()`函数获得变量的值。

fork执行完毕后，出现两个进程，

![](https://313308022-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30T9rtIVtc5eWQf%2F-LfnT9dAhhstiSPRKKVD%2F-LfnTGYEMD6RbfdiRFvO%2Ffork_1.png?generation=1558862973024671\&alt=media)

有人说两个进程的内容完全一样啊，怎么打印的结果不一样啊，那是因为判断条件的原因，上面列举的只是进程的代码和指令，还有变量啊。

执行完fork后，进程1的变量为count=0，fpid！=0（父进程）。进程2的变量为count=0，fpid=0（子进程），这两个进程的变量都是独立的，存在不同的地址中，不是共用的，这点要注意。可以说，我们就是通过fpid来识别和操作父子进程的。

还有人可能疑惑为什么不是从\\#include处开始复制代码的，**这是因为fork是把进程当前的情况拷贝一份**，执行fork时，进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

## **二、fork进阶知识**

先看一份代码：

```c
/*
 *  fork_test.c
 *  version 2
 */
#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   printf("i son/pa ppid pid  fpid\n");
   //ppid指当前进程的父进程pid
   //pid指当前进程的pid,
   //fpid指fork返回给当前进程的值
   for(i=0;i<2;i++){
       pid_t fpid=fork();
       printf("pid:%d\n",fpid);
       if(fpid==0)
           printf("%d child  %4d %4d %4d\n",i,getppid(),getpid(),fpid);
       else
           printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),fpid);
   }
   return 0;
}
```

运行结果是：(注意:本人在ubuntu下执行,每次的执行结果不同,未按照原文说的执行结果)

```
i son/pa ppid pid  fpid
pid:32152
0 parent 32100 32147 32152
pid:0
0 child  32147 32152    0
pid:32153
1 parent 32100 32147 32153
pid:0
1 child  32147 32153    0
pid:32154
1 parent 32147 32152 32154
pid:0
1 child  2108 32154    0
```

这份代码比较有意思，我们来认真分析一下：

**第一步：**

在父进程中，指令执行到for循环中，`i=0`，接着执行fork，fork执行完后，系统中出现两个进程，分别是p32147和p32152(第一个是当前进程,另一个是创建出来的子进程)（后面我都用pxxxx表示进程id为xxxx的进程）。可以看到父进程p32147的父进程是p32100，子进程p32152的父进程正好是p32147。我们用一个链表来表示这个关系：

```
p32100->p32147->p32152
```

```
第一次fork后，p3224（**父进程**）的变量为i=0，fpid=p32152（fork函数在父进程中返向子进程id），代码内容为：
```

```c
   for(i=0;i<2;i++){
       pid_t fpid=fork();//执行完毕，i=0，fpid=32152
       printf("pid:%d\n",fpid);
       if(fpid==32152)
           printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
           printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;
```

```
p32152（**子进程**）的变量为i=0，fpid=0（fork函数在子进程中返回0），代码内容为：
```

```c
   for(i=0;i<2;i++){
       pid_t fpid=fork();//执行完毕，i=0，fpid=0
       printf("pid:%d\n",fpid);
       if(fpid==0)
           printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
           printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;
```

所以打印出结果：

```
pid:32152
0 parent 32100 32147 32152
pid:0
0 child  32147 32152    0
```

**第二步：**

假设父进程p32147先执行，当进入下一个循环时，`i=1`，接着执行fork，系统中又新增一个进程p32153，对于此时的父进程，p32100->p32147（当前进程）->p32153（被创建的子进程）。

对于子进程p32152，执行完第一次循环后，`i=1`，接着执行fork，系统中新增一个进程p32154，对于此进程，p32147->p32152（当前进程）->p32154（被创建的子进程）。从输出可以看到32152原来是32147的子进程，现在变成32154的父进程。父子是相对的，这个大家应该容易理解。只要当前进程执行了fork，该进程就变成了父进程了，就打印出了parent。

所以打印出结果是：

```
1 parent 32100 32147 32153
pid:0
1 child  32147 32153    0
pid:32154
1 parent 32147 32152 32154
pid:0
1 child  2108 32154    0
```

第三步：

第二步创建了两个进程p32153，p32154，这两个进程执行完printf函数后就结束了，因为这两个进程无法进入第三次循环，无法fork，该执行return 0;了，结束。

总结一下，这个程序执行的流程如下：

![](https://313308022-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30T9rtIVtc5eWQf%2F-LfnT9dAhhstiSPRKKVD%2F-LfnTGYHhy-F7xUOHvI1%2Ffork_2.jpg?generation=1558862973229513\&alt=media)

这个程序最终产生了3个子进程，执行过6次printf（）函数。

我们再来看一份代码：

```c
/*
 *  fork_test.c
 *  version 3
 */
#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   for(i=0;i<3;i++){
       pid_t fpid=fork();
       if(fpid==0)
           printf("son\n");
       else
           printf("father\n");
   }
   return 0;

}
```

它的执行结果是：

```
father
father
son
father
father
father
son
father
son
son
son
father
son
son
```

进一步分析fork函数。

问题是不算main这个进程自身，程序到底创建了多少个进程。

为了解答这个问题，我们先做一下弊，先用程序验证一下，到此有多少个进程。

```c
#include <stdio.h>
int main(int argc, char* argv[])
{
   fork();
   fork() && fork() || fork();
   fork();
   printf("+\n");
}
```

答案是总共20个进程，除去main进程，还有19个进程。

我们再来仔细分析一下，为什么是还有19个进程。

第一个fork和最后一个fork肯定是会执行的。

主要在中间3个fork上，可以画一个图进行描述。

这里就需要注意`&&`和`||`运算符。

`A&&B`，如果`A=0`，就没有必要继续执行`&&B`了；A非0，就需要继续执行`&&B`。

`A||B`，如果A非0，就没有必要继续执行`||B`了，`A=0`，就需要继续执行||B。

fork()对于父进程和子进程的返回值是不同的，按照上面的`A&&B`和`A||B`的分支进行画图，可以得出5个分支。

![](https://313308022-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30T9rtIVtc5eWQf%2F-LfnT9dAhhstiSPRKKVD%2F-LfnTGYJKhB7VzrFiCcZ%2Ffork_3.png?generation=1558862963311776\&alt=media)加上前面的fork和最后的fork，总共4\*5=20个进程，除去main主进程，就是19个进程了。

**三、fork高阶知识**

```
    这一块我主要就fork函数讲一下操作系统进程的创建、死亡和调度等。
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xiaoxiami.gitbook.io/linux-server/duo-jin-cheng-bian-cheng/forkhan-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
