进阶 #os#process

进程的概念与状态转换

进程(Process)是正在运行的程序实例——程序是硬盘上的"菜谱",进程是锅里正在炒的"菜"

程序和进程有什么区别?

你手机桌面上有一个”微信”图标——那是程序(Program),是存放在硬盘上的一堆字节。当你点开它,微信开始运行——那是进程(Process)。

📖 程序:静态的,存在磁盘上的一串指令 进程:动态的,是程序的一次执行过程,有生命周期

类比:菜谱 vs 炒菜

程序 = 菜谱                 进程 = 炒菜
─────────────────           ─────────────────
• 静态的文字和图片         • 动态的烹饪过程
• 存在书架上(磁盘)       • 在锅里进行(内存)
• 不会变                   • 状态不断变化
• 可以复印多份             • 可以同时炒多份
  给不同的人看               用不同的锅

💡 一个程序可以启动多个进程——比如你开三个 Chrome 窗口,就是三个 Chrome 进程。

进程的构成

一个进程在内存中包含以下部分:

高地址
┌──────────────────────┐
│        栈(Stack)     │ ← 局部变量、函数调用、返回地址
├──────────────────────┤
│         ↓              │
│       闲置区域          │
│         ↑              │
├──────────────────────┤
│       堆(Heap)       │ ← 动态分配的内存(malloc)
├──────────────────────┤
│    数据段(Data)      │ ← 全局变量、静态变量
├──────────────────────┤
│    代码段(Text)      │ ← 程序的指令(只读)
└──────────────────────┘
低地址
组成部分存放内容大小变化
代码段CPU 执行的指令固定(只读)
数据段全局/静态变量固定
malloc 分配的内存动态增长/缩小
局部变量、函数调用动态增长/缩小

进程控制块(PCB)

操作系统为每个进程维护一个进程控制块(Process Control Block, PCB)——它是进程的”身份证”:

进程的 PCB 包含:
┌──────────────────────────┐
│  PID(进程 ID)           │ ← 唯一标识
│  状态(就绪/运行/阻塞)    │
│  程序计数器(PC)         │ ← 下一条指令地址
│  CPU 寄存器              │ ← 进程被切换时保存
│  内存地址空间             │ ← 页表基址
│  打开的文件列表           │
│  I/O 状态                │
│  调度信息(优先级等)      │
└──────────────────────────┘

🏫 类比:学生档案 每个学生入学就建一个档案(PCB),记录学号(PID)、当前状态(在读/休学/毕业)、成绩(寄存器值)、选课列表(打开的文件)等。学生毕业了档案也不会立即销毁——留着做统计。

进程的状态

一个进程在其生命周期中会经历不同的状态:

         ┌──────────────┐
         │    创建      │
         │    New      │
         └──────┬───────┘
                │  admitted(入队)

         ┌──────────────┐
    ┌─── │    就绪      │ ←─────────────┐
    │    │    Ready    │               │
    │    └──────┬───────┘               │
    │           │ dispatch(调度选中)    │
    │           ▼                       │
    │    ┌──────────────┐               │
    │    │    运行      │               │
    │    │   Running   │               │
    │    └──────┬───────┘               │
    │           │                       │
    │  timeout  │   I/O 或事件等待      │
    │(时间片到)│                       │
    │           ▼                       │
    │    ┌──────────────┐               │
    └────│    阻塞      │───────────────┘
         │    Waiting  │  I/O 完成或事件到达
         └──────────────┘
状态含义例子
新建(New)进程正在被创建你双击了微信图标
就绪(Ready)进程已就绪,等待 CPU排队等打饭
运行(Running)CPU 正在执行该进程正在打饭
阻塞(Waiting)进程在等某事件(I/O)等米饭蒸熟
终止(Terminated)进程执行完毕吃完饭走了

💡 三状态模型(就绪/运行/阻塞)是所有调度算法的基础。你会在进程调度算法中看到操作系统如何在这三个状态之间做决策。

进程如何被创建?

fork()——Unix/Linux 的进程创建方式

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("父进程 PID: %d\n", getpid());
    
    pid_t pid = fork();  // ← 关键系统调用
    
    if (pid == 0) {
        // 子进程
        printf("我是子进程!PID: %d\n", getpid());
    } else if (pid > 0) {
        // 父进程
        printf("我是父进程,刚生了个子进程 PID: %d\n", pid);
    } else {
        // fork 失败
        printf("fork 失败了!\n");
    }
    return 0;
}

运行结果:

父进程 PID: 1234
我是父进程,刚生了个子进程 PID: 1235
我是子进程!PID: 1235

💡 fork() 一次调用,两次返回——子进程从 fork 返回 0,父进程返回子进程的 PID。父进程和子进程的执行顺序是不确定的(取决于调度器)。

fork 的”写时复制”优化

传统 fork 会把父进程的整个内存空间复制给子进程——很慢。现代 OS 使用写时复制(Copy-on-Write, CoW)

fork() 刚发生时:
父进程 ──→ 内存页(标记为共享,只读)
子进程 ──→ 同一份内存页(只读)

子进程要写入时:

父进程 ──→ 原来的内存页
子进程 ──→ 新复制的内存页(可以写了)

💡 大多数 fork 之后紧接着就是 execve()(加载新程序)——如果之前真的复制了所有内存,那就白复制了。写时复制只在必要时才复制,大大加快了 fork 速度。

exec()——替换进程内容

fork() 创建了子进程,但子进程和父进程执行同样的代码。要执行不同的程序,需要 exec()

pid_t pid = fork();
if (pid == 0) {
    // 子进程中执行"ls"程序
    execlp("ls", "ls", "-l", NULL);
    // 如果 exec 成功,下面的代码不会执行
    printf("这行不会被打印\n");
}

💡 在 Unix/Linux 中,“创建新进程”的典型流程是 fork + exec:先 fork 复制自身,再 exec 加载新程序。Windows 用 CreateProcess 一步完成。

进程的终止

进程终止的原因:

  1. 正常退出:main 函数 return,或调用 exit()
  2. 错误退出:参数错误、文件不存在等
  3. 致命错误:段错误(Segmentation Fault)、除零等
  4. 被杀死:被其他进程 kill
#include <stdlib.h>
#include <signal.h>

exit(0);          // 正常退出
exit(1);          // 错误退出
abort();          // 异常终止
kill(pid, SIGKILL); // 杀死进程(从外部)

小结

概念要点
进程 vs 程序程序是静态的指令集合,进程是动态的执行实例
进程的组成代码段 + 数据段 + 堆 + 栈 + PCB
进程三状态就绪→运行→阻塞
fork()Unix/Linux 创建进程的方式,写时复制优化
exec()替换进程执行的程序
PCB进程的”身份证”,记录所有信息

为什么先学这个? 进程是操作系统管理 CPU 的基本单位——理解了进程是什么,才能理解 CPU 如何在进程间切换(上下文切换)和如何调度(进程调度算法)。