父进程与子进程关系
在UNIX和类UNIX操作系统中,fork
是一个系统调用,它用于创建一个新进程,这个新进程被称为子进程。子进程是父进程的一个副本,它继承了父进程的许多属性,例如打开的文件描述符、信号处理设置等,但是它有自己的唯一进程ID(PID)和内存空间。
fork
函数的原型如下:
pid_t fork(void);
当 fork
被调用时,它会返回两次:
- 在父进程中,
fork
返回子进程的PID。 - 在子进程中,
fork
返回0。 - 如果
fork
失败,它返回-1,并设置errno
来指示错误原因。
fork
调用成功后,操作系统会创建一个新的进程表项,并将父进程的许多属性复制到子进程中。这包括:
- 父进程的内存映像,包括代码段、数据段、堆栈段等。
- 父进程的打开文件描述符表。
- 父进程的信号处理设置。
- 父进程的当前工作目录。
- 父进程的环境变量。
但是,子进程有一些自己的独立属性,例如: - 它有自己的唯一的PID。
- 它的父进程PID(PPID)是原父进程的PID。
- 它有自己的独立的内存空间,虽然最初与父进程相同。
- 它有自己的程序计数器(PC),用于跟踪下一条要执行的指令。
fork
是UNIX操作系统中“一切皆文件”哲学的体现,它允许进程通过复制自己的方式来创建新进程,这是进程控制的基础。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
// 错误:fork失败
fprintf(stderr, "Fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf("Hello from the child process!\n");
} else {
// 父进程
printf("Hello from the parent process! Child PID: %d\n", pid);
}
// 父子进程都会执行的代码
printf("This will be printed by both parent and child.\n");
return 0;
}
在这个例子中,程序将输出两条 “This will be printed by both parent and child.” 消息,一条来自父进程,一条来自子进程。父进程还会打印出子进程的PID。
父进程退出后子进程是否会退出?
在UNIX和类UNIX操作系统中,当父进程退出时,子进程并不会自动退出。子进程将继续运行,除非它自己终止或者被外部信号终止。当一个父进程退出时,如果它有子进程,这些子进程将成为“孤儿进程”,并由 init 进程(PID 为 1)接管。init 进程会自动收割这些孤儿进程,即当它们终止时回收它们占用的资源。
如何一起退出?
如果你希望父进程退出时子进程也退出,你需要采取一些额外的步骤。这里有几种方法可以实现:
-
信号处理:父进程可以在退出前向子进程发送一个信号(例如 SIGTERM),子进程可以捕获这个信号并执行清理操作后退出。
-
使用进程组:可以将父进程和子进程放在同一个进程组中,然后向整个进程组发送信号。所有属于该进程组的进程都会收到这个信号。
-
使用会话:如果父进程创建了一个新的会话,并且子进程是这个会话的成员,那么当父进程退出时,子进程可能会收到 SIGHUP 信号。子进程可以捕获这个信号并退出。
-
使用监督者进程:可以使用如 supervisord 这样的监督者进程来管理父进程和子进程。监督者可以确保当父进程退出时,子进程也相应地退出。
-
使用 PID 文件:父进程可以在退出前创建一个包含子进程 PID 的文件。然后,可以编写一个外部脚本或程序来监视父进程的状态,并在父进程退出时读取 PID 文件,并向子进程发送信号。
示例:使用信号处理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle_sigterm(int sig) {
printf("Child process received SIGTERM, exiting.\n");
exit(EXIT_SUCCESS);
}
int main() {
pid_t pid = fork();
if (pid < 0) {
// 错误:fork失败
fprintf(stderr, "Fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
signal(SIGTERM, handle_sigterm);
while (1) {
// 子进程的工作循环
sleep(1);
}
} else {
// 父进程
sleep(5); // 父进程运行一段时间后
kill(pid, SIGTERM); // 向子进程发送SIGTERM信号
exit(EXIT_SUCCESS); // 父进程退出
}
return 0;
}
在这个例子中,父进程在退出前向子进程发送了 SIGTERM 信号。子进程捕获了这个信号并退出。