【进程与线程】父子进程与进程亲缘关系

父子进程与进程亲缘关系

在操作系统中,进程之间有着复杂的关系,特别是父子进程、亲缘进程、孤儿进程等,它们的管理涉及到系统资源的分配和回收。

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) 退出。
  1. getpid()getppid() 的作用:
    • getpid() 返回当前进程的 PID。
    • getppid() 返回当前进程的父进程 PID。
  2. 父进程提前退出:调用 exit(0) 后,父进程终止,子进程会被操作系统的 init 进程(或 systemd)收养。
  3. 子进程的父进程 PID 变化:在父进程退出后,子进程的父进程 PID 会从原父进程的 PID(如 12345)变为 1(init 进程的 PID)。
  4. 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)接管。收养是由操作系统自动完成的,目的是防止孤儿进程无人管理并确保系统稳定性。

这些概念是操作系统进程管理的核心内容,理解它们有助于掌握进程的生命周期管理、资源分配和回收机制。

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫的小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值