前言上一篇我们完整讲解了 Linux 文件系统与目录操作的核心内容从本篇开始正式进入系统编程的核心模块 —— 进程管理。进程是操作系统分配资源的基本单位也是多任务并发的核心载体。本篇从进程的本质概念出发深度拆解 fork 创建进程的底层机制详解写时复制的核心原理梳理父子进程的资源共享与差异是理解多进程并发、后续进程控制与通信的前置基石。一、进程核心概念1. 程序与进程的区别程序存放在磁盘上的可执行文件是静态的代码与数据的集合永久存在不占用系统运行资源。进程程序的一次运行过程是操作系统分配资源的基本单位是动态的有独立的生命周期与运行状态。简单来说同一个程序可以同时启动多个独立的进程每个进程拥有独立的内存空间与运行上下文互不干扰。比如同时打开两个终端窗口执行ls命令就是同一个程序对应两个独立的进程。2. 进程控制块PCB操作系统内核通过进程控制块Process Control Block管理系统中所有的进程它是描述进程的核心结构体记录了进程的全部属性与状态信息。在 Linux 中PCB 的具体实现是task_struct结构体。核心包含以下关键信息进程标识进程 IDPID、父进程 IDPPID、用户 ID、组 ID进程状态运行、就绪、阻塞、停止、僵尸等状态标记内存指针指向进程的虚拟地址空间包含代码段、数据段、堆、栈的位置文件描述符表进程打开的所有文件的索引表寄存器上下文进程切换时保存的 CPU 寄存器状态用于恢复执行调度优先级进程调度的优先级与时间片信息3. 进程的三种基础状态进程在整个生命周期中会在不同状态之间切换由操作系统的调度器统一管理运行态当前正在占用 CPU 执行指令单核 CPU 同一时间只有一个进程处于运行态就绪态已经准备好运行等待 CPU 调度分配时间片阻塞态等待某一事件发生如 IO 完成、信号、锁暂时不参与 CPU 调度状态转换的核心逻辑运行态的进程时间片用完会回到就绪态运行态的进程发起 IO 等阻塞操作会进入阻塞态IO 完成后阻塞态进程会被唤醒进入就绪态等待调度。4. 进程 ID 与基础函数每个进程都有唯一的非负整数 PID作为进程的系统标识。#include sys/types.h #include unistd.h pid_t getpid(void); // 获取当前进程的PID pid_t getppid(void); // 获取当前进程的父进程PIDLinux 中有两个特殊的进程PID 0idle 空闲进程内核启动时创建CPU 空闲时运行PID 1init/systemd 进程是所有用户态进程的祖先负责系统初始化与孤儿进程收养二、进程创建fork 函数深度解析fork是 Linux 创建进程的核心系统调用也是笔试面试的 100% 高频考点。1. 函数原型与核心特性#include unistd.h pid_t fork(void);返回值规则调用一次返回两次给父进程返回新创建的子进程的 PID大于 0 的整数给子进程返回0创建失败返回-1设置 errno执行 fork 后内核会创建一个全新的子进程父子进程各自独立运行返回值分别在两个进程的地址空间中返回因此可以通过返回值的不同进入不同的代码分支。2. 基础代码示例#include stdio.h #include unistd.h int main(void) { printf(进程启动当前PID%d\n, getpid()); pid_t pid fork(); if (pid -1) { perror(fork failed); return 1; } else if (pid 0) { // 子进程执行分支 printf(我是子进程PID%d父进程PID%d\n, getpid(), getppid()); } else { // 父进程执行分支 printf(我是父进程PID%d子进程PID%d\n, getpid(), pid); } // 父子进程都会执行到这里 printf(PID%d 程序结束\n, getpid()); return 0; }执行说明fork 之后父子进程的执行顺序完全由操作系统调度器决定谁先运行、谁先结束都不确定编程时不能依赖任何固定的执行顺序。3. 写时复制Copy On Write写时复制是 fork 的核心底层机制也是面试必考题。fork 创建子进程后不会立刻完整复制父进程的全部物理内存而是让父子进程共享同一份物理内存页面内核将内存页面设置为只读权限。只有当任意一方尝试修改内存时内核才会为修改的页面复制一份独立的副本让两个进程各自拥有独立的内存修改互不影响。核心优势极大提升了 fork 的执行效率避免了全量内存拷贝的巨大开销绝大多数场景下fork 后子进程会立刻执行 exec 程序替换全量拷贝完全是浪费写时复制完美适配这个场景只读数据如代码段全程共享进一步节省内存资源4. 父子进程的资源异同资源维度父子进程关系代码段完全共享内容完全一致全局数据、堆、栈写时复制修改前共享修改后各自独立文件描述符表复制一份共享同一个文件表项文件偏移量共享进程 ID各自独立PID、PPID 均不同内存映射区域写时复制共享信号处理方式子进程继承父进程的信号处理方式缓冲区数据父进程的用户态缓冲区会被子进程复制三、父子进程的文件共享特性结合之前的文件 IO 知识fork 之后父子进程的文件描述符共享是非常重要的特性也是高频易错点。1. 底层共享机制fork 创建子进程时会复制父进程的文件描述符表但两个进程中相同编号的 fd会指向内核中同一个文件表项。这意味着父子进程共享同一个文件读写偏移量一方写入数据后另一方的文件指针会同步后移一方修改文件状态标志另一方也会受到影响2. 代码验证示例#include stdio.h #include unistd.h #include fcntl.h #include string.h int main(void) { int fd open(test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd -1) { perror(open failed); return 1; } pid_t pid fork(); if (pid 0) { // 子进程写入 write(fd, hello child\n, 12); printf(子进程写入完成\n); close(fd); return 0; } // 父进程等待子进程写完 sleep(1); // 父进程写入会接在子进程内容之后 write(fd, hello parent\n, 13); printf(父进程写入完成\n); close(fd); return 0; }执行后文件中会同时存在两行内容不会互相覆盖证明两者共享文件偏移量。如果需要各自独立的写入位置需要在 fork 后各自重新打开文件。四、拓展vfork 与 fork 的区别vfork 是早期的进程创建函数在写时复制机制普及之前用于解决 fork 全量拷贝的性能问题现在实际开发中已极少使用但面试偶尔会问到。对比维度forkvfork地址空间写时复制共享物理内存完全共享父进程的地址空间不复制执行顺序父子进程调度顺序不确定子进程先运行父进程阻塞直到子进程 exit 或 exec数据修改写时复制后各自独立子进程修改数据直接影响父进程适用场景通用进程创建专门为 fork 后立刻 exec 的场景优化现状主流标准用法已被淘汰不推荐使用注意vfork 的子进程中不能修改父进程的数据也不能在 main 函数中 return 返回必须调用_exit 退出否则会破坏父进程的栈结构。五、面试高频考点与易错坑点1. 经典面试问答Q1程序和进程有什么本质区别答 程序是存放在磁盘上的静态可执行文件是代码和数据的集合永久存在不占用运行资源 进程是程序的一次运行过程是动态的有自己的生命周期是操作系统分配资源的基本单位占用内存、CPU 等系统资源。 同一个程序可以对应多个独立的进程每个进程有独立的内存空间。Q2fork 函数有什么核心特点什么是写时复制答 fork 的核心特点是一次调用、两次返回给父进程返回子进程 PID给子进程返回 0通过返回值区分父子执行分支。 写时复制是 fork 的底层内存机制fork 后父子进程共享物理内存内存设为只读只有当任意一方尝试修改内存时内核才为修改的页面复制独立副本各自拥有独立的内存。它大幅提升了 fork 的效率避免了无意义的全量内存拷贝。Q3fork 之后父子进程哪些资源是共享的哪些是独立的答 共享的资源代码段全程共享数据段、堆、栈写时复制修改前共享文件描述符指向同一个文件表项共享文件偏移量。 独立的资源进程 ID、进程状态、独立的进程控制块内存修改后各自独立每个进程有自己的寄存器上下文。Q4fork 之后父子进程的文件描述符有什么特性会带来什么影响答 fork 会复制父进程的文件描述符表父子进程相同编号的 fd 指向内核中同一个文件表项共享文件偏移量和文件状态。 带来的影响一方写入数据后另一方的文件指针会同步移动接续写入不会覆盖如果需要独立读写需要在 fork 后各自重新打开文件。Q5fork 和 vfork 有什么核心区别答地址空间fork 采用写时复制内存只读共享vfork 完全共享父进程地址空间不复制。执行顺序fork 父子进程调度顺序不确定vfork 子进程先运行父进程阻塞直到子进程退出或执行 exec。数据影响fork 修改数据触发写时复制互不影响vfork 子进程修改数据会直接影响父进程。 现在写时复制的 fork 性能已经很好vfork 基本被淘汰。2. 常见易错坑点误以为 fork 后父子进程有固定的执行顺序实际由操作系统调度决定具有随机性误以为 fork 后文件描述符的偏移量各自独立多进程写文件时出现数据错乱认为子进程修改全局变量会影响父进程忽略写时复制机制修改后内存已经独立忘记判断 fork 的返回值不区分父子分支导致逻辑混乱子进程中使用 vfork 后直接 return 返回破坏父进程栈结构导致程序异常崩溃fork 前使用 printf 打印不带换行的内容fork 后子进程复制缓冲区导致输出重复两次父进程不回收子进程子进程退出后变成僵尸进程占用系统 PID 资源以上就是 Linux 进程基础与 fork 创建的全部核心内容。理解进程的本质与创建机制是后续学习进程控制、信号、进程间通信的前置基础。下一篇我们将讲解进程的退出与资源回收详解 wait/waitpid、exec 函数族以及僵尸进程、孤儿进程的核心原理。制作不易如果对你有用希望能点赞收藏支持一下。