✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:Linux篇–CSDN博客
文章目录
- 线程概念
- 1.Linux中线程该如何理解
- 2.重新定义线程和进程
- 3.Linux下的线程管理
- 4.补充内容:重谈地址空间
- 5.线程周边概念
线程概念
1.Linux中线程该如何理解
一个进程要访问物理内存上的代码和数据,必须通过地址空间和页表映射,可以理解为地址空间就是进程的资源窗口。
任何执行流想要被CPU执行,都要有一份自己的资源,而地址空间属于进程的资源窗口。
所以一个执行流想要使用别人的资源,有两种方式:
1.给自己拷贝一份资源—这就是通过父进程创建子进程;
2.多个执行流共享同一个资源,每个都使用地址空间的一部分—这就是接下来要讲的线程。
结论:在Linux中线程是进程的一个执行分支,线程的执行粒度要比进程细,一个进程可以包含多个线程,这些线程共享进程的地址空间和系统资源。
如何理解这个执行粒度细?
目前只从资源角度来看就是一个进程使用一个地址空间,而多个线程共享使用一个地址空间。
此外在Linux中线程是在进程的地址空间内运行的,如果不在内部运行,那就是在一个独立的地址空间中运行,这就不是线程了,而是一个独立的进程。
2.重新定义线程和进程
线程是操作系统调度的基本单位;
进程是承担分配系统资源的基本实体,在操作系统内部是以进程为单位分配资源的。
之前讲解进程的时候总结过进程=内核数据结构(task_struct;页表等)+代码和数据,这个其实不太正确,但是当时还没讲线程,只能这么理解。
现在讲解线程后应该是进程=内核数据结构(多个task_struct;页表等)+代码和数据。
进程内部是包含线程,因为线程是进程内部的执行流资源,而一个task_struct
代表一个执行流资源,所以这就是为什么进程中实际应该是存在多个task_struct
的。
既然如此,又该如何理解之前学过的进程呢?
系统以进程为单位分配资源,只不过当前进程内部只有一个执行流(也就是一个task_struct
)!!!
一个进程包含多个执行流才是常规情况,只有一个属于特殊情况(我们学进程的时候其实就是特殊情况)。
当只有一个执行流时,这个执行流就是进程(实际上应该叫做主线程);多个执行流时,每个执行流就是一个线程。
CPU只有调度执行流的概念,并不关心这个执行流是进程还是线程,CPU只要找到代码和数据来执行就行了。
类比理解进程和线程:
- 进程=工厂(拥有独立场地,原料资源,生产线)
- 线程=车间工人(共享工厂资源,各自执行不同的生产任务)
3.Linux下的线程管理
系统内部存在大量的进程,而进程和线程的比例是1:n
,所以系统内部的线程数量一定会比进程还多,而线程的创建只是开始,系统还要调度,执行,切换线程,所以注定了系统要对线程做管理。
如何管理?先描述再组织。
在大多数的操作系统中(除了Linux)比如windows
系统都存在描述线程的数据结构----struct tcb
,但是维护进程的PCB
已经够复杂了,还要在此基础上维护线程tcb
以及进程PCB
之间的关系,所以维护的关系就会变得非常复杂。
而在Linux
系统中,线程和进程一样也要被调度,切换等,所以也要按照先描述再组织来进行管理线程。
但是在Linux系统中并没有为线程创建新的内核数据结构以及新的组织方法,而是直接复用进程的内核数据结构task_struct
和组织方法来模拟实现线程,所以在Linux中其实并没有真正的线程概念,而是被称为轻量级进程。
如果一个资源中只有一个task_struct
那就是进程,有多个task_struct
那就是线程。
正是Linux这种直接复用方式,让维护成本降低,代码更加简洁,健壮性比其他系统更强。
在大多数教材中都会说:Linux没有真正意义上的线程,而是用进程模拟的线程。
其实这句话说对也对,说不对也确实有点不对。首先,Linxu也是有真正意义上的线程,不能因为有新的内核数据tcb
把线程管理起来了就是有线程,而Linux没有创建新的那就是没有线程了;Linux也遵守了线程的定义以及先描述再组织的管理策略,只不过是复用进程的PCB
以及组织方式而已。
此外进程是系统资源分配的基本单位,线程是进程内部的一个执行流,并不能直接说用进程模拟线程,正确说法应该是用进程的内核数据结构来模拟的线程。
4.补充内容:重谈地址空间
页表的共享性
- 同一地址空间:同一进程的所有线程共享相同的虚拟地址空间,这意味着所有线程必须使用同一套页表来维护虚拟地址到物理地址的映射关系。
- CR3寄存器:在x86架构中,页表的基地址由控制寄存器CR3指向,同一进程的所有线程共享相同的CR3值,因此他们访问同一页表结构。
- 内存一致性:所有线程对内存的修改(如堆分配,全局变量写入)会通过共享的页表立即反映到其他线程,确保内存试图一致。
接下来就是讲解虚拟地址是如何通过页表转换到物理地址的,以32为虚拟地址为例。
1.地址划分
32为虚拟地址被划分为三部分:
- 页目录索引:高10位(位31-22)
- 页表索引:中间10位(位21-12)
- 页内偏移量:低12位(位11-0)
2.关键数据结构
页目录
- 大小:页目录索引就是页目录的下标,其中页目录索引一共有10位,对应2^10种情况,一个比特位是4字节,所以页目录大小就是
4*1024B=4KB
- 存储位置:物理内存上,基地址保存在CR3寄存器中(上面提到的页表基地址准确来说其实是页目录基地址)。
- 页目录项:每个页目录项存放的是一个页表基地址,一个目录项对应一个二级页表,一共有1024个页目录项,也就是有1024个二级页表。
二级页表
- 大小:页表索引是二级页表的下标,和页目录一样,一个二级页表大小是
4*1024B=4KB
;所有二级页表总大小就是4KB*1024=4MB
。 - 页表项:每个页表项存放的是一个页框地址(物理地址以4KB为一个页框进行划分)。
3.地址转化流程
步骤1:获取页目录基地址
根据CR3寄存器获取页目录的基地址,根据基地址找到对应的页目录。
步骤2:定位页目录项
根据虚拟地址划分的页目录索引,到页目录中找到对应的页目录项,获取页表基地址。
步骤3:定位页表项
根据获取到的页表基地址找到目标二级页表,再根据虚拟地址划分的页表索引,在目标二级页表中找到对应的页表项,获取到页框地址。
步骤4:转换为物理地址
根据获取到的页框地址在物理内存中找到目标页框,再根据虚拟地址划分的页内偏移量获取到物理地址:物理地址=页框地址+页内偏移量。
前面提到二级页表一共有1024个,一个大小是4KB,总大小就是4MB。但实际上一个进程可能仅使用少量二级页表,大部分二级页表都是未被分配。
主要原因:
1.一个进程的虚拟地址空间被划分为多个区域,这些区域通常分散在地址空间的不同位置,中间存在大量未使用的空隙,所以对应的页表项自然为空。
2.现代操作系统采用按需分页策略,仅在进程首次访问某个虚拟地址时,通过缺页中断动态分配物理页框和填充页表项;如果预先填满所有页表项,即使进程未实际使用物理内存,也会占用大量物理内存存放页表本身,所以按需分配显著减少内存开销。
3.保证内存安全:比如未使用的页表项通常标记为”不存在“,访问未分配的地址会触发缺页中断,内核可以拦截并终止非法访问;此外
还有写时拷贝,子进程共享父进程页表,但是页表项会被标记为只读,当任一进程尝试写入时,就会触发缺页中断,重新分配物理页框和填充页表项。
根据上面的内容,会重新了解页表这个内核数据结构,以及虚拟地址到物理地址的转换过程;所以实际上页表并不是只有一张表,而是采用多级页表的形式来实现映射的。
5.线程周边概念
1.线程比进程要更轻量化
主要体现在两点上:
-
创建和释放线程更加轻量化(生死)
线程只需要创建
task_struct
即可,而进程是需要分配整块资源,包括地址空间,页表等; -
线程切换更加轻量化(运行)
线程切换时只需要切换上下文和相关的数据结构即可;至于地址空间,页表等则不需要切换;
线程在执行本质上是一个进程在执行,因为线程是进程的一个执行分支。
以上两点共同体现了线程的整个生命周期更轻量化。
2.线程的切换效率更高
主要体现在两点上:
-
地址空间不变
切换进程时,需要更新CR3寄存器指向新进程的页目录基地址,新进程的数据布局不同,原有Cache(CPU上的一个缓存器)中的缓存数据可能无效,需要重新加载。
切换线程时,同一进程的线程共享地址空间,CR3寄存器无需变更;线程共享进程的代码段,堆和全局数据,这些数据可能仍驻留在Cache中。
-
数据共享性
进程切换的Cache失效:
- 冷启动效应:新进程的数据和代码可能分布在不同的物理地址区域,导致Cache Miss未命中。
线程切换的Cache亲和性:
- 共享热数据:线程共享进程的全局变量和堆内存,切换时部分Cache数据仍有效。
3.线程共享进程数据,但也会有自己的一部分数据
线程执行时也要有一组寄存器来保存上下文;栈是运行时数据,每个线程都要有自己的栈结构。
线程独立最重要的两个数据:
-
线程上下文
独立的上下文能体现出线程是独立被调度的;
-
栈结构
独立的栈结构能体现出线程运行时不会导致出现混乱,错乱;
上面两个表现的是线程的动态特性。
除了上面两个,线程还有一些其他数据是独立的:
比如线程ID,errno错误码,信号屏蔽字,调度优先级。
4.线程共享的资源
线程除了共享进程的代码段,堆和全局数据以外,还共享以下进程资源:
- 文件描述表
- 每种信号的处理方式
- 当前工作目录
- 用户id和组id
5.进程和线程的关系
如图所示:
以上就是关于线程概念部分的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!