1. 进程概念与进程控制块(PCB)

1.1 进程的定义

进程是操作系统对正在运行的程序的一种抽象,是系统进行资源分配和调度的基本单位。从内核视角看,进程是一个执行上下文,包含程序代码、数据、堆栈、寄存器状态以及打开的文件描述符等资源。

核心定义:进程是程序在某个数据集合上的一次执行活动,是系统资源分配和独立调度的最小单位。

1.2 进程与程序的区别

进程和程序是操作系统中最容易被混淆的两个概念,二者存在本质差异:

对比维度 程序 进程
本质 静态的指令集合,存储在磁盘上的文件 动态的执行实例,存在于内存中
生命周期 永久存在(除非被删除) 从创建到终止,具有时效性
资源占用 仅占用磁盘空间 占用内存、CPU、文件等系统资源
并发关系 一个程序可对应多个进程 一个进程只能对应一个程序
组成结构 代码 + 数据(静态) 代码 + 数据 + 堆栈 + PCB + 上下文
记忆要点:程序是菜谱(静态),进程是按照菜谱做菜的过程(动态)。同一个菜谱可以同时被多个厨师使用,对应一个程序可以产生多个进程。

1.3 进程控制块(PCB)概述

PCB(Process Control Block)是操作系统用于描述和管理进程的数据结构,内核通过PCB感知和控制每个进程。在Linux中,PCB由 task_struct 结构体实现,它是内核中最庞大的数据结构之一。

Linux 进程控制块 (task_struct) 核心字段结构 task_struct 进程标识 • pid (进程ID) • tgid (线程组ID) • comm (进程名称) • credential (凭证) 状态与调度 • state (进程状态) • prio/static_prio • sched_info • se (调度实体) 内存管理 • mm (内存描述符) • active_mm • brk/start_brk • stack (内核栈) 文件与文件系统 • fs (文件系统信息) • files (打开文件表) • signal (信号处理) • sighand 进程关系 • parent (父进程) • children (子进程链表) • sibling (兄弟进程) • real_parent 时间与统计 • utime/stime (CPU时间) • start_time • nvcsw/nivcsw (切换次数) • last_switch task_struct 约含 300+ 字段,是内核中最大、最核心的数据结构

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 进程生命周期状态转换

Linux 进程状态转换图 TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE __TASK_STOPPED EXIT_ZOMBIE EXIT_DEAD 等待资源 I/O等待 SIGSTOP 资源就绪/信号 I/O完成 SIGCONT exit() wait() 进程退出后先进入僵尸状态,父进程调用 wait() 后进入死亡状态

1.6 进程的创建与终止流程

Linux中进程的创建通过 fork() 系统调用实现,终止通过 exit() 系统调用实现。核心流程如下:

  1. 创建阶段:调用 fork() → 内核分配 task_struct → 复制父进程资源(写时复制优化) → 设置子进程状态为 TASK_RUNNING → 加入调度队列
  2. 运行阶段:调度器选择进程 → 上下文切换 → 进程获得CPU执行权
  3. 终止阶段:调用 exit() → 释放大部分资源 → 状态变为 EXIT_ZOMBIE → 向父进程发送 SIGCHLD 信号
  4. 回收阶段:父进程调用 wait()/waitpid() → 读取子进程退出状态 → 释放 task_struct → 子进程彻底消亡
关键设计思想:Linux将进程终止分为"僵尸"和"死亡"两个阶段,目的是让父进程有机会获取子进程的退出状态(exit code),这是Unix/Linux进程管理的重要设计哲学——父进程对子进程的"身后事"负有责任。

1.7 本章小结

  • 进程是动态的执行实例,程序是静态的指令集合,二者有本质区别
  • PCB是操作系统管理进程的核心数据结构,Linux中由 task_struct 实现
  • task_struct 包含进程标识、状态、调度、内存、文件、信号、时间统计等300+字段
  • 进程状态转换遵循严格的状态机模型,僵尸状态是进程退出的必经中间状态
  • 进程家族树以 init 为根,通过 parent/children/sibling 指针维护层级关系