1. 进程概念与进程控制块(PCB)
1.1 进程的定义
进程是操作系统对正在运行的程序的一种抽象,是系统进行资源分配和调度的基本单位。从内核视角看,进程是一个执行上下文,包含程序代码、数据、堆栈、寄存器状态以及打开的文件描述符等资源。
核心定义:进程是程序在某个数据集合上的一次执行活动,是系统资源分配和独立调度的最小单位。
1.2 进程与程序的区别
进程和程序是操作系统中最容易被混淆的两个概念,二者存在本质差异:
| 对比维度 | 程序 | 进程 |
|---|---|---|
| 本质 | 静态的指令集合,存储在磁盘上的文件 | 动态的执行实例,存在于内存中 |
| 生命周期 | 永久存在(除非被删除) | 从创建到终止,具有时效性 |
| 资源占用 | 仅占用磁盘空间 | 占用内存、CPU、文件等系统资源 |
| 并发关系 | 一个程序可对应多个进程 | 一个进程只能对应一个程序 |
| 组成结构 | 代码 + 数据(静态) | 代码 + 数据 + 堆栈 + PCB + 上下文 |
记忆要点:程序是菜谱(静态),进程是按照菜谱做菜的过程(动态)。同一个菜谱可以同时被多个厨师使用,对应一个程序可以产生多个进程。
1.3 进程控制块(PCB)概述
PCB(Process Control Block)是操作系统用于描述和管理进程的数据结构,内核通过PCB感知和控制每个进程。在Linux中,PCB由 task_struct 结构体实现,它是内核中最庞大的数据结构之一。
1.4 task_struct 核心字段深度解析
task_struct 定义在 include/linux/sched.h 中,以下按功能模块分类解析最关键的字段:
1.4.1 进程标识字段
// 进程标识相关
pid_t pid; // 进程ID,唯一标识一个进程
pid_t tgid; // 线程组ID,主线程的pid等于tgid
pid_t ppid; // 父进程ID(通过 parent 指针获取)
char comm[TASK_COMM_LEN]; // 进程名称,默认16字节
注意:在Linux中,线程被视为轻量级进程(LWP),同一线程组内的所有线程共享相同的
tgid,但各自拥有独立的 pid。这是Linux线程模型与其它操作系统的关键区别。
1.4.2 进程状态字段
// 进程状态
volatile long state; // 进程当前状态
/* 状态常量定义 */
#define TASK_RUNNING 0 // 可运行状态(就绪或正在运行)
#define TASK_INTERRUPTIBLE 1 // 可中断睡眠(等待资源)
#define TASK_UNINTERRUPTIBLE 2 // 不可中断睡眠(如等待I/O)
#define __TASK_STOPPED 4 // 暂停状态(收到SIGSTOP等)
#define __TASK_TRACED 8 // 被跟踪状态(如ptrace调试)
#define EXIT_DEAD 16 // 进程已退出,但PCB未释放
#define EXIT_ZOMBIE 32 // 僵尸状态(已退出,等待父进程回收)
| 状态 | 宏定义 | 说明 | 典型场景 |
|---|---|---|---|
| 运行 | TASK_RUNNING | 进程正在CPU上执行或等待调度 | 普通用户进程 |
| 可中断睡眠 | TASK_INTERRUPTIBLE | 等待某事件,可被信号唤醒 | 等待键盘输入 |
| 不可中断睡眠 | TASK_UNINTERRUPTIBLE | 等待硬件I/O完成,不可被信号打断 | 磁盘读写等待 |
| 暂停 | __TASK_STOPPED | 进程执行被暂停 | 收到SIGSTOP信号 |
| 僵尸 | EXIT_ZOMBIE | 进程已终止,但PCB未被父进程回收 | 子进程退出后未wait |
| 死亡 | EXIT_DEAD | 最终状态,即将被彻底销毁 | 父进程调用wait后 |
1.4.3 进程调度相关字段
// 调度优先级
int prio; // 动态优先级(调度时实际使用)
int static_prio; // 静态优先级(用户可调整)
int normal_prio; // 基于静态优先级和调度策略计算的优先级
unsigned int rt_priority; // 实时优先级(0~99,仅实时进程使用)
// 调度策略
unsigned int policy; // 调度策略:SCHED_NORMAL/SCHED_FIFO/SCHED_RR等
// 调度实体
struct sched_entity se; // 普通进程调度实体(CFS调度器使用)
struct sched_rt_entity rt; // 实时进程调度实体
1.4.4 进程关系与家族树
// 进程关系
struct task_struct __rcu *parent; // 父进程
struct list_head children; // 子进程链表头
struct list_head sibling; // 兄弟进程链表节点
struct task_struct __rcu *real_parent; // 真正的父进程(不考虑ptrace)
Linux内核通过上述字段构建了一棵进程家族树,所有进程以 init 进程(PID=1)为根节点,形成树形结构。每个进程通过 parent 找到父进程,通过 children 遍历所有子进程。
1.4.5 内存管理字段
// 内存描述符
struct mm_struct *mm; // 进程地址空间(用户空间)
struct mm_struct *active_mm; // 内核线程借用其他进程的mm
unsigned long start_brk, brk; // 堆的起始和当前边界
unsigned long start_stack; // 用户栈起始地址
关键理解:内核线程没有用户地址空间,其
mm 为 NULL,但 active_mm 指向上一个用户进程的 mm_struct,这是内核线程访问用户内存时的特殊设计。
1.4.6 文件与文件系统字段
// 文件系统信息
struct fs_struct *fs; // 当前工作目录、根目录等
struct files_struct *files; // 已打开的文件描述符表
// 信号处理
struct signal_struct *signal; // 共享的信号处理信息
struct sighand_struct *sighand; // 信号处理函数表
sigset_t blocked; // 被屏蔽的信号集
sigset_t real_blocked; // 实际被屏蔽的信号集
1.4.7 时间与统计字段
// CPU时间统计
u64 utime; // 用户态CPU时间(jiffies)
u64 stime; // 内核态CPU时间(jiffies)
u64 gtime; // 虚拟机客户机CPU时间
unsigned long nvcsw; // 自愿上下文切换次数
unsigned long nivcsw; // 非自愿上下文切换次数
struct timespec64 start_time; // 进程启动时间
1.5 进程生命周期状态转换
1.6 进程的创建与终止流程
Linux中进程的创建通过 fork() 系统调用实现,终止通过 exit() 系统调用实现。核心流程如下:
- 创建阶段:调用
fork()→ 内核分配task_struct→ 复制父进程资源(写时复制优化) → 设置子进程状态为TASK_RUNNING→ 加入调度队列 - 运行阶段:调度器选择进程 → 上下文切换 → 进程获得CPU执行权
- 终止阶段:调用
exit()→ 释放大部分资源 → 状态变为EXIT_ZOMBIE→ 向父进程发送SIGCHLD信号 - 回收阶段:父进程调用
wait()/waitpid()→ 读取子进程退出状态 → 释放task_struct→ 子进程彻底消亡
关键设计思想:Linux将进程终止分为"僵尸"和"死亡"两个阶段,目的是让父进程有机会获取子进程的退出状态(exit code),这是Unix/Linux进程管理的重要设计哲学——父进程对子进程的"身后事"负有责任。
1.7 本章小结
- 进程是动态的执行实例,程序是静态的指令集合,二者有本质区别
- PCB是操作系统管理进程的核心数据结构,Linux中由
task_struct实现 task_struct包含进程标识、状态、调度、内存、文件、信号、时间统计等300+字段- 进程状态转换遵循严格的状态机模型,僵尸状态是进程退出的必经中间状态
- 进程家族树以
init为根,通过parent/children/sibling指针维护层级关系