父子进程与进程亲缘关系
在操作系统中,进程之间有着复杂的关系,特别是父子进程、亲缘进程、孤儿进程等,它们的管理涉及到系统资源的分配和回收。
1. 父子进程
- 父进程:创建其他进程的进程称为“父进程”。
- 子进程:由父进程创建的进程称为“子进程”。
父进程通过系统调用(如 fork()
)来创建子进程。子进程会继承父进程的大部分属性(例如文件描述符、环境变量等)。父进程和子进程可以独立运行,但通过进程间通信(IPC)可以协作。
假设有两个进程:
进程 1 创建了 进程 2,那么:进程 1 是 父进程;进程 2 是 子进程。
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid == 0) {
// 子进程
printf("This is the child process. PID: %d, Parent PID: %d\n", getpid(), getppid());
} else if (pid > 0) {
// 父进程
printf("This is the parent process. PID: %d, Child PID: %d\n", getpid(), pid);
} else {
// fork 失败
perror("Fork failed");
}
return 0;
}
2. 亲缘进程
亲缘进程是指具有相同父进程的多个进程;如果两个进程有相同的父进程,则它们是彼此的“兄弟进程”。
虽然兄弟进程之间没有直接的父子关系,但它们都来源于同一个父进程。进程的亲缘关系有助于操作系统在进程调度和资源管理时优化性能。
假设 进程 1 创建了 进程 2 和 进程 3,那么:进程 2 和进程 3 是 兄弟进程。 它们共享相同的父进程(进程 1)。
我们通过 fork()
创建多个子进程来模拟亲缘进程的关系:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid1, pid2;
// 创建第一个子进程
pid1 = fork();
if (pid1 < 0) {
// fork 失败
perror("Fork failed for child 1");
return 1;
} else if (pid1 == 0) {
// 第一个子进程
printf("I am child 1. PID: %d, Parent PID: %d\n", getpid(), getppid());
return 0; // 子进程退出
}
// 创建第二个子进程
pid2 = fork();
if (pid2 < 0) {
// fork 失败
perror("Fork failed for child 2");
return 1;
} else if (pid2 == 0) {
// 第二个子进程
printf("I am child 2. PID: %d, Parent PID: %d\n", getpid(), getppid());
return 0; // 子进程退出
}
// 父进程等待子进程结束
wait(NULL); // 等待第一个子进程结束
wait(NULL); // 等待第二个子进程结束
// 父进程
printf("I am the parent process. PID: %d\n", getpid());
return 0;
}
// 1.创建兄弟进程:使用 fork() 两次,分别创建两个子进程(pid1 和 pid2),它们是相同父进程的子进程,因此是兄弟关系。
// 2.子进程行为:每个子进程通过 getpid() 打印自己的 PID,通过 getppid() 打印其父进程的 PID。
// 3.父进程行为:父进程通过 wait() 等待两个子进程完成,避免子进程变成僵尸进程。
// 4.进程关系:父进程是主进程,子进程 1 和子进程 2 是亲缘进程(兄弟进程)。
输出:
I am child 1. PID: 1234, Parent PID: 1233
I am child 2. PID: 1235, Parent PID: 1233
I am the parent process. PID: 1233
# PID 1234 和 PID 1235 是兄弟进程(具有相同的父进程)。
# PID 1233 是父进程。
3. 孤儿进程
当一个进程的父进程提前退出或死亡,而子进程仍在运行时,子进程就会成为 孤儿进程。
虽然叫 孤儿进程,但孤儿进程不会无人管理。在大多数操作系统中,孤儿进程会被 1 号进程(init 进程) 或其他系统进程“收养”,由其接管孤儿进程的资源和状态管理。
代码:
父进程退出: 假设某个父进程意外终止,但其子进程正在执行任务,此时子进程成为孤儿进程,操作系统会将其交给 init 进程 管理。
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
sleep(5); // 子进程延迟执行
printf("I am an orphan process. My PID: %d, My Parent PID: %d\n", getpid(), getppid());
} else if (pid > 0) {
// 父进程
printf("Parent process is exiting. PID: %d\n", getpid());
}
return 0;
}
输出:父进程提前退出后,子进程会显示其父进程 PID 被更改为 1(即 init 进程)。
4. 收尸
定义:收尸是指父进程通过系统调用(如 wait()
或 waitpid()
)回收其子进程的资源。当子进程结束时,它的资源不会立即释放,而是保留在系统中,直到父进程显式回收这些资源。
如若父进程没有回收子进程的资源,子进程会变成 僵尸进程,它虽然已退出,但仍占用系统的 PID 和一些资源。僵尸进程会导致系统资源浪费,长期存在可能影响系统性能。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("Child process is running. PID: %d\n", getpid());
sleep(2); // 模拟任务执行
printf("Child process is exiting.\n");
} else if (pid > 0) {
// 父进程
printf("Parent process is waiting for child to exit.\n");
wait(NULL); // 回收子进程资源
printf("Parent process has collected the child process.\n");
}
return 0;
}
输出:父进程调用 wait()
后,子进程退出时,父进程会立即回收子进程的资源,避免其变成僵尸进程。
5. 收养
当父进程提前退出时,由其他进程(通常是 init 进程,也可能是指定的其他进程)代替父进程回收子进程的资源,这个过程称为 收养。
收养过程是自动的,由操作系统完成。收养的主要目的是防止孤儿进程无人管理,确保系统的稳定性。
代码:当父进程提前退出时,子进程会被系统中的 PID 为 1 的进程(通常是 init 进程或 systemd) 收养的过程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
// fork 失败
perror("Fork failed");
exit(1);
} else if (pid == 0) {
// 子进程
printf("Child process is running. PID: %d, Parent PID: %d\n", getpid(), getppid());
sleep(5); // 子进程延迟执行,等待父进程退出
printf("Child process after parent exit. PID: %d, New Parent PID: %d\n", getpid(), getppid());
exit(0); // 子进程退出
} else {
// 父进程
printf("Parent process is running. PID: %d\n", getpid());
printf("Parent process is exiting.\n");
exit(0); // 父进程提前退出
}
return 0;
}
// 运行过程:
// 1.父进程创建子进程:
// - 调用 fork() 创建子进程。
// - 父进程和子进程分别执行不同的代码分支。
// 2.父进程提前退出:
// - 父进程在打印完成后调用 exit(0) 提前退出。
// - 此时,子进程仍在运行。
// 3.子进程变为孤儿进程:
// - 父进程退出后,子进程会被操作系统的 init 进程(PID 为 1) 或其他收养机制接管。
// - 子进程的 ppid(父进程 ID)会变为 1。
// 4.子进程完成执行:
// - 子进程休眠 5 秒后继续执行,打印当前的 PID 和新的父进程 PID。
// - 最后,子进程调用 exit(0) 退出。
getpid()
和getppid()
的作用:getpid()
返回当前进程的 PID。getppid()
返回当前进程的父进程 PID。
- 父进程提前退出:调用
exit(0)
后,父进程终止,子进程会被操作系统的init
进程(或systemd
)收养。 - 子进程的父进程 PID 变化:在父进程退出后,子进程的父进程 PID 会从原父进程的 PID(如 12345)变为 1(init 进程的 PID)。
sleep()
的作用:通过在子进程中调用 sleep(5),确保子进程在父进程退出后仍保持运行状态,从而观察到父进程 PID 的变化。
输出:
Parent process is running. PID: 12345
Parent process is exiting.
Child process is running. PID: 12346, Parent PID: 12345
Child process after parent exit. PID: 12346, New Parent PID: 1
通过执行该段代码可以清楚地看到 “收养” 的过程:当父进程退出时,子进程不会成为僵尸进程,而是被操作系统的 init 进程(或其他指定进程)接管,继续执行其任务。这种机制保证了孤儿进程的正常管理,从而维护了系统的稳定性和资源的有效利用。
综上,
概念 | 定义 | 特点 |
---|---|---|
父子进程 | 一个进程通过创建系统调用(如 fork)生成另一个进程,分别为父进程和子进程。 | 父进程和子进程可以独立运行,也可以通过进程间通信协作。 |
亲缘进程 | 具有相同父进程的多个进程。 | 亲缘进程之间没有直接关系,但共享同一个父进程。 |
孤儿进程 | 父进程提前退出时,子进程成为孤儿进程。 | 孤儿进程由 init 或其他系统进程收养,不会无人管理。 |
收尸 | 父进程通过 wait 系统调用回收子进程的资源。 | 未被回收的子进程会变成僵尸进程,占用系统资源。 |
收养 | 孤儿进程被其他进程(如 init)接管。 | 收养是由操作系统自动完成的,目的是防止孤儿进程无人管理并确保系统稳定性。 |
这些概念是操作系统进程管理的核心内容,理解它们有助于掌握进程的生命周期管理、资源分配和回收机制。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!