Linux 进程探索:从 PCB 管理到 fork() 的写时拷贝

1. 进程的本质与描述

1.1 进程的基本概念

  • 通俗定义

    • 程序的一个执行实例。

    • 正在执行的程序。

  • 内核观点 :担当分配系统资源(CPU时间,内存)的实体

  • 组成公式

text 复制代码
进程 = 内核数据结构对象 + 自己的代码和数据
进程 = PCB (task_struct) + 自己的代码和数据
  • 核心理解 :操作系统中每一个正在执行的程序(包括所有命令行工具、用户程序等)都是进程。操作系统对所有被加载到内存中的程序进行管理。

1.2 进程控制块(PCB)与"先描述,再组织"

  • PCB (Process Control Block) :操作系统为每个进程维护的一个数据结构 ,用于存放该进程的所有属性信息。在Linux中具体实现为 struct task_struct

  • 管理方式 :操作系统将所有进程的PCB通过数据结构(如链表)组织起来。因此,对进程的管理,就转化为对这个PCB链表的增、删、查、改

  • 代码示例

c 复制代码
// 简化的PCB结构示意
struct task_struct {
    // 标识符:唯一标识进程的ID(PID)
    // 状态:进程的运行状态(运行、就绪、阻塞等)
    // 优先级:进程调度的优先级
    // 程序计数器:即将执行的下一条指令地址
    // 内存指针:指向代码和数据的指针
    // 上下文数据:CPU寄存器的数据快照(用于进程切换)
    // I/O状态信息:进程使用的I/O设备和文件列表
    // 记账信息:CPU使用时间等统计信息
    // 链接指针:用于组织进程链表
    struct task_struct *next;
    // ... 其他属性
};

2. 进程标识与信息查看

2.1 getpid()getppid()

  • getpid():获取当前进程的进程ID (PID)

  • getppid():获取当前进程的父进程ID (PPID)

2.2 进程信息在 /proc 文件系统中的体现

  • Linux将正在运行的进程信息 映射到 /proc/[pid] 目录下。

  • 重要符号链接

    • cwd:指向进程的当前工作目录 。例如 fopen("d.txt", "w") 会在此目录下创建文件。

    • exe:指向进程对应的可执行程序文件的完整路径。

2.3 命令行解释器(Shell)与进程的关系

  • 用户登录后,操作系统会分配一个命令行解释器进程(如bash)

  • 用户在Shell中输入命令执行程序时,Shell进程会调用 fork() 创建子进程来运行该命令

  • 因此,我们执行的大多数命令进程的父进程ID (PPID) 通常就是当前Shell的PID

3. 进程的创建:fork()

3.1 fork() 的基本行为

  • 功能:创建一个新的子进程。

  • 调用方式pid_t fork(void);

  • 核心特点调用一次,返回两次。分别在父进程和子进程中各返回一次。

  • 返回值含义

    • 父进程 中:返回新创建的子进程的 PID(大于0)

    • 子进程 中:返回 0

    • 如果创建失败(例如内存不足),则在父进程中返回 -1 ,并设置 errno

3.2 为什么fork()会返回两次?

fork() 的核心功能是创建子进程,包括:

  1. 为子进程申请新的PCB

  2. 拷贝父进程的PCB信息给子进程。

  3. 将子进程的PCB插入进程链表调度队列

    • 当执行到 return 语句时,子进程已经被创建并可能已被调度 。因此,fork() 会在两个独立的进程上下文(父、子)中各执行一次返回操作,从而产生两次返回.

3.3 为什么同一个变量 id 能同时满足 id == 0id > 0 的条件,导致 ifelse 都能执行?

  • 父进程和子进程拥有各自独立的地址空间fork() 之后,子进程获得了父进程数据段的副本

  • 在父进程的地址空间中,id 被赋值为子进程的PID(>0)。

  • 在子进程的地址空间中,id 被赋值为0。

  • 因此,两个进程根据自己地址空间中的 id 值,执行了不同的代码分支 。从外部观察,就好像 ifelse 同时成立。

4. 进程的独立性与写时拷贝

4.1 进程的独立性

  • 核心原则进程是操作系统资源分配的基本单位,进程之间相互独立。一个进程的异常不应影响其他进程(尤其是父进程)的运行。

  • 体现在数据上:子进程创建后,理论上应拥有父进程数据的独立副本。

4.2 写时拷贝

  • 问题 :如果 fork() 时立刻复制父进程的全部数据,会造成巨大的内存和时间开销 ,而很多情况下子进程会立刻执行 exec() 加载新程序,导致复制的数据被丢弃。

  • 解决方案写时拷贝技术

  • 工作原理

    1. fork() 创建子进程时,并不立即复制父进程的数据(代码段、数据段、堆栈等)

    2. 内核将父子进程的地址空间映射到相同的物理内存页 ,并将这些页标记为只读

    3. 任何一个进程试图修改某块数据时,会触发一个页错误(Page Fault)。

    4. 此时,操作系统内核才真正地为要修改的进程复制该内存页,并修改其页表映射,使其指向新的副本。然后恢复进程执行,完成写操作。

  • 优势

    • 延迟拷贝 :避免了不必要的复制,大大提升了 fork() 的效率。

    • 节约资源:如果数据不需要修改,则父子进程可以一直共享同一份物理内存。

    • 保障独立:从进程视角看,每个进程都拥有自己独立的数据副本,符合进程独立性的要求。

5. 总结

  1. 进程是程序的执行实体 ,由PCB(task_struct)和代码数据组成。操作系统通过管理PCB链表来管理进程。

  2. 进程有唯一的PID,可以通过 /proc/[pid] 查看其详细信息。Shell是特殊的进程,负责创建其他用户进程。

  3. fork() 是创建进程的基本方式,其"调用一次,返回两次"的特性是实现多任务的基础。

  4. 写时拷贝是保证进程数据独立性的关键技术,它在效率和资源利用上达到了很好的平衡。

相关推荐
QiZhang | UESTC2 小时前
从基础 RoPE 到 YaRN:源码学习路线揭秘
pytorch·深度学习·学习
xuhaoyu_cpp_java2 小时前
MyBatis学习(五)
经验分享·笔记·学习·mybatis
宣宣猪的小花园.2 小时前
C语言重难点全解析:指针到内存四区
c语言·开发语言
域中四大2 小时前
rk3568中修改波特率
linux·运维
众少成多积小致巨2 小时前
GNU Make 核心指南
android·c++
ECT-OS-JiuHuaShan2 小时前
整体论体系定理,全球开放,无法绕过
人工智能·科技·学习·算法·生活
风曦Kisaki2 小时前
# Linux Shell 编程入门 Day01:Shell 基础认知、脚本编写规范、变量四大类型、数值运算
linux·运维·chrome
AI_661465972 小时前
副业平台收益效率评估:实验设计、指标体系与数据分析框架
经验分享·笔记
谭欣辰2 小时前
详细讲解 C++ 状压 DP
开发语言·c++·动态规划