2. Linux 进程与线程:进程控制块 PCB、线程轻量级特性、内核调度实体
在 DPDK 多核调度场景中,理解 Linux 内核如何管理进程与线程是进行任务绑定的前提。本章深入剖析进程控制块(PCB)、线程的轻量级特性以及内核调度实体(struct sched_entity)的核心机制,为后续的 CPU 亲和性设置与核间负载均衡打下坚实基础。
2.1 进程控制块(PCB)—— task_struct
Linux 内核使用 struct task_struct 来描述和管理每一个进程与线程。这个结构体是内核中最大的数据结构之一,包含了进程的所有信息,被称为进程控制块(PCB)。
核心要点: 在 Linux 中,线程被视为轻量级进程(LWP),它们与进程共享同一个
task_struct 描述符,但通过不同的标志位和资源指针来区分共享与私有资源。
PCB 的主要字段分类如下表所示:
| 分类 | 关键字段 | 说明 |
|---|---|---|
| 标识符 | pid, tgid |
pid 为线程 ID,tgid 为线程组 ID(即主进程 ID) |
| 状态 | state |
运行、可中断睡眠、不可中断睡眠、停止、僵尸等 |
| 调度信息 | se (struct sched_entity) |
CFS 调度器使用的调度实体,包含虚拟运行时间等 |
| 内存管理 | mm, active_mm |
指向进程地址空间描述符 struct mm_struct |
| 文件系统 | fs |
当前工作目录、根目录等信息 |
| 文件描述符 | files |
已打开文件的描述符表 |
| 信号处理 | signal, sighand |
信号处理函数与挂起信号 |
| CPU 亲和性 | cpus_allowed |
允许该任务运行的 CPU 位掩码,DPDK 绑核的核心操作对象 |
DPDK 视角: 当 DPDK 通过
rte_thread_set_affinity() 或 pthread_setaffinity_np() 绑定时,实际上是在修改 task_struct 中的 cpus_allowed 位掩码,从而限制内核调度器只能将任务分配到指定的 CPU 核心上。
2.2 线程的轻量级特性
Linux 线程(POSIX 线程,pthread)的实现基于轻量级进程(LWP)概念。与传统的 fork() 创建进程相比,线程在创建和切换时开销显著降低。
线程的轻量级特性体现在以下方面:
- 资源共享: 同一进程内的线程共享地址空间(
mm指针指向同一mm_struct)、文件描述符表、信号处理函数等资源。 - 创建开销小: 创建线程时不需要复制父进程的地址空间,只需分配一个新的
task_struct和内核栈。 - 切换速度快: 线程上下文切换仅需保存/恢复寄存器、程序计数器等少量状态,无需切换页表(TLB 刷新开销小)。
- 私有资源: 每个线程拥有独立的
pid、内核栈、信号掩码、errno 变量和调度策略。
性能陷阱: 虽然线程切换比进程切换快,但在 DPDK 轮询模式下,应尽量避免线程切换。DPDK 通常将每个线程永久绑定到一个核心上,并采用轮询模式(无阻塞),从而将上下文切换次数降为零。
进程与线程的对比表:
| 对比维度 | 进程(fork) | 线程(pthread_create) |
|---|---|---|
| 地址空间 | 独立副本(写时复制) | 共享同一地址空间 |
| 创建速度 | 慢(需复制页表) | 快(仅分配 task_struct) |
| 上下文切换 | 慢(TLB 刷新) | 快(无需切换页表) |
| 通信方式 | IPC(管道、消息队列、共享内存) | 直接读写共享内存 |
| 内核开销 | 高(每个进程独立 PCB) | 低(共享部分内核资源) |
| DPDK 使用场景 | 多进程模型(primary/secondary) | 多核分发模型(每个 lcore 一个线程) |
2.3 内核调度实体(struct sched_entity)
struct sched_entity 是 CFS(完全公平调度器)的核心数据结构,它嵌入在 task_struct 的 se 字段中。调度器不直接操作 task_struct,而是通过调度实体来进行调度决策。
调度实体的关键字段:
load:任务的负载权重,影响 CPU 时间分配比例。run_node:红黑树节点,用于将调度实体插入 CFS 运行队列的红黑树中。vruntime:虚拟运行时间,CFS 通过比较 vruntime 来选择下一个运行的任务。on_rq:标记该调度实体是否在运行队列上。exec_start:当前任务开始执行的时间戳。sum_exec_runtime:任务累计执行的实际时间。
调度流程: CFS 调度器每次选择
vruntime 最小的调度实体运行。当任务运行时,其 vruntime 以与负载权重成反比的速度增长,从而实现公平调度。
下面通过 SVG 图展示进程、线程与调度实体之间的关系:
2.4 调度实体与 CFS 红黑树
CFS 调度器使用红黑树来组织所有可运行的调度实体。红黑树的 key 是 vruntime,值最小的节点位于最左侧,即下一个被调度的任务。
调度实体在红黑树中的操作流程:
- 入队: 当任务变为可运行状态时,其调度实体被插入红黑树,位置由
vruntime决定。 - 选择: 调度器每次选择红黑树最左侧的节点(
vruntime最小)作为下一个运行任务。 - 运行: 任务运行时,其
vruntime随时间递增,递增速率与任务权重成反比。 - 出队: 任务运行完毕或主动让出 CPU 时,从红黑树中移除。
- 重新入队: 任务再次变为可运行状态时,重新计算
vruntime并插入红黑树。
DPDK 优化启示: 在 DPDK 绑核场景中,每个 lcore 上的线程通常设置为 SCHED_FIFO 实时调度策略,此时 CFS 红黑树不再参与调度,而是由实时调度器直接管理。这进一步减少了调度延迟,保证了 DPDK 数据面的确定性。
2.5 本章小结
本章深入剖析了 Linux 进程与线程的内核实现:
- PCB(task_struct) 是内核管理进程/线程的核心数据结构,包含调度、内存、文件、信号等所有信息。
- 线程的轻量级特性 源于共享地址空间和资源,创建和切换开销远小于进程。
- struct sched_entity 是 CFS 调度器的基本调度单位,通过
vruntime实现公平调度。 - cpus_allowed 位掩码是 DPDK 绑核操作的目标字段,直接限制任务可运行的 CPU 集合。
理解这些内核机制,是后续编写 CPU 亲和性脚本、实现 DPDK 多核调度优化的理论基础。