图解嵌套进程

fork() 函数详解
fork() 是一个系统调用,用于创建一个新进程。调用 fork() 后,会生成一个新的子进程,该子进程是父进程的几乎完整副本。这个函数的特点包括:调用一次,返回两次:(一次是在调用进程[父进程], 一次是在新创建的子进程[子进程的 PID 总是返回 0]当中),并发执行,相同但独立的地址空间,以及共享文件描述符。
1. fork() 的返回值
调用 fork() 时,它会返回不同的值给父进程和子进程:
- 返回值为 0:表示这是在子进程中运行。
- 返回值为子进程的 PID(进程 ID):表示这是在父进程中运行。
- 返回 -1:表示
fork()调用失败,通常是因为系统资源不足。
2. 代码详解
代码回顾
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int x = 1;
pid = fork(); // 创建一个子进程
if(pid == 0) // 子进程
{
printf("child : x=%d\n", ++x);
exit(0); // 子进程结束
}
// 父进程
printf("parent: x=%d\n", --x);
exit(0); // 父进程结束
}
执行流程
-
fork()创建一个子进程。- 在子进程中,
fork()返回0,执行子进程代码块。 - 在父进程中,
fork()返回子进程的 PID,继续执行父进程代码块。
- 在子进程中,
-
子进程输出:
x的初始值是1,在子进程中执行++x,变为2,打印:child : x=2。
-
父进程输出:
- 父进程中的
x仍然是1(和子进程中的x独立),执行--x,变为0,打印:parent: x=0。
- 父进程中的
可能的输出顺序
由于父进程和子进程是并发执行的,输出顺序无法确定,可能是:
child : x=2
parent: x=0
或:
parent: x=0
child : x=2
3. fork() 的关键特点
(1) 调用一次,返回两次(“返回两次”指的是 fork() 函数在父进程和子进程中分别返回不同的值)
一次在父进程中返回子进程的 PID,另一次在子进程中返回 0
- 父进程调用
fork()后:- 返回值是子进程的 PID。
- 父进程继续执行
fork()后的代码。
- 子进程调用
fork()后:- 返回值是
0。 - 子进程从
fork()后的代码开始执行。
- 返回值是
这就是“调用一次,返回两次”的含义。
(2) 并发执行
- 父进程和子进程在
fork()后并发运行(实际顺序由操作系统调度器决定)。 - 两个进程独立运行,互不干扰,但共享 CPU 时间。
(3) 相同但独立的地址空间
- 子进程继承了父进程的内存内容:
- 父子进程的变量(如
x)在fork()后会有相同的初始值。 - 但是父子进程的内存空间是独立的,在其中一方修改变量不会影响另一方。
- 父子进程的变量(如
- 实现细节:
- 现代操作系统通常采用写时复制(Copy-on-Write, COW)机制:
- 父子进程最初共享相同的物理内存。
- 当其中一个进程尝试修改共享内存时,操作系统会为其分配新的内存。
- 现代操作系统通常采用写时复制(Copy-on-Write, COW)机制:
(4) 共享文件描述符
- 父子进程共享文件描述符表(指向打开的文件)。
- 影响:
- 如果父进程和子进程操作相同的文件,文件偏移量会受到影响。
-
示例:
#include <stdio.h> #include <unistd.h> int main() { FILE *fp = fopen("test.txt", "w"); if (!fp) return 1; fork(); fprintf(fp, "Hello\n"); // 父子进程都会写入 fclose(fp); return 0; }输出可能是:
Hello Hello
4. 总结
- 调用一次,返回两次:父子进程的返回值不同。
- 并发执行:父子进程独立运行,实际顺序由调度决定。
- 相同但独立的地址空间:父子进程初始内存相同,但后续修改互不影响。
- 共享文件描述符:父子进程共享打开的文件,可能影响文件操作的结果。
通过 fork(),Linux 实现了多进程的创建与管理,成为并发编程的重要工具。