云计算百科
云计算领域专业知识百科平台

【系统编程】父子进程的关系

文章目录

  • 一、区分父子进程
    • 1.1 父子进程的相同点
    • 1.2 父子进程的不同点
    • 1.3 写时复制机制(Copy-On-Write, COW)
    • 1.4 文件描述符的共享
  • 二、代码示例
    • 2.1 通过 `fork()` 区分父子进程
    • 2.2 父子进程地址空间相互独立
  • 三、父子进程小结
  • 四、常用进程相关命令
    • 4.1 ps 命令
    • 4.2 kill 命令

一、区分父子进程

1.1 父子进程的相同点

在这里插入图片描述

当一个进程调用 fork() 之后,子进程会复制父进程的大部分内容。它们初始状态一致,包括:

  • 用户空间内容(通过写时复制机制实现):

    • 代码段 .text(共享)
    • 数据段 .data(初始相同)
    • BSS段(未初始化全局变量)
    • 堆区(malloc 动态分配)
    • 栈区(函数局部变量)
  • 全局变量、局部变量:

    • 初始值相同,但内存空间独立(写时复制)
  • 环境变量

  • 进程元信息(初始继承):

    • 用户 ID(UID)/ 有效用户 ID(EUID)
    • 工作目录
    • 宿主目录(HOME)
    • 信号处理方式(signal handlers)
  • 打开的文件描述符:

    • 父子共享文件表(如指向同一个文件,偏移量共享,引用计数增加)
  • 共享的内存映射(mmap 区)

  • 1.2 父子进程的不同点

  • 进程ID
  • 父进程ID
  • fork返回值
  • 进程运行时间
  • 闹钟(定时器)
  • 未决信号集
  • 一般来说,在 fork() 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。

    需要注意的是,在子进程的地址空间里,子进程是从 fork() 这个函数后才开始执行代码。

    1.3 写时复制机制(Copy-On-Write, COW)

    子进程不是立即拷贝整个地址空间,而是延迟拷贝,用来节省内存。

    虽然 fork() 看似只复制“少量不同信息”,但:

    • 仍涉及创建新的进程控制块(PCB)
    • 分配 PID 和其他资源
    • 如果触发写操作,需复制内存页

    因此通过写时复制技术最大程度降低成本:

    • 读时共享:父子进程共享物理内存(只读权限)
    • 写时复制:当一方尝试写入:系统才真正复制该页面 → 实现分离

    优点:

    • 节省资源,避免不必要的内存拷贝
    • 提高性能(尤其是立即 exec() 的场景)

    1.4 文件描述符的共享

  • 父子进程的文件描述符指向相同的文件表项

    • 表现为:
      • 引用计数增加
      • 文件偏移量共享
      • 文件状态标志共享(如 O_APPEND)
  • mmap 创建的映射区共享

  • 注意:文件描述符共享 ≠ 文件内容共享,但操作影响对方的读写位置

    二、代码示例

    2.1 通过 fork() 区分父子进程

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>

    int main() {
    pid_t pid;
    pid = fork(); // 创建子进程

    if (pid < 0) {
    // 创建失败
    perror("fork");
    return 1;
    }

    if (pid == 0) {
    // 子进程
    while (1) {
    printf("I am son\\n");
    sleep(1);
    }
    } else {
    // 父进程
    while (1) {
    printf("I am father\\n");
    sleep(1);
    }
    }

    return 0;
    }

    运行效果: 父子进程交替(或连续)输出不同的字符串,顺序不固定。

    2.2 父子进程地址空间相互独立

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>

    int a = 10; // 全局变量

    int main() {
    int b = 20; // 局部变量
    pid_t pid;
    pid = fork();

    if (pid < 0) {
    perror("fork");
    return 1;
    }

    if (pid == 0) {
    // 子进程修改变量
    a = 111;
    b = 222;
    printf("son: a = %d, b = %d\\n", a, b);
    } else {
    // 父进程等待子进程先运行
    sleep(1);
    printf("father: a = %d, b = %d\\n", a, b);
    }

    return 0;
    }

    运行效果:

    son: a = 111, b = 222
    father: a = 10, b = 20

    三、父子进程小结

    1. 父子进程的区别

    • 创建方式:通过 fork() 系统调用创建子进程。

    • 代码关系:子进程是父进程的“复制品”,初始代码和数据一致,但运行是相互独立的。

    • 返回值区别:

      • 父进程中 fork() 返回 子进程的 PID(> 0)。
      • 子进程中 fork() 返回 0。
      • 如果返回值 < 0,表示创建失败。
    • 运行顺序:父进程先执行还是子进程先执行 不确定,取决于内核调度算法。

    • 子进程执行位置:在子进程地址空间中,从 fork() 调用之后的代码开始执行。

    2. 父子进程的地址空间

    • 相互独立:父子进程有独立的地址空间,互不影响。
    • 变量修改互不干扰:
      • 全局变量和局部变量在 fork() 之后各自拥有一份拷贝。
      • 子进程修改变量,不会影响父进程的值(反之亦然)。

    四、常用进程相关命令

    4.1 ps 命令

    • 功能:查看进程的详细状态。
    • 常用选项:
    选项含义
    -a 显示所有终端上的进程,包括其他用户的
    -u 显示详细状态
    -x 显示无控制终端的进程
    -j 显示作业控制相关信息
    • 常用用法:
      • ps aux:显示当前所有进程。
      • ps ajx:以更完整格式显示所有进程。

    4.2 kill 命令

    • 功能:向指定进程发送信号。

    • 基本用法:

      • kill PID → 默认发送 SIGTERM(终止信号)。
      • kill -l → 查看所有信号编号与名称。
      • kill -9 PID 或 kill -SIGKILL PID → 强制终止进程。
    • 注意:

      • SIGKILL(信号编号 9)不可被捕获或忽略,能强制结束进程。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【系统编程】父子进程的关系
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!