Linux 线程概念
目录
一、什么是线程
1. 线程的本质
2. 线程的独有资源
3. 进程与线程关系示意图
二、线程的优缺点
2.1 线程的优点
2.2 线程的缺点
三、线程的异常与用途
1. 线程异常
2. 线程用途
四、进程 VS 线程
1. 核心差异
2. 进程的多个线程共享的资源
3. 进程和线程的关系
五、二级页表
1. 为什么需要二级页表?
2. 二级页表如何工作?
3. 页表项的奥秘:不只是地址映射
4. 段错误背后的硬件机制
5. MMU:幕后功臣
总结:二级页表的精妙设计
一、什么是线程
1. 线程的本质
想象一个大型商场(进程)里有多个导购员(线程)。每个导购员可以独立服务顾客,但共享商场的公共资源(试衣间、收银台等)。线程也是如此:
-
执行路线:线程是程序内部的一条独立执行路径。
-
轻量级进程:在Linux中,线程被称为"轻量级进程"(LWP),因为它的PCB(进程控制块)比传统进程更精简。
-
资源共享:所有线程共享进程的内存空间(代码段、数据段、打开的文件等)。
- 简单来说,在一个程序里,一个执行路线就叫做线程。更准确地讲,线程是 “一个进程内部的控制序列”。
- 一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行。
- 在 Linux 系统中,从 CPU 的角度看,它看到的 PCB(进程控制块)比传统的进程更加轻量化。
- 透过进程虚拟地址空间,我们可以看到进程的大部分资源,而将这些资源合理分配给每个执行流,就形成了线程执行流。
2. 线程的独有资源
虽然共享大部分资源,但每个线程也有自己的"私人空间":
-
线程ID:唯一身份标识
-
寄存器集合:保存当前执行上下文
-
独立栈空间:用于函数调用和局部变量
-
错误状态:errno变量记录最近错误
-
信号屏蔽:自定义哪些信号被阻塞
-
调度优先级:影响CPU时间分配
3. 进程与线程关系示意图
-
进程是资源分配的单位:独立王国,拥有领土(内存)、法律(页表)和军队(资源)。
-
线程是执行的单位:王国中的骑士团,共享国土但各自征战。
-
轻量级进程(LWP):Linux的独特设计,用“小王国”模拟“骑士团”。
🌴线程的真相:共享资源的“轻量级员工”
如果创建新“进程”时只复制task_struct,并共享父进程的地址空间和页表,结果会怎样?
——这就是线程的诞生!
-
四个线程共享同一地址空间,就像同一部门的四个团队,共用办公室、打印机和文件柜。
-
每个线程有自己的task_struct(员工档案)、独立栈空间(个人工作台)和寄存器(当前任务进度)。
关键区别:
特性 | 进程 | 线程(轻量级进程) |
---|---|---|
资源分配 | 独立地址空间和页表 | 共享父进程地址空间和页表 |
创建成本 | 高(复制所有资源) | 低(仅复制task_struct) |
独立性 | 完全独立 | 共享大部分资源 |
🌴CPU眼中的世界:只认“执行流”
对CPU来说,调度单位是task_struct,无论它是进程还是线程:
-
单执行流进程:部门里只有一个员工(传统进程)。
-
多执行流进程:一个部门有多个团队协作(多线程进程)。
CPU不关心是进程还是线程,就像老板只关心任务是否完成,不关心是哪个团队做的。Linux将所有执行流视为轻量级进程,通过复用进程管理机制简化设计。
🌴为何Linux没有“真线程”?设计哲学的智慧
-
简化内核设计
Linux选择用进程模拟线程,避免为线程单独设计管理模块。想象一家公司如果同时管理部门和团队,需要两套制度,而Linux选择只管理“部门”,通过调整权限实现“团队”协作。 -
用户层封装
虽然内核没有线程系统调用,但用户可以通过pthread库创建线程。这就像公司提供“团队协作工具包”(pthread),让部门经理(程序员)轻松管理多个团队(线程),而无需修改公司制度(内核)。
二、线程的优缺点
2.1 线程的优点
1. 创建成本低
创建一个新线程的代价要比创建一个新进程小得多。就好比在工厂里,增加一个新的工作小组,比新建一个工厂要简单得多,不需要重新购置大量的设备和资源。
2. 切换速度快
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。这是因为线程共享进程的很多资源,切换时不需要重新加载这些资源,就像同一个工厂里的不同小组切换任务时,不需要重新布置整个工厂的环境。
3. 资源占用少
线程占用的资源要比进程少很多,这样在系统资源有限的情况下,可以更高效地利用资源,运行更多的任务。
4. 充分利用多处理器
在多处理器系统中,线程能充分利用多处理器的可并行数量,让多个线程在不同的处理器上同时运行,大大提高计算效率。
5. 提高 I/O 操作效率
在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务。比如,在下载文件的同时,还可以进行其他操作,不会因为等待下载而让整个程序卡住。
6. 优化计算密集型应用
对于计算密集型应用,为了能在多处理器系统上运行,可以将计算分解到多个线程中实现,从而加快计算速度。
7. 提升 I/O 密集型应用性能
对于 I/O 密集型应用,为了提高性能,可以将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作,让程序运行更加流畅。
2.2 线程的缺点
1. 性能损失风险
一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失。这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
2. 健壮性降低
编写多线程程序需要更全面更深入的考虑。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。换句话说,线程之间是缺乏保护的,一个线程的错误操作可能会影响到其他线程。
3. 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响。这就意味着,在多线程程序中,需要更加小心地控制对系统资源的访问,避免出现意外的情况。
4. 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多。多线程程序的逻辑更加复杂,需要考虑线程之间的同步、通信等问题,这对程序员的技术水平和经验要求更高。
三、线程的异常与用途
1. 线程异常
单个线程如果出现除零、野指针问题导致线程崩溃,进程也会随着崩溃。因为线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程。进程终止,该进程内的所有线程也就随即退出。所以在多线程程序中,需要特别注意异常处理,避免因为一个线程的问题导致整个程序崩溃。
2. 线程用途
合理的使用多线程,能提高 CPU 密集型程序的执行效率。比如在进行大规模数据计算、图像渲染等任务时,多线程可以让计算任务在多个处理器上并行执行,大大缩短计算时间。
同时,合理的使用多线程,能提高 I/O 密集型程序的用户体验。比如在生活中,我们一边写代码一边下载开发工具,这就是多线程运行的一种表现。在下载文件的同时,还可以继续进行其他操作,不会因为等待下载而让整个程序卡住,从而提高了用户的操作体验。
四、进程 VS 线程
1. 核心差异
- 进程是资源分配的基本单位,它拥有独立的内存空间、文件描述符等资源。
- 而线程是调度的基本单位,线程共享进程数据,但也拥有自己的一部分数据,如线程 ID、一组寄存器、栈、errno、信号屏蔽字、调度优先级等。
特征 | 进程 | 线程 |
---|---|---|
资源分配 | 独立内存空间 | 共享进程内存 |
创建开销 | 高(复制地址空间) | 低(仅需MB级资源) |
通信方式 | 管道/共享内存等 | 直接共享全局变量 |
容错性 | 一个进程崩溃不影响其他 | 线程崩溃导致进程终止 |
上下文切换速度 | 慢(需切换地址空间) | 快(仅寄存器切换) |
2. 进程的多个线程共享的资源
进程的多个线程共享同一地址空间,因此 Text Segment、Data Segment 都是共享的。
- 如果定义一个函数,在各线程中都可以调用;
- 如果定义一个全局变量,在各线程中都可以访问到。
除此之外,各线程还共享以下进程资源和环境:
- 文件描述符表
- 每种信号的处理方式(SIG_IGN、SIG_DFL 或者自定义的信号处理函数)
- 当前工作目录
- 用户 id 和组 id 等。
3. 进程和线程的关系
进程和线程的关系就像工厂和工作小组的关系。进程提供了线程运行的环境和资源,线程在进程的框架内执行具体的任务。多个线程协同工作,共同完成进程的目标。
五、二级页表
1. 为什么需要二级页表?
想象你要管理一个拥有40亿房间(4GB内存)的酒店,如果给每个房间分配一张独立门卡(单一级页表),需要40亿张卡,但酒店前台只有4GB的储物柜,显然放不下。于是,酒店设计了楼层+房间号的二级管理模式:
-
楼层号(前10位):定位到某一层(页目录)
-
房间号(中间10位):定位到该层的具体房间(页表)
-
偏移量(后12位):精确到房间内的具体位置(如床、桌子)
2. 二级页表如何工作?
1. 虚拟地址结构(32位)
字段 | 位数 | 作用 |
---|---|---|
页目录索引 | 10位 | 定位页目录中的条目 |
页表索引 | 10位 | 定位页表中的条目 |
页内偏移 | 12位 | 定位物理页内的具体字节 |
2. 映射过程
-
步骤1:用前10位在页目录中找到对应的页表地址
-
步骤2:用中间10位在页表中找到物理页框的起始地址
-
步骤3:用后12位偏移量在物理页框内找到具体字节
3. 内存消耗计算
-
页目录:1张表,含1024项(2^10),每项10字节 → 10KB
-
页表:最多1024张表,每张10KB → 总计约10MB
-
总内存占用:仅为单级页表的1/4000!
3. 页表项的奥秘:不只是地址映射
每个页表项(10字节)包含:
-
物理页框地址(20位):指向4KB物理页的起始地址
-
权限位:
-
读/写(R/W):控制是否允许修改(如只读代码段)
-
用户/内核(U/S):区分用户态和内核态访问权限
-
-
存在位(P):标记该页是否在物理内存中
4. 段错误背后的硬件机制
案例:尝试修改字符串常量char *str = "Hello"; str[0] = 'h';
-
虚拟地址通过页表查询,发现目标页的权限为只读(R=1, W=0)
-
MMU检测到写操作违反权限,触发硬件异常
-
操作系统收到异常,向进程发送
SIGSEGV
信号 -
进程默认处理方式是终止并生成核心转储文件
5. MMU:幕后功臣
-
硬件加速:MMU(内存管理单元)集成在CPU中,专用于地址转换
-
TLB缓存:缓存最近使用的页表项,加速查询(类似酒店前台记住常用楼层)
-
缺页中断:当访问的页不在内存时,MMU触发中断,由操作系统加载数据
总结:二级页表的精妙设计
通过“分而治之”的思想,二级页表:
-
节省内存:仅需10MB即可管理4GB地址空间
-
灵活扩展:支持按需分配页表(未使用的虚拟区域无需分配页表)
-
权限控制:硬件级保护内存安全