🖥️ 操作系统进程结构详解

深入剖析进程控制块(PCB)、进程状态转换、内存布局、线程结构、文件描述符、调度队列及进程间通信机制

📋

进程控制块 (PCB - Process Control Block)

🔧 标识符信息
PID
进程唯一标识符
PPID
父进程标识符
UID
用户标识符
GID
组标识符
⚙️ 处理器状态信息
PC / IP
程序计数器
PSW
程序状态字
SP
堆栈指针
通用寄存器
RAX, RBX, RCX...
📦 进程控制信息
状态
就绪/运行/阻塞
优先级
静态/动态优先级
调度信息
时间片、调度策略
资源统计
CPU时间、运行时间
🔗 内存管理与资源信息
基址/限长寄存器
内存基址、页表指针
文件描述符表
FD[0-1023]
地址空间
虚拟内存布局指针
信号掩码
阻塞/挂起信号集
🔄

进程状态转换图

新建态NEW
就绪态READY
运行态RUNNING
阻塞态WAITING
终止态TERMINATED
新建 → 就绪
进程被创建,初始化完成
就绪 → 运行
调度器选中,CPU执行
运行 → 就绪
时间片耗尽或被抢断
运行 → 阻塞
等待I/O或资源
阻塞 → 就绪
I/O完成,事件就绪
运行 → 终止
正常退出或被杀死
🧠

进程内存布局 (Process Memory Layout)

内核空间
Kernel Space
栈 (Stack)
函数调用、局部变量
内存映射区
动态库、文件映射
堆 (Heap)
malloc/new 动态分配
BSS 段
未初始化全局/静态变量
Data 段
已初始化全局/静态变量
Text 段
代码指令、只读
保留区域
Reserved

🔒 内核空间 (Kernel Space)

操作系统内核代码运行区域,拥有最高权限。包含系统调用、驱动程序、内核数据结构等。

📚 栈 (Stack)

后进先出(LIFO)数据结构,存储函数调用栈帧、局部变量、函数参数、返回地址。栈向低地址增长,由编译器自动管理。

📍 内存映射区 (Memory Mapping)

用于内存映射文件、动态链接库等。可用于匿名映射实现进程间共享内存。

📊 堆 (Heap)

动态内存分配区域,通过 malloc/new 手动申请,free/delete 释放。堆向高地址增长,由程序员手动管理。

📦 BSS 段 (Block Started by Symbol)

存放未初始化的全局变量和静态变量。程序加载时此区域被操作系统初始化为0。节省磁盘空间。

💾 Data 段 (Data Segment)

存放已初始化的全局变量和静态变量。可读可写,程序加载时从可执行文件中读取初始值。

💿 Text 段 (Code Segment)

存放可执行程序的机器指令。只读区域,防止程序意外修改自身代码。多个进程可共享同一份代码段。

🧵

进程与线程结构 (Process vs Thread)

🖥️ 进程 Process

PID: 1234
独立地址空间
资源拥有者

T1 (主)
栈: 独有
寄存器: 独有
PC: 独有
FD: 共享
堆: 共享
T2
栈: 独有
寄存器: 独有
PC: 独有
FD: 共享
堆: 共享
T3
栈: 独有
寄存器: 独有
PC: 独有
FD: 共享
堆: 共享

🔑 进程 vs 线程 核心区别

进程 (Process)
  • 独立的虚拟地址空间
  • 独立的资源拥有者
  • 进程间隔离,通信开销大
  • 创建/销毁开销较大
  • 需要 IPC 机制通信
  • 切换时需要切换地址空间
线程 (Thread)
  • 共享进程的地址空间
  • CPU 调度的基本单位
  • 同一进程内线程共享资源
  • 创建/销毁开销小
  • 可直接通过共享内存通信
  • 切换时无需切换地址空间
📁

文件描述符表 (File Descriptor Table)

FD类型指向
0标准输入 stdin/dev/tty, /dev/pts/0
1标准输出 stdout/dev/tty, /dev/pts/0
2标准错误 stderr/dev/tty, /dev/pts/0
3普通文件inode: /home/user/data.txt
4管道 Pipepipe: [0] ←→ [1]
5SocketTCP: 192.168.1.100:8080
...更多描述符最大 1024 (Linux)

📖 什么是文件描述符?

一个非负整数(int),内核用 fd 来追踪进程打开的文件。每个进程有自己的文件描述符表,指向内核中的文件表项。

🔗 三层映射结构

进程 fd 表内核文件表VFS inode

同一文件可被多次打开,每次获得不同的 fd,但共享相同的文件表项(文件偏移量)。

🔄 继承机制

fork() 子进程继承父进程的文件描述符。exec() 保持继承的 fd,除非设置 close-on-exec 标志。

🛠️ 常用操作

open(), close(), read(), write(), dup(), dup2(), pipe(), socket(), accept()

📊

进程调度队列结构

📋 就绪队列 (Ready Queue)

P1nginx worker
P3python script
P5node server
P8compiler

⏱️ 等待队列 (Waiting Queues)

磁盘 I/O
P2P7
网络 I/O
P4P6P9
信号等待
P10

🔄 调度器工作流程

就绪队列
🟢
⚡ CPU
完成/阻塞
🔴
调度算法
FCFS SJF RR 优先级 多级队列
🔗

进程间通信机制 (IPC - Inter-Process Communication)

📨

管道 (Pipe)

半双工通信,分为匿名管道(亲缘进程)和命名管道(FIFO)。数据流动,单向传输。

✓ 简单易用,速度快
✗ 单向,需两个管道双向通信
🌐

Socket 套接字

网络通信基础,支持本地(Unix Domain)和网络(TCP/UDP)。全双工通信,可跨主机。

✓ 功能强大,可跨主机
✗ API 较复杂,开销较大
📝

共享内存 (Shared Memory)

最快 IPC 方式,多进程映射同一块物理内存。需要信号量同步。

✓ 速度最快,无需拷贝
✗ 需同步机制,复杂
📬

消息队列 (Message Queue)

内核维护的消息链表,发送/接收格式化消息。异步通信,有边界。

✓ 异步,有边界,易解耦
✗ 有容量限制,不适合大数据
🚦

信号 (Signal)

软件中断机制,用于通知进程异步事件。如 SIGINT, SIGKILL, SIGSEGV。

✓ 轻量,即时响应
✗ 信息量少,异步处理复杂
🗺️

内存映射 (mmap)

将文件或设备映射到进程虚拟地址空间,也可用作进程间共享内存。

✓ 与文件结合,持久化
✗ 需处理同步问题
🔨

进程创建流程 (Process Creation)

1

fork() 系统调用

父进程调用 fork(),内核创建子进程。复制父进程的 PCB(PID 除外)、用户空间(写时复制 Copy-On-Write)、文件描述符表等。

2

分配 PID

内核为新进程分配唯一的进程标识符(PID)。查找 PID 表,确保不重复。PID 通常递增,达到上限后回绕。

3

复制/共享资源

复制父进程的资源:页表、文件描述符(共享同一文件表项)、信号处理表、调度信息。根据 clone() 标志决定共享或复制。

4

设置 PCB

初始化进程控制块:设置进程状态为 READY、优先级、父子关系(PPID)、用户/组ID、统计信息等。

5

加入就绪队列

将新进程的 PCB 加入就绪队列,等待调度器选中。返回两次:父进程返回子进程 PID,子进程返回 0。

6

exec() 可选

如果需要执行新程序,子进程调用 exec() 替换自己的地址空间为新程序的代码段、数据段等。

🔀

上下文切换 (Context Switch)

🔄 上下文切换示意图

⚡ CPU 执行
正在运行: P1 (PID: 1001)
⏸️ 中断/系统调用
保存 P1
PCB #1
寄存器 →
恢复 P2
PCB #2
→ 寄存器
⚡ 调度切换
⚡ CPU 执行
正在运行: P2 (PID: 1005)

📦 什么是上下文?

进程的上下文 = 进程执行时的状态快照,包括:

  • CPU 寄存器:程序计数器(PC)、堆栈指针(SP)、通用寄存器
  • 内核栈:内核态堆栈,用于系统调用
  • 进程控制块(PCB):进程状态、调度信息、资源信息
  • 内存管理信息:页表基址、段表指针

⚙️ 切换步骤

  • 1. 触发:时钟中断、I/O 中断、系统调用、异常
  • 2. 保存:将当前进程上下文存入 PCB
  • 3. 切换内核栈:切换到新进程的内核栈
  • 4. 恢复:从目标进程 PCB 恢复上下文
  • 5. 返回用户态:跳转到新进程上次停止位置

⚠️ 性能影响

  • 上下文切换开销约 1-100 微秒(CPU 类型决定)
  • 频繁切换会导致 CPU 效率下降
  • 线程切换比进程切换快(无需切换地址空间)
  • Linux 中使用 vsyscall/vDSO 优化常用系统调用

📌 图例说明

新建态
就绪态
运行态
阻塞态
终止态
内核空间
内存映射
BSS
Data
Text