目录
1.线程的概念
线程是进程内部的一个执行流(或称执行分支),是操作系统进行CPU调度的基本单位,其执行粒度比进程更细
不同的操作系统对此概念的实现策略不同:
- Windows操作系统采用了严格区分的架构,为线程(如ETHREAD结构)和进程(如EPROCESS结构)设计了独立的内核数据结构和完整的调度管理机制
- Linux操作系统则采用了独特而高效的设计哲学:复用进程模型来模拟线程 。内核并不为"线程"创建全新的数据结构,而是直接复用表示进程的task_struct,并让多个这样的结构体共享同一份进程地址空间、文件表等资源,从而在逻辑上表现为多个线程。这种实现被称为轻量级进程(LWP)。
CPU和内核调度器并不关心被调度的对象是"进程"还是"线程",它们只认"可调度的执行流"。所以基于**"线程的许多资源管理与调度需求与进程高度相似"**这一洞察,Linux选择复用进程的框架,避免了代码冗余,使得代码维护成本成本降低,不容易出错,所需测试工作较少,系统的整体健壮性较强
操作系统概念是理论指导,不同系统实现是具体工程实践
理论指导工程实践,二者相结合更有利于理解理论
线程相较于进程的优点
1. 轻量化
创建快:无需复制地址空间等资源,仅分配少量私有数据(栈、task_struct)。
切换快:在同一地址空间内切换,无需更换页表和刷新TLB,CPU缓存热数据保留。
占用资源少:共享进程绝大多数资源,仅维护少量私有状态。
2. 强大的并行能力充分利用多核CPU:同一进程的多个线程可被调度到不同核心上真正并行执行,加速计算密集型任务。
3. 高效的通信机制共享内存,零拷贝通信:线程间通过直接读写内存通信,无需昂贵且复杂的进程间通信(IPC)。
4. 灵活的并发模型与性能提升提升响应性:在等待慢速I/O时,其他线程可继续执行计算任务,保持程序响应(尤其对交互式应用)。
优化任务结构:
计算密集型应用:将计算任务分解到多个线程,在多处理器系统上并行执行,缩短总计算时间。
I/O密集型应用:使用多个线程同时等待或处理不同的I/O操作(如网络请求、磁盘读写),将I/O等待时间重叠,显著提高系统吞吐量。
线程相较于进程的缺点
1.性能损失
当计算密集型线程数超过核心数时,性能损失主要来自于高频的上下文切换导致的缓存失效以及同步机制(锁)带来的额外开销
2.健壮性降低编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间缺乏保护
3.缺乏访问控制进程是访问控制资源的基本粒度,在一个线程中调用某些OS函数(如chdir)会对整个进程造成影响
4.编程难度提高编写与调试一个多线程程序比单线程程序困难得多
线程异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常 ,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
线程用途
合理的使用多线程,能提高CPU密集型程序的执行效率
合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现
2.Linux中的线程

Linux中,线程与进程的概念
线程是操作系统调度的基本单位,叫作一个执行流,也叫做轻量级进程
进程是承担分配系统资源的基本实体(进程 = 内核数据结构(多个执行流结构体+进程地址空间+页表) + 代码和数据)
操作系统以进程为单位分配资源,线程作为进程内的执行流,共享这些资源
Linux中,线程的特点任何执行流执行都需要资源,进程地址空间是进程的资源窗口,所以线程在 进程内部执行,本质是在进程的进程地址空间内运行
线程执行进程代码的一部分,所以线程的执行粒度要比进程细
线程的整个生命周期都比进程更加轻量化(线程创建时 仅需分配task_struct、独立的栈空间及寄存器上下文,无需复制 进程地址空间等核心资源;切换时 由于共享地址空间,无需切换页表和刷新MMU寄存器,且CPU缓存(Cache)的热数据得以部分保留,仅需切换线程私有的上下文,属于局部切换,进而大幅提升了切换效率)
在内核调度中,每个线程(task_struct)都是独立的调度实体。调度器直接为每个线程分配CPU时间,并根据其优先级、调度策略等参数进行公平调度。
用户视角:创建进程时拥有的第一个线程叫作主线程,后续的线程叫作新线程/子线程
内核视角:一视同仁线程共享进程的进程地址空间,还共享文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id等
线程也拥有自己的一部分数据:线程ID、一组寄存器、栈、errno、信号屏蔽字、调度优先级等(栈和寄存器是重点,栈意味着线程存在独立的调用链,是一个独立的执行流,寄存器意味着线程存在独立的上下文数据)

3.页表
以二级页表为例
页表结构

| 级 | 名称 | 索引位 | 条目数 | 每个条目(PDE/PTE)内容 |
|---|---|---|---|---|
| 第一级 | 页目录 | 虚拟地址[31:22] | 1024个 | 二级页表的物理地址 + 权限位(Present, R/W, U/S等) |
| 第二级 | 页表 | 虚拟地址[21:12] | 1024个 | 物理页框(Page Frame)的起始地址 + 权限位、访问位、脏位等 |
| 结果 | 物理页框 | - | - | 最终映射的4KB物理内存块 |
虚拟地址转换流程
以32位虚拟地址为例,32 = 10 + 10 + 12
高10位存储页目录索引;中10位存储页表索引;最后12位存储物理内存一页中的偏移量
页目录和页表共有1024个条目,物理内存按页划分,每一页通常为4KB大小
10位二进制数的范围是 [0, 1024);12位二进制数的范围是 [0, 4096);所以正好覆盖
虚拟地址 (32-bit):0x804A123
页目录索引 (Directory):10-bit (高10位: 0x201)
页表索引 (Table): 10-bit (中10位: 0x04A)
页内偏移 (Offset): 12-bit (低12位: 0x123)
转换流程如下:
- CPU通过CR3寄存器找到页目录物理基址
- 用目录索引(0x201)找到页目录项(PDE) → 内含二级页表物理地址
- 用页表索引(0x04A)找到页表项(PTE) → 内含目标页框物理地址
- 页框物理地址 + 页内偏移(0x123) = 最终物理地址
相关硬件与寄存器
- MMU (内存管理单元):CPU中负责地址转换的专用硬件
- CR3寄存器:存放当前进程页目录的物理基地址。进程切换时,内核必须加载新进程的页目录地址到此寄存器
- CR2寄存器:当发生缺页异常时,CPU会自动将引发异常的虚拟地址存入此寄存器,供操作系统异常处理程序使用
特性与计算
- 页大小:由偏移量位数决定。偏移量12位 → 页大小 = 2¹² = 4KB。
- 页表大小计算 (假设PDE/PTE为4字节):
- 单级页表条目数:2¹⁰ = 1024个
- 页目录大小:1024条目 × 4字节 = 4KB
- 一个二级页表大小:1024条目 × 4字节 = 4KB
- 单进程理论最大页表开销:1个页目录 + 1024个二级页表 = 4KB + 1024×4KB ≈ 4MB
- 关键:实际中任何进程都有页目录,但二级页表稀疏,大部分虚拟地址区域未使用,无需分配所有1024个二级页表,这是虚拟内存节省空间的核心
- 进程创建的"重"负担:创建进程(如fork())需要为新进程建立完整的页表结构。虽然采用写时复制(Copy-On-Write) 优化(仅复制页目录和二级页表结构,实际物理页共享并标记为只读),但仍需分配和初始化大量页表项
缺页中断与内存管理
- 触发条件:CPU转换地址时,发现页目录项或页表项的"Present"位为0,表示对应页表或物理页不在内存中
- 处理流程:
- CPU陷入内核,将异常地址存入CR2。
- 操作系统检查:是页表本身不存在(需分配页表页),还是页表项指向的物理页不存在(需从磁盘换入或分配新页)。
- 操作系统分配物理页、建立映射、将Present位置1。
- 返回用户态,CPU重新执行引发异常的指令,此时转换成功。
- 内存管理单位:操作系统以页 (Page,如4KB) 为单位管理物理内存和磁盘交换空间。
核心结论与拓展
- 页表的核心作用 :将连续的虚拟地址空间,灵活、稀疏地映射到可能不连续的物理内存页上,同时提供内存保护(通过权限位)。
- 为什么需要多级页表:单级页表需要连续分配4MB内存。多级页表是稀疏目录结构,仅为实际使用的虚拟地址区域分配下级页表,极大节省内存。
- 现代演进:x86-64架构使用四级页表应对64位地址空间。同时支持大页(如2MB、1GB) ,减少TLB(缓存最近的映射关系)压力,提升大数据块访问性能。
| 困境维度 | 单级页表 (灾难) | 二级页表 (解决方案) | 原理 |
|---|---|---|---|
| 启动开销 | 必须为每个进程立即分配完整的4MB。 | 只需分配一个4KB的页目录。所有二级页表初始为"不存在"。 | 按需分配。页目录项如同"章节目录",二级页表是"具体章节",只有程序访问到某个"章节"时,才分配对应的页表页。 |
| 内存利用率 | 极低。即使进程只使用1个页,也要占用4MB页表空间。 | 极高。一个使用10MB(约2560页)的进程,开销约为:1个页目录(4KB) + 约3个二级页表(12KB) = 16KB。 | 稀疏存储。只为实际存在的"地址孤岛"建立映射,广阔的"未使用海洋"在页目录中仅用一个"无效"条目表示。 |
| 连续性要求 | 必须找到连续的4MB空间,极易失败。 | 只需为页目录找4KB连续空间。各个二级页表是独立的4KB页,可分散在内存任何位置。 | 化整为零。将一个大连续块需求,分解为多个小块的、可分散的需求,极大地缓解了碎片化问题。 |
| 共享与保护 | 困难。整个页表是一个整体。 | 灵活。可以在页目录级或页表级设置权限。例如,让两个进程的页目录项指向同一个只读的二级页表(共享库代码)。 | 粒度控制。多级结构提供了不同粒度的控制点,便于实现精细的内存共享和保护策略。 |