是fork 不是 f**k。好吧,这个fork 翻译过来是”分叉”,在GitHub 那里可以fork 一份别人的仓库到自己的名下。就是开了个分叉,然后可以再自己fork 的这份仓库里进行操作,操作完了以后可以发起pull request 到原有的地方。

很形象啊有木有。在Linux 系统下可以通过fork (系统调用)来创建子进程,子进程会从fork 起继续向下执行代码。

图片来源:我们团队仓库的network

一调用fork,就有了两个进程,当前进程创建出一个子进程,两个进程都继续从fork 这行继续往下走。

  1. 子进程的fork 返回值是0

  2. 父进程fork 返回子进程的pid

  3. 如果fork 返回值小于0,说明创建进程失败。

    创建失败的原因:

    1. 内存不够

    2. 内核参数限制(进程太多达到上限)ulimit -a查看

    3. 平台不支持

    4. 信号打断

好了,现在知道了这个写代码验证一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t ret = fork();
if (ret > 0) {
// father process
printf("father process said: my pid=%d, and my father's pid=%d\n", getpid(), getppid());
} else if (ret == 0) {
// child process
printf("child process said: my pid=%d, and my father's pid=%d\n", getpid(), getppid());
} else {
perror("fork");
}

sleep(1); // 注意这里的sleep
return 0;
}

上面这个代码的16行,我不写sleep 和写了的输出结果是有区别的:

第一个红色框起来的是不写sleep:
爸爸:我叫23798,我的爸爸叫3766
儿子:我叫23799,我的爸爸叫1
为什么他爸生出来的儿子不认亲爸呢?这就是孤儿进程,爸爸把儿子创造出来后没多久就抛下儿子了,儿子被叫1号进程的人收养,所以这个儿子说我爸是1不是23798。

第二个红色框的是写了sleep的。

fork 的详细解释

fork 会以父进程为模板,创建一个子进程。

  1. 会把父进程的 PCB 稍加修改,成为子进程的 PCB。

  2. 会把父进程的虚拟地址空间拷贝一份,作为子进程的地址空间。

    堆和栈都会拷贝一份,注意这里是写时拷贝(偷懒?效率?)

    父子进程会共用一份代码,各自有一份数据(代码有的时候也会被拷贝,比如做外挂的时候应该会😼)

  3. fork 分别在父子进程中返回

    父进程返回子进程的pid,子进程返0,在fork后继续执行。

  4. 父子进程的执行顺序并不是 if 判断返回值来的,这个是由调度器来弄。

fork过程

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

图片来源于网络,稍改


笔记参考:
http://www.maiziedu.com/wiki/process/fork/