【Linux】线程概念与控制(1)
1. Linux 线程概念
线程是进程内的执行流,是操作系统调度的基本单位。在 Linux 中,线程与进程深度融合(通过 “轻量级进程” 实现),共享进程的地址空间、文件描述符等资源,同时具备独立的执行上下文(如寄存器、栈)。本节从内存管理、线程特性、优缺点等角度,解析 Linux 线程的本质。
1-1 什么是线程
线程是进程内部的控制序列,核心特点:
- 进程与线程的关系:进程是资源分配的基本单位,线程是调度的基本单位。一个进程至少包含一个线程(主线程)。
- 运行空间:线程在进程的地址空间内运行,共享进程的代码段、数据段、堆、文件描述符等资源。
- 轻量级 PCB:Linux 中,线程以 “轻量级进程(LWP)” 形式存在,内核的
PCB
(进程控制块)对线程的管理更轻量化(共享大部分进程资源,仅需维护独立的执行上下文)。
1-2 分页式存储管理:线程资源划分的基础
线程能共享进程资源,依赖分页式虚拟内存管理—— 通过虚拟地址、页表、物理内存的分层映射,实现 “进程地址空间共享,物理内存离散分配”。
1-2-1 虚拟地址与页表的由来
问题背景:若直接使用物理内存,进程需连续的物理空间,易导致 “内存碎片”(进程退出后,物理内存被分割为零散块,无法被大进程利用)。
解决方案:虚拟内存 + 分页:
- 物理内存分页:将物理内存划分为固定大小的 “页框(Page Frame)”,每个页框存一个 “物理页(Page)”(32 位系统通常为 4KB,64 位系统多为 8KB)。
- 虚拟地址空间:为每个进程分配独立的虚拟地址空间(32 位系统为 0~4GB),进程通过虚拟地址间接访问物理内存。
- 页表映射:操作系统维护 “页表”,记录虚拟页(虚拟地址划分的页)与物理页框的映射关系。
效果:虚拟地址连续,物理内存可离散分配,解决了内存碎片问题。
1-2-2 物理内存管理
Linux 内核用struct page
管理每个物理页,核心字段:
flags
:页的状态(如是否脏页、是否锁定)。_mapcount
:页被引用的次数(-1 表示未被内核引用,可分配)。virtual
:页的虚拟地址(高端内存可能为NULL
,需动态映射)。
内存开销:若物理内存为 4GB(4KB 页框),共需4GB / 4KB = 1,048,576
个struct page
。若每个struct page
占 40 字节,总开销为1,048,576 × 40B = 40MB
,仅占物理内存的 1%(可接受)。
1-2-3 页表:多级映射优化
单级页表的问题:32 位系统中,虚拟地址 4GB,4KB 页框需4GB / 4KB = 1,048,576
个页表项,每个项占 4 字节,总页表大小为4MB
,需4MB / 4KB = 1024
个连续页框 —— 既浪费内存,又违反 “离散分配” 的初衷。
多级页表(以二级为例):
- 将单级页表拆分为 “页目录表 + 页表”:
- 页目录表:含 1024 个项,每个项指向一个 “页表”。
- 页表:每个页表含 1024 个项,指向物理页框。
- 优势:进程仅需加载 “实际使用的页表”,无需加载全部 1024 个页表,节省内存(如 10MB 进程仅需 3 个页表)。
1-2-4 页目录与地址转换
- 页目录表:管理所有页表,其物理地址由
CR3
寄存器存储(每个进程的CR3
不同,实现地址空间隔离)。 - 地址转换流程:
- CPU 将虚拟地址拆分为 “页目录索引 + 页表索引 + 页内偏移”。
- 通过
CR3
找到页目录表,再通过 “页目录索引” 找到目标页表。 - 通过 “页表索引” 找到物理页框,结合 “页内偏移” 得到物理地址。
1-2-5 TLB:加速地址转换
多级页表虽节省内存,但增加了地址转换的 “查表次数”(二级页表需查 2 次表)。为加速转换,CPU 引入TLB(Translation Lookaside Buffer,转译后备缓冲器):
- TLB 是 “页表项的高速缓存”,存储近期使用的虚拟页→物理页映射。
- 工作流程:
- 地址转换时,先查 TLB:命中则直接获取物理地址。
- 未命中则查多级页表,同时将新映射存入 TLB(淘汰旧项)。
1-2-6 缺页异常:内存不足时的补救
当 CPU 访问的虚拟页无物理页映射(或权限错误)时,触发缺页异常(Page Fault),进入内核态处理:
- 硬缺页(Major Page Fault):物理内存中无对应页,需从磁盘(如交换分区、可执行文件)加载页到内存,再建立映射。
- 软缺页(Minor Page Fault):物理内存中有对应页(如其他进程共享的页),仅需建立映射。
- 无效缺页:访问越界(如空指针、非法地址),触发
SIGSEGV
信号,终止进程。
线程资源划分的真相
线程共享进程的虚拟地址空间,因此天然共享进程的代码、数据、堆等资源。操作系统只需为线程分配独立的栈空间(通常为 8MB)和寄存器上下文,即可实现 “进程内多执行流”。
1-3 线程的优点
相比进程,线程具备以下优势:
创建开销小:线程共享进程的地址空间、文件描述符等资源,创建时无需重复分配这些资源,仅需初始化栈和寄存器上下文,开销远小于进程。
切换开销小:
- 线程切换时,虚拟地址空间不变,无需刷新 TLB(页表缓存),仅需切换寄存器和栈。
- 进程切换需切换地址空间,刷新 TLB,开销是线程切换的数倍。
资源占用少:线程仅需独立的栈和少量控制结构,相比进程(需完整的地址空间、文件描述符表等),资源占用显著减少。
多核并行:多线程可在多核 CPU 上并行执行,提升计算密集型任务的效率。
IO 与计算重叠:IO 密集型任务中,线程可在等待 IO(如磁盘读写、网络传输)时,让其他线程继续执行计算,提升整体吞吐量。
1-4 线程的缺点
性能损失:若计算密集型线程数超过 CPU 核心数,线程切换(上下文切换)会产生额外开销,导致整体性能下降。
健壮性降低:线程共享进程资源,一个线程的错误(如野指针、除零)可能导致整个进程崩溃,缺乏进程级的隔离性。
缺乏访问控制:进程是系统 “访问控制的基本粒度”,线程无法独立设置资源权限(如文件权限、内存权限),一个线程的操作可能影响整个进程。
编程难度高:多线程需处理竞态条件、死锁、数据同步等问题,调试难度远高于单线程程序。
1-5 线程异常
线程是进程的 “执行分支”,线程异常会直接导致进程崩溃:
- 若线程出现 “除零错误”“野指针访问” 等,会触发内核的信号机制(如
SIGFPE
、SIGSEGV
),最终终止整个进程,进程内所有线程随之退出。
1-6 线程用途
提升 CPU 密集型任务效率:多线程可在多核 CPU 上并行计算,将大任务拆分为多个子任务,充分利用 CPU 资源。
优化 IO 密集型任务体验:如 “边下载文件边显示进度”,IO 线程等待网络 / 磁盘时,UI 线程可继续响应用户操作,提升交互体验。
总结
- 线程本质:进程内的轻量级执行流,共享进程地址空间,独立维护执行上下文。
- 内存基础:分页式虚拟内存管理让线程可高效共享进程资源,同时通过页表、TLB、缺页异常保证内存访问的灵活性与效率。
- 适用场景:CPU 密集型任务(多核并行)、IO 密集型任务(IO 与计算重叠),但需权衡线程切换开销与同步复杂度。
2. Linux 进程 VS 线程:资源共享与独占的边界
进程与线程的核心差异在于资源分配与调度粒度:进程是资源分配的基本单位,线程是调度的基本单位。线程作为进程内的执行流,共享进程的大部分资源,但也拥有独立的私有数据以保证执行独立性。
2-1 进程与线程的核心定位
- 进程:操作系统进行资源分配的基本单位(如内存、文件描述符、用户 ID 等),进程间相互独立,拥有各自的地址空间。
- 线程:操作系统进行调度的基本单位(CPU 时间片分配的对象),线程依附于进程存在,共享进程的资源。
2-2 线程的私有资源(独占)
线程作为独立的执行流,需要维护自身的执行状态,因此拥有以下私有资源:
线程 ID(TID)标识线程的唯一编号(类似进程 ID,但仅在进程内唯一),用于内核调度和线程管理。
寄存器上下文包括程序计数器(PC,记录下一条指令地址)、栈指针(SP)、通用寄存器等,用于线程切换时保存和恢复执行状态。
线程栈独立的栈空间(默认 8MB,可调整),用于存储局部变量、函数调用参数和返回地址,避免多线程执行时栈数据冲突。
errno 变量记录线程执行系统调用时的错误码(线程私有,避免多线程操作同一全局
errno
导致的混乱)。信号屏蔽字线程可独立设置对某些信号的屏蔽(如
sigprocmask
),不影响其他线程的信号处理。调度优先级线程有独立的调度优先级(如
nice
值),内核根据优先级分配 CPU 时间片。
2-3 线程共享的进程资源
线程属于进程的一部分,天然共享进程的大部分资源,这是线程轻量性的核心原因:
地址空间
- 共享代码段(Text Segment):进程的函数可被所有线程调用。
- 共享数据段(Data Segment)和堆(Heap):全局变量、静态变量、动态分配的内存(
malloc
/new
)可被所有线程访问。
文件描述符表进程打开的文件、套接字等描述符对所有线程可见,线程关闭一个描述符会影响其他线程。
信号处理方式进程对信号的处理动作(忽略
SIG_IGN
、默认SIG_DFL
、自定义函数)是全局的,所有线程共享(但线程可独立屏蔽信号)。当前工作目录进程的工作目录由所有线程共享,一个线程调用
chdir
会改变所有线程的工作目录。用户 ID(UID)和组 ID(GID)线程共享进程的用户身份,权限检查基于进程的 UID/GID,而非线程。
其他进程级资源如进程组 ID(PGID)、会话 ID(SID)、计时器、信号量等。
2-4 进程与线程的关系图示
2-5 对 “单进程” 的重新理解
之前学习的 “单进程” 本质是只包含一个线程的进程(主线程)。该进程的所有资源由主线程独占,执行流单一,无需考虑多线程同步问题。
总结
- 核心差异:进程是资源容器,线程是执行流。线程共享进程资源以减少开销,同时通过私有资源保证独立调度。
- 共享资源:地址空间、文件描述符、信号处理、工作目录等进程级资源。
- 私有资源:线程 ID、寄存器、栈、
errno
、信号屏蔽字、优先级等执行相关状态。 - 设计意义:线程的共享性降低了进程间通信成本,私有性保证了调度独立性,适合并发任务处理。