在C语言中,父进程的概念是操作系统进程管理的一部分,主要通过一组特定的系统调用来创建和管理,C语言本身没有ParentProcess这样的关键字或内置函数,但我们可以使用C标准库提供的函数来与操作系统内核交互,从而创建和操作父进程。

(图片来源网络,侵删)
核心概念
- 进程:一个程序的一次执行实例,它拥有独立的内存空间、文件描述符、状态等。
- 父进程:创建其他进程的进程,在Linux/Unix系统中,除了第一个进程(
init或systemd)外,所有进程都有一个父进程。 - 子进程:由父进程创建的新进程,子进程是父进程的一个副本,它会继承父进程的许多属性(如代码段、数据段、打开的文件、当前工作目录等),但拥有自己独立的PID(进程ID)和PPID(父进程ID)。
关键系统调用
在C语言中,我们主要通过以下两个函数来创建和管理父子进程:
fork() - 创建子进程
fork() 是在Linux/Unix以及类Unix系统(如macOS)中创建新进程的唯一方式。
- 头文件:
<unistd.h> - 功能: 创建一个与当前进程(父进程)几乎完全相同的子进程。
- 返回值:
- 在父进程中,
fork()返回子进程的PID(一个大于0的整数)。 - 在子进程中,
fork()返回0。 - 如果创建失败,
fork()返回-1。
- 在父进程中,
为什么会有两个返回值?
这是fork()最精妙的设计之一,它就像一个分叉路口:
- 父进程继续执行,得到子进程的PID,以便后续管理(如等待子进程结束)。
- 子进程从
fork()调用之后的地方开始执行,它知道自己是新创建的进程,所以返回0。
getpid() 和 getppid() - 获取进程ID
getpid(): 获取当前进程的进程ID。getppid(): 获取当前进程的父进程的进程ID。
这两个函数在调试和理解父子进程关系时非常有用。

(图片来源网络,侵删)
一个经典的父子进程示例
下面是一个最基础的示例,展示了如何创建一个子进程,并在父子进程中执行不同的代码。
#include <stdio.h>
#include <unistd.h> // 用于 fork(), getpid(), getppid()
#include <sys/wait.h> // 用于 wait()
#include <stdlib.h> // 用于 exit()
int main() {
pid_t pid; // pid_t 是进程ID的数据类型,通常是 int
printf("Before fork, Parent PID: %d\n", getpid());
// 创建子进程
pid = fork();
if (pid < 0) {
// fork失败
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// --- 这是子进程执行的代码块 ---
// fork() 返回 0 给子进程
printf("Hello from Child! My PID is: %d, My Parent's PID is: %d\n", getpid(), getppid());
// 子进程可以执行自己的任务
// ...
exit(EXIT_SUCCESS); // 子进程正常退出
} else {
// --- 这是父进程执行的代码块 ---
// fork() 返回子进程的PID (> 0) 给父进程
printf("Hello from Parent! My PID is: %d, My Child's PID is: %d\n", getpid(), pid);
// 父进程通常会等待子进程结束
// 这是一个好习惯,可以避免“僵尸进程”的产生
int status;
waitpid(pid, &status, 0);
printf("Parent: Child process with PID %d has finished.\n", pid);
}
return 0;
}
代码解析:
pid = fork();: 这行代码是关键,它创建了一个新的子进程。if (pid == 0): 这个分支代表子进程,子进程打印自己的PID和父进程的PID,注意,父进程的PID就是调用fork()的那个进程的PID。else: 这个分支代表父进程,父进程打印自己的PID和它刚刚创建的子进程的PID。waitpid(pid, &status, 0);: 父进程调用waitpid()来等待PID为pid的子进程结束,这非常重要,因为如果父进程在子进程之前结束,子进程会变成“孤儿进程”,并被init进程接管,如果父进程不等待,子进程结束后会变成“僵尸进程”,占用系统资源直到父进程结束。
编译和运行:
你需要使用支持POSIX标准的C编译器,比如GCC。
gcc parent_process.c -o parent_process ./parent_process
可能的输出:
输出顺序可能会有所不同,这取决于操作系统的调度。
Before fork, Parent PID: 12345
Hello from Parent! My PID is: 12345, My Child's PID is: 12346
Hello from Child! My PID is: 12346, My Parent's PID is: 12345
Parent: Child process with PID 12346 has finished.
或者

(图片来源网络,侵删)
Before fork, Parent PID: 12345
Hello from Child! My PID is: 12346, My Parent's PID is: 12345
Hello from Parent! My PID is: 12345, My Child's PID is: 12346
Parent: Child process with PID 12346 has finished.
父子进程的协作与数据共享
内存空间:写时复制
fork()创建的子进程会获得父进程内存空间的一个副本,但这并不意味着立即复制所有内存,现代操作系统采用了一种叫做“写时复制”(Copy-on-Write, COW)的优化技术。
- 初始状态: 子进程和父进程共享相同的物理内存页面。
- 写入时: 当任何一个进程试图修改这些共享的内存页面时,操作系统会为该进程创建一份该页面的副本,然后进行修改,另一个进程的页面保持不变。
这意味着父子进程在只读数据(如代码、常量)上是共享的,但在修改数据时会变得独立。
进程间通信
由于进程拥有独立的内存空间,它们不能直接读写对方的变量,如果需要在父子进程之间传递数据,必须使用进程间通信机制,
- 管道
- 消息队列
- 共享内存
- 信号
示例:通过管道通信
下面是一个例子,父进程向子进程发送一个消息。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 25
int main() {
int pipefd[2];
pid_t pid;
char write_msg[BUFFER_SIZE] = "Hello from Parent!";
char read_msg[BUFFER_SIZE];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == 0) {
// 子进程
close(pipefd[1]); // 关闭写端
read(pipefd[0], read_msg, BUFFER_SIZE);
printf("Child received: %s\n", read_msg);
close(pipefd[0]); // 关闭读端
exit(EXIT_SUCCESS);
} else {
// 父进程
close(pipefd[0]); // 关闭读端
write(pipefd[1], write_msg, strlen(write_msg) + 1); // +1 to include null terminator
close(pipefd[1]); // 关闭写端
wait(NULL); // 等待子进程结束
}
return 0;
}
Windows平台上的注意事项
如果你在Windows上编程,情况会不同,Windows没有fork()系统调用,它提供了以下API:
CreateProcess(): 用于创建一个全新的进程,可以指定要运行的程序和命令行参数,这与fork()+exec()的组合类似。GetCurrentProcessId(): 获取当前进程ID。GetProcessId(): 获取指定进程的ID(需要获取该进程的句柄)。
Windows的进程创建模型更为复杂和灵活,因为它允许你完全控制新进程的启动方式,而不是像fork()那样先复制再执行。
在C语言中,“父进程”是一个通过操作系统系统调用来实现的概念,核心是fork()函数。
fork()是创建子进程的关键,它在父进程中返回子进程的PID,在子进程中返回0。getpid()和getppid()用于获取进程ID信息,是调试和理解父子关系的重要工具。wait()/waitpid()是父进程用来同步和清理子进程的重要工具,避免僵尸进程。- 内存:父子进程初始共享内存,但通过“写时复制”机制,在写入时变得独立。
- 通信:进程间需要专门的IPC机制进行通信,不能直接共享内存变量。
理解父子进程是掌握操作系统并发编程和系统级编程的基础。
