Linux之多线程(上)——Linux下的线程概念

这篇具有很好参考价值的文章主要介绍了Linux之多线程(上)——Linux下的线程概念。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

本文介绍了地址空间和二级页表、Linux下的线程、线程的优缺点以及线程与进程的关系等概念。


一、地址空间和页表

地址空间是进程能看到的资源窗口:一个进程可以看到代码区、堆栈区、共享区、内核区等,大部分的资源是在地址空间上看到的。
页表决定进程真正有用资源的情况:进程认为自己独占系统的4GB资源,但实际上进程拥有多少物理资源是由页表决定的。
合理的对地址空间和页表进行资源划分,我们就可以对进程所拥有的资源进行分类:通过地址空间的区域划分,划分为栈区、堆区……,通过页表映射到不同的物理内存中。

1.二级页表

在32位平台下,一共有2^32个地址,这也意味着有2^32个地址需要被映射。
地址空间有2^32个地址,每个地址单位都是1字节,页表也要有2^32个条目(每个地址都要经过页表映射,它们都是页表的条目),包括是否命中,包括RWX权限,包括U/K权限。一个条目假设有6个字节的数据,那么光保存页表的空间就需要24GB(4GB大约40亿字节)。
Linux之多线程(上)——Linux下的线程概念
每个表项中处理要有虚拟地址和它映射的物理地址外,时间还需要一些权限相关的信息,用户级页表和内核级页表实际就是通过权限进行区分。
Linux之多线程(上)——Linux下的线程概念
虚拟地址:32位下是32位。
物理地址:被划分为一块块的数据框。
OS要对物理内存进行管理:先描述(结构体:struct Page{//内存的属性——4KB}),再组织(数组:struct Page mem[])。
在OS中把物理内存一块块的数据框称为页框,磁盘上编译形成可执行程序的时候被划分为一个个4KB的区域称为页帧。当内存和磁盘进行数据交换时,也是以4KB大小为单位进行加载和保存的。
因此,将数据加载到内存时,在文件系统级别需要按照4KB为基本单位将数据从外设搬到内存。最后,OS系统想要管理内存,除了结构匹配还要有管理算法Linux常见的管理算法称为伙伴系统

虚拟地址转化为物理地址:虚拟地址形成后(以10,10,12的二进制构成),页表不止一张。第一级页表页目录:前十个在页目录中查找,2^10个指向页表的内容。页表:页表的条目项为2^10个,条目写的是指定页框的起始物理地址,页表项指向物理内存中某一页,剩下的12位虚拟地址刚好与页框的大小是等价的(4KB = 2^12B),因此,从物理地址的起始处 + 虚拟地址的低12位(2^12偏移量)作为页内偏移,就可以直接在某个页内找到某个地址。
Linux之多线程(上)——Linux下的线程概念
其中的页目录项是一级页表,页表项是二级页表。映射过程由MMU这个硬件完成(该硬件集成在CPU内),页表是一种软件映射,MMU是一种硬件映射,虚拟地址转为物理地址实际上是软硬件结合的。

2.例子

修改常量字符串为什么会发送错误?
如果要修改一个常量字符串,虚拟地址需要经过页表映射查找到对应的物理内存,但是在查表的过程中会发现该地址的权限是只读,对一个只读地址进行修改会导致在MMU内部触发硬件错误,OS识别到这个错误会该对应进程发送信号终止对应进程。

二、线程

1.概念

  1. 在一个程序里的一个执行路线就叫做线程(可以参考进程)。更准确的定义是:线程是一个进程内部的控制序列
  2. 一切进程都至少有一个执行线程。
  3. 线程在进程内部运行本质是在进程的地址空间内运行。
  4. Linux中,在CPU眼中看到的PCB都比传统的进程更加轻量化。
  5. 透过进程的虚拟地址空间可以看到进程的大部分资源,将进程的资源合理分配给每个执行流,就形成了线程执行流。
  6. 不同平台的多线程底层实现策略都是不同的,本文我们了解的是Linux下的多线程策略。

线程对应的模型:进程的创建实际上伴随着进程控制块(PCB)、进程地址空间(mm_struct)以及页表的创建(虚拟地址和物理地址是通过页表建立映射的):
Linux之多线程(上)——Linux下的线程概念
进程 = 内核数据结构 + 代码和数据。
每个进程都有字节独立的进程地址空间和独立的页表,这意味着每个进程在运行时会具有独立性,
如果我们在创建进程时只创建进程的PCB,并要求创建出来的PCB不再独立创建资源,而是与父进程共享资源。那么创建的结果就是下面这样的:

因为我们可以通过虚拟地址空间 + 页表的方式对进程的资源进行划分,单个进程的执行力度会比之前的进程更细。
上图中每个线程都是当前进程的一个执行流,线程在进程的内部运行,在进程的地址空间运行,拥有该进程的一部分资源。

重新理解前面讲的进程:在内核的视角,进程是承担分配系统资源的基本实体。

创建进程时,申请的PCB、虚拟内存空间、页表以及加载到物理内存中的代码和数据:花费CPU资源创建进程并初始化;花费内存资源保存进程的内核数据结构、代码和数据;花费CPU的IO资源从外设IO到内存。所以承担分配系统资源的基本实体是进程。
总结一下,我们创建进程时,OS申请一堆的内核数据结构占用资源,进程的代码和数据加载到内存中也要占用资源,以及其他部分占用的资源。因此,进程是承担系统资源分配的基本实体。
我们之前讨论的进程都是只有一个PCB,也就是说该进程内部只有一个执行流,即单执行流,这与我们上面讲的并不冲突,如果是像上面这样的一个进程内部由多个执行流,那它就是多执行流进程。

站在CPU角度,能否去识别当前调度的task_struct是进程还是线程?

不能,也不需要,CPU不关心当前调度的是进程还是线程。CPU以task_struct为单位进行调度,今天我们喂给CPU的task_struct是小于等于过去所说的task_struct的,它比之前的更轻量化。因此,在Linux中可以把进程和线程做一个统一,CPU看到的task_struct称为轻量级期间进程。
在Linux中,什么是线程?——线程是CPU的基本调度单位

Linux下并不存在真正的线程

Linux下的线程是用进程模拟的。
如果OS真正要专门设计“线程”概念,OS就要管理线程了(先描述,再组织)。
Windows下确实是为线程专门设计了数据结果表示线程对象TCB,但是线程的创建就是为被执行,执行需要被调度、存在ID/状态、优先级、上下文、栈……等内容,这些线程调度需要的东西与进程有很多地方是重叠的。因此,Linux下没有为“线程”专门设计对应的数据结构,而是直接复用了进程的PCB,用PCB来表示Linux下的“线程”

总结

  1. Linux内核中严格来说是没有真正意义的线程的,Linux用进程PCB来模拟线程,它有一套完全属于自己的线程方案。
  2. 站在CPU角度,每一个PCB都可以称为轻量级进程。
  3. Linux下,线程是CPU调度的基本单位,进程是承担分配系统资源的基本单位。
  4. 进程用来整体申请资源,线程是伸手向进程要资源。(所以线程在执行时申请的资源,实际上是进程向系统申请的资源)
  5. 进程模拟线程的好处:用PCB模拟线程,则为PCB编写的结构和算法都可以进行复用,不用单独再为线程创建结构和调度算法,降低了系统的维护成本,同时复用进程的那套,可靠高效

2.线程的优点

  1. 创建一个线程要花费的代价比创建一个进程的代价要小得多,与进程切换相比,线程之间的切换需要操作系统做的工作要少很多。
    进程切换:要切换页表、虚拟地址空间、PCB、上下文;
    线程切换:切换PCB和上下文。
  2. 线程占用的资源要比进程占用的资源少很多。
  3. 线程能充分利用多处理器的可并行数量。
  4. 在等待慢速I/O操作结束的同时,程序可执行其他计算任务。
  5. 计算密集型应用(CPU、加密、解密、算法等),为了能在多处理器系统上运行,可以讲计算分解到多个线程中实现。
  6. I/O密集型应用(外设、磁盘、显示器、网络),为了提高性能,讲I/O操作重叠,使线程可以同时等待不同的I/O操作。

3.线程的缺点

  1. 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。
    如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用资源是不变的。
  2. 健壮性降低:编写多线程需要更全面深入的考虑。
    在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。换而言之,线程之间是缺乏保护的。
  3. 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  4. 编程难度提高:编写与调试一个多线程程序比单线程程序困难的多。

健壮性降低的例子

一个线程如果出现了异常会影响其他线程(健壮性、鲁棒性较差)

  1 #include<iostream>
  2 #include<string>
  3 #include<unistd.h>
  4 #include<pthread.h>
  5 using namespace std;
  6 void* start_routine(void* args)
  7 {
  8         string name = static_cast<const char*>(args);//安全的进行强制类型转换
  9         while(1)
 10         {
 11                 cout<<"new thread create success, name:"<<name<<endl;
 12                 sleep(1);
 13                 int* p = nullptr;
 14                 *p = 0;
 15         }
 16 }
 17 int main()
 18 {
 19         pthread_t id;
 20         pthread_create(&id, nullptr, start_routine, (void*)"thread new");
 21         while(1)
 22         {
 23                 cout<<"new thread create success, name: main thread"<<endl;
 24                 sleep(1);
 25         }
 26         return 0;
 27 }

运行:
Linux之多线程(上)——Linux下的线程概念

线程出现异常会影响其他线程,这是因为信号是由OS发送给整个进程的,当前线程出现异常,那么OS识别到当前硬件报错、地址转化出现失败、没有权限的空间进行写入、MMU+页表执行异常等问题,OS会立即识别是哪个线程/进程出错,而所有的线程的PID是相同的,因此OS会直接给所有该PID的线程的PCB写入11号段错误信号,这就终止了当前的进程执行流,当前进程就退了,而线程所拥有的资源是进程给的,进程没了,线程也就得退出了。

4.线程的异常

当线程如果出现除零、野指针问题,会导致当前线程崩溃,进程也会随之崩溃。线程是进程的执行分支,线程出现异常,就等同于进程出现异常,进而触发信号机制,终止进程。进程终止了,进程内运行的所有线程也就终止了。

5.线程的用途

  1. 合理使用多线程,可用提高CPU密集型程序的执行效率;
  2. 合理使用多线程,可用提高IO密集型程序的用户体验(例如,我们一边写代码,一边下载开发工具,就是多线程运行的一种表现)

三、Linux下的进程与线程

进程是承担分配系统资源的基本实体,线程是系统调度的基本单位。

进程的多个线程共享的资源

  1. 因为这些线程在同一个地址空间,所以代码段(Text Segment)、数据段(Data Segment)都是共享的。
  2. 如果是函数,那么在各个线程内都是可用调用的;如果是变量,那么在各个线程中都可以访问到。
  3. 线程还贡献一下进程资源和环境:
    文件描述符表、每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)、当前的工作目录、用户id和组id。

线程独立的数据

进程内的线程共享进程的数据,但是也拥有自己独立的一部分数据。
线程ID、一组寄存器:存储线程的上下文信息、栈:线程的临时数据、errno、信号屏蔽字、调度优先级。

进程与线程的关系

Linux之多线程(上)——Linux下的线程概念
我们之前接触到的只有一个线程执行流的进程,就是单线程进程。


总结

以上就是今天要讲的内容,本文介绍了本文介绍了地址空间和二级页表、Linux下的线程、线程的优缺点以及线程与进程的关系等概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!文章来源地址https://www.toymoban.com/news/detail-483798.html

到了这里,关于Linux之多线程(上)——Linux下的线程概念的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaEE之多线程编程:4. 线程安全(重点!!!)

    下面我们来举个例子: 我们大家都知道,在单线程中,以下的代码100%是正确的。 但是,两个线程,并发的进行上述循环,此时逻辑可能就出现问题了。 上述这样的情况就是非常典型的线程安全问题。这种情况就是bug!! 只要实际结果和预期的结果不符合,就一定是bug。 想

    2024年01月25日
    浏览(39)
  • 【Linux】线程-线程概念

    实际上,线程是一个进程内部的控制序列,一个程序的一个执行线路就是一个线程。 并且一个进程中至少有一个线程,本质上,一个进程内部如果有多个线程,那么这些线程实际上是指向同一块地址空间的。而不论进程还是线程,从CPU看来都是一个PCB,只是说线程的PCB要比进

    2023年04月25日
    浏览(39)
  • JAVA之多线程

    进程是指运行中的应用程序,每一个进程都有自己独立的内存空间; 线程是指进程中的一个执行流程,有时也称为执行情景; 一个进程可以由多个线程组成,即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务; 当进程内的多个线程同时运行,这种运行

    2024年02月07日
    浏览(40)
  • Java之多线程进阶

    目录 一.上节内容复习 1.线程池的实现 2.自定义一个线程池,构造方法的参数及含义 3.线程池的工作原理 4.拒绝策略 5.为什么不推荐系统提供的线程池 二.常见的锁策略 1.乐观锁和悲观锁 2.轻量级锁和重量级锁 3.读写锁和普通互斥锁 4.自旋锁和挂起等待锁 5.可重入锁和不可重入

    2024年02月05日
    浏览(47)
  • Linux 多线程 ( 多线程概念 )

    在一个程序里的一个执行路线叫做线程 thread ),更准确的定义为:“线程是一个进程内部的控制序列\\\"。 一切进程至少有一个执行线程。 线程在进程内部运行,本质上是在进程地址空间中运行。 在linux系统中,CPU看到的PCB比传统的进程更加轻量化。 透过进程虚拟地址空间,可

    2024年02月09日
    浏览(62)
  • JavaEE之多线程编程:3. 线程的状态(易懂!)

    进程 最核心的状态,一个是就绪状态,一个是阻塞状态(对于线程同样使用)。 以线程为单位进行调度的。 在Java中,又给线程赋予了一些其他的状态。 线程的状态是一个枚举类型 Thread.State 线程的状态一共有6个: NEW:安排了工作, 还未开始行动。(Thread对象已经有了,.

    2024年01月19日
    浏览(50)
  • 从理解概念开始,彻底学会linux下的磁盘扩容操作

    对于linux磁盘空间不足需要扩容的情况,其他文章一般只介绍要如何操作,使用什么样的命令,但是不去介绍为什么要这么做,搞得好多小白一头雾水。本文从linux的文件系统开始讲起,帮你彻底学会linux系统中的磁盘扩容操作。 假设你的程序突然不能正常运行了,你怀疑是日

    2024年02月02日
    浏览(41)
  • Linux下的多线程编程:原理、工具及应用(1)

                                                    🎬慕斯主页 : 修仙—别有洞天                                               ♈️ 今日夜电波:Flower of Life—陽花                                                                 0:34━━━

    2024年03月17日
    浏览(35)
  • Linux下的多线程编程:原理、工具及应用(2)

                                                    🎬慕斯主页 : 修仙—别有洞天                                               ♈️ 今日夜电波: Flower of Life—陽花                                                                 0:34━━━

    2024年03月18日
    浏览(48)
  • Linux下的多线程编程:原理、工具及应用(3)

                                                    🎬慕斯主页 :修仙—别有洞天                                               ♈️ 今日夜电波: Flower of Life—陽花                                                                 0:34━━━

    2024年03月17日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包