当前位置: 首页 > news >正文

深入解析Linux进程创建与fork机制

目录

一、fork函数初识

二、fork函数返回值

思考:

1. fork函数为何给子进程返回0,而给父进程返回子进程的PID?

2. 关于fork函数为何有两个返回值这个问题

三、写时复制机制

写时拷贝(Copy-On-Write)机制解析

1. 必要性:为什么需要写时拷贝?

2. 延迟拷贝的优势:为何不立即拷贝?

3. 代码段的写时拷贝适用性

关键结论

四、fork函数的常规用法

五、fork调用失败的原因:


一、fork函数初识

        在Linux系统中,fork函数是一个关键的系统调用,它通过复制现有进程来创建新进程。被创建的进程称为子进程,而原始进程则称为父进程。

#include <unistd.h>
pid_t fork(void);

函数返回值:

  • 子进程返回0
  • 父进程返回子进程的PID
  • 出错时返回-1

当进程调用fork函数时,内核会执行以下操作:

  1. 为子进程分配新的内存空间和内核数据结构
  2. 将父进程的部分数据结构内容复制到子进程
  3. 将子进程添加到系统进程列表
  4. fork函数返回,由调度器开始进行进程调度

        当进程调用fork()后,会生成两个二进制代码完全相同的子进程。这两个进程会从相同的执行点继续运行,但随后各自进入独立的执行流程。请看以下示例代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(void) 
{pid_t pid;printf("Before: pid is %d\n", getpid());if ((pid = fork()) == -1) {perror("fork()");exit(1);}printf("After: pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
}

运行结果:

        输出内容分为三行:一行"Before"和两行"After"。进程2619首先打印"Before"消息,随后又打印了一条"After"消息。另一条"After"消息则是由进程2620打印的。值得注意的是,进程2620并未打印"Before"消息。为什么呢?如下图所示:

        在fork调用之前,父进程独立运行;调用之后,父进程和子进程将各自执行。需要注意的是,fork之后哪个进程先执行完毕,完全取决于系统调度器的安排。


二、fork函数返回值

  • 子进程返回0
  • 父进程返回子进程的PID
  • 出错时返回-1

思考:

1. fork函数为何给子进程返回0,而给父进程返回子进程的PID?

        这种设计源于进程间的关系特性:一个父进程可以创建多个子进程,但每个子进程只能有一个父进程。对子进程而言,它无需识别父进程;而对父进程来说,必须明确每个子进程的标识。父进程需要获取子进程的PID才能有效分配和管理任务。

2. 关于fork函数为何有两个返回值这个问题

当父进程调用fork函数时,系统会执行一系列创建子进程的操作:

  1. 创建子进程的进程控制块
  2. 建立子进程的进程地址空间
  3. 生成子进程对应的页表完成这些步骤后,操作系统会将子进程的进程控制块加入系统进程列表,此时子进程创建完成。

        换句话说,当fork函数执行return语句时,子进程的创建已经完成。因此,后续的return语句不仅由父进程执行,子进程也会执行同样的操作,这就解释了为何fork函数会返回两个值。


三、写时复制机制

        子进程刚创建时,与父进程共享相同的内存数据段和代码段,两者的页表都指向同一块物理内存区域。只有当父进程或子进程尝试修改数据时,系统才会执行写时复制操作,将原始数据复制一份供修改使用。

具体原理如下图所示:

        得益于写时拷贝技术,父子进程得以完全分离,从而确保了进程的独立性。写时拷贝是一种延迟申请技术,可以有效提高系统内存的使用效率。

这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝技术。

写时拷贝(Copy-On-Write)机制解析

1. 必要性:为什么需要写时拷贝?

  • 进程独立性要求
    多进程环境下,操作系统需确保各进程资源独占性。写时拷贝通过延迟拷贝策略,保证子进程修改数据时才会复制父进程资源,避免进程间数据干扰。

  • 性能优化
    直接拷贝父进程全部数据会带来显著开销(如内存占用、CPU复制时间),而多数情况下子进程可能仅读取数据或使用部分资源。

2. 延迟拷贝的优势:为何不立即拷贝?

  • 资源利用率

    • 避免冗余拷贝:子进程可能仅访问父进程部分数据(如只读代码段),立即全量拷贝会导致内存浪费。

    • 按需分配:仅在子进程尝试修改数据时触发拷贝,减少无效的内存占用(例如fork()后接exec()的场景无需拷贝父进程数据)。

  • 效率提升
    现代操作系统通过页表映射共享父进程资源,写时拷贝将实际拷贝操作推迟到最后一刻,显著降低进程创建开销。

3. 代码段的写时拷贝适用性

  • 常规情况(90%以上)
    代码段通常是只读的,父子进程可共享同一物理内存页,无需触发拷贝。

  • 例外场景

    • 进程替换(如exec():新程序加载会覆盖原代码段,此时需重新分配内存,本质上仍遵循"修改时拷贝"逻辑。

    • 自修改代码:极少见情况下程序动态修改代码段内容,会触发写时拷贝机制。

关键结论

写时拷贝通过共享只读、延迟写入的策略,在保证进程独立性的同时,最大化内存和计算资源的利用率,是操作系统优化进程创建的核心机制。


四、fork函数的常规用法

  • 父进程需要复制自身,使父子进程可以执行不同的代码段。例如,父进程监听客户端请求,创建子进程来处理具体请求。
  • 进程需要执行新程序。例如子进程在fork返回后调用exec函数。

五、fork调用失败的原因:

fork函数创建子进程也可能会失败,有以下两种情况:

  1. 系统中有太多的进程,内存空间不足,子进程创建失败。
  2. 实际用户的进程数超过了限制,子进程创建失败。
http://www.lryc.cn/news/591788.html

相关文章:

  • 学习日志12 python
  • 强化第三讲—一元函数微分学的概念
  • LIN协议核心详解
  • 【Dv3Admin】传递数据实现查询功能
  • Mac OS上docker desktop 替代方案
  • 【JavaEE进阶】使用云服务器搭建Linux环境
  • 数据结构排序算法总结(C语言实现)
  • Leetcode刷题营第二十九,三十题:二叉树的中序以及后序遍历
  • Docker 镜像原理
  • 在windows平台上基于OpenHarmony sdk编译三方库并暴露给ArkTS使用(详细)
  • 深入理解Java中的Map.Entry接口
  • AI问答-供应链管理:各种交通运输方式货运成本分析
  • C/C++---rdbuf()函数
  • 建筑兔零基础人工智能自学记录111|初识comfyui-20
  • 系统设计时平衡超时时间与多因素认证(MFA)带来的用户体验下降
  • VMware Workstation Pro 17下载安装
  • 安装wsl-Ubuntu到D盘
  • 微信远程控制系统2.0
  • 如何下载视频 (pc端任何视频均可下载)
  • 通义万相-文生视频实践
  • Redis主从复制数据同步实现原理详细介绍
  • 【LeetCode刷题指南】--数组串联,合并两个有序数组,删除有序数组中的重复项
  • Install Docker Engine on UbuntuMySQL
  • Docker国内镜像
  • 网络服务(第一次作业)
  • 【Servo】伺服驱动器扫频功能方案文档
  • 微信小程序地理定位功能
  • 批判式微调(CFT):原理、架构与高效推理训练新范式
  • ubuntu系统+N卡 | docker compose+ollama+dify
  • Springboot绑定Date类型时出现日期转换异常问题