初步体验进程的概念后,你也知道了你的 a.out 进程实际上是由终端 bash 进程创造出来的。那 bash 进程的父进程又是谁呢?如果你不断追溯下去,你就会发现,最终于那个进程就是 pid 为1 的 init 进程,而 init 进程,又是由一个 pid 为 0 的进程通过系统调用 fork 生成的,它也是第一个由 fork 函数创造出来的进程(linux 0.11 内核)。
fork 是本篇的重点,它是用户创建进程的唯一方法(万事都有特例,现阶段可忽略)。
1. fork 函数
fork 中文含义为“叉子”,这把叉子形状有点像中文的汉字“丫”,一个树干,两个分支。
如果你的 a.out 程序中有一个地方调用了 fork 函数,这时候你的 a.out 进程就会分出两条叉路,在这个分叉点,这两个进程几乎是“一毛一样”的。
1.1 学习分身咒语——fork
fork 函数恐怕是你到目前为止学习到的最简洁的函数了,它甚至连参数都没有:
pid_t fork(void);
这个函数神奇的地方在于,“一次调用两次返回”。这可能让你懵逼,那就举个例子让你明白。
有一天你学会了影分身术,念了一句咒语“急急如律令,分!”,一瞬间冒出了一个和你一模一样的人站在你身边。那个复制出来的你刚一出来就大喊“大家好,我是鸣人分身!”,同时你也大喊到“大家好,我是鸣人本体!”。
fork 函数就像那句咒语,而分身完成后,你和你的分身说的话,就相当于 fork 函数的返回值。
1.2 fork 函数的返回值
fork 函数返回值是 pid_t 类型,它就是通过 typedef 定义的一个 32 位的有符号整型数(32位 linux 系统)。不同的返回值,描述了进程的身份,到底是分身,还是本体?
- >0 : 本体(调用 fork 的那个进程),这个值,是分身(子进程)的 id 号。
- =0 :分身(子进程)
- =−1 :失败
2. 练习你的分身术
为了加深对 fork 的理解,具体看下面的例子。
2.1 实验代码
- 代码
// myfork.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main() {
char buf[] = "Hello, I'm father\n";
write(STDOUT_FILENO, buf, strlen(buf));
printf("before fork\n");
pid_t pid = fork();
if (!pid) {
printf("I'm child %d; my father is %d\n", getpid(), getppid());
sleep(2);
}
else if(pid > 0) {
printf("I'm father %d; my child is %d\n", getpid(), pid);
sleep(2);
}
else {
perror("fork");
return 1;
}
return 0;
}
- 编译
$ gcc myfork.c -o myfork
- 运行
$ ./myfork
- 结果
Hello, I'm father
before fork
I'm father 4382; my child is 4383
I'm child 4383; my father is 4382
2.2 结果分析
myfork 程序在调用 fork 前,先通过 write 和 printf 函数打印了两句不同的字符串。接下来调用 fork,fork 返回的的值保存在 pid 这个变量里。
你始终要记住,fork 是影分身口语,是本体念出来的;而返回值,是本体和分身都要念的。如何判断谁是本体谁是分身,就看本体和分身念的是什么。
在上面的代码里,返回值如果是 0 ,说明它是分身,是子进程,它不需要喊出自己的名字(对应于 pid),因为子进程可以通过 getpid 函数来知道自己的名字。
如果返回值大于 0,说明它是本体,因为本体需要知道自己分身的名字。
如果返回值等于 -1,很不幸,这次分身术失败。
3. 总结
- 理解“分身术”
- 理解 fork 函数及其返回值含义
其实,上面这段代码很不简单,坑也很多。如果你的程序还没删除,赶紧运行一下下面这个命令:
$ ./myfork > tmp
它把结果重定向到了 tmp 文件中,打开你的 tmp 文件看看吧!
不用惊慌,世界没有崩塌,请听下回分解。