linux线程 | 线程的概念

news2024/11/23 2:33:29

        前言:本篇讲述linux里面线程的相关概念。 线程在我们的教材中的定义通常是这样的——线程是进程的一个执行分支。 线程的执行粒度, 要比进程要细。 我们在读完这句话后其实并不能很好的理解什么是线程。 所以, 本节内容博主将会带友友们理解什么是线程!线程和进程的关系等等。现在, 开始我们的学习吧!

        ps:本节适合已经学习了进程的友友们进行观看哦。

目录

linux中的线程该如何理解

linux实现线程的方案

重新定义进程和线程

tcb

模拟线程

tcb与模拟线程的区别

如何分配线程

线程和进程的切换问题


linux中的线程该如何理解

         首先我们知道我们的进程看待自己所能看到的所有资源都是通过地址空间来看的。 所以,地址空间是进程的资源窗口。所以我们的进程如果想做任何事情, 诸如加载动态库, 申请内存, 查看变量等等操作就必须使用地址空间加页表的方案, 从物理内存当中找到我们的代码和数据或者堆空间 ——地址空间是进程的资源窗口。 如果我们今天cpu调度进程时, 就相当于task_struct在cpu所对应的运行队列中去排队。 

        另外, 在我们的子进程当中, 我们的子进程是拷贝一份父进程的task_struct和父进程的地址空间。 然后重新映射到物理空间的不同位置。 如同下图:

        但是呢, 如果我们这么设计: 不再拷贝进程的PCB以及地址空间了。 只拷贝PCB, 然后让拷贝的PCB分走一部分进程地址空间的代码区, 堆区, 等等各种区。 然后呢, 创建多个PCB, 这些PCB都和原本的PCB共享一个地址空间。 (如何划分这个地址空间成为若干份, 由后续的设计者规定)。 那么我们就会发现我们新创建的进程以及曾经的进程, 他们可以对地址空间进行一定程度上的分享, 此时我们就可以认为新创建出来的这些新进程, 他们执行的粒度要比我们原本的进程要细一些。 并且, 又因为这些新进程执行的代码是一整个一整个代码区的一部分, 所以就称为它是一个分支, 我们为了区分这种进程, 就叫做线程。 

linux实现线程的方案

        在linux中, 线程在进程的地址空间运行(为什么?任何执行流要执行, 必须要有资源!而地址空间是进程的资源窗口。又因为线程是进程内部的一个执行流, 所以线程在进程的地址空间运行!)

        在linux中, 线程的执行力度要比进程更细? 这是因为线程执行进程代码的一部分。

不同的操作系统对于线程的概念一定都是一样的, 但是不同的操作系统对于线程的管理或者线程的整体的实现方案不一样的。 linux的实现方案是上面的样子。 所以, 站在cpu的角度, cpu知不知道我们现在正在访问task_struct是线程还是进程呢? 或者说cpu需不需要知道呢?——答案是不需要关心cpu只有调度执行流的概念, 所以cpu只要访问代码就访问代码, 访问数据就访问数据。 cpu只要拿到代码和数据, 并不关心到底是进程还是线程。 

重新定义进程和线程

  •         线程:我们认为,线程是操作系统调度的基本单位!!
  •         进程:进程是承担分配系统资源的基本实体!

        (我们之前的理解是进程 = 内核数据结构task_struct+代码和数据 ------- v1)现在我们把整个的所有的线程, 以及地址空间, 页表和页表映射到的一点点物理内存叫做进程。 就如同下图:

        在操作系统中, 我们分配资源的方式是以进程为单位进行分配的。 就比如线程, 那么操作系统创建task_struct, 然后分配一部分地址空间——执行流是资源吗? 执行流就是task_struct, 就是资源。 

        所以,进程和线程的关系就是:进程里面就是包含线程的。 因为进程是操作系统分配资源的基本实体, 而我们的线程是进程内部的执行流资源。 

        那么, 我们重新理解进程, 如何理解以前的进程呢?

        操作系统以进程为单位为我们分配资源。 只不过我们当前的进程内部, 只有一个执行流!!——也就是说, 我们之前讲的才是特殊情况。 现在的多个线程是正常情况。 

        如果我们的一个进程内部真的有多个进程。 所以, 进程与线程的比率一定是1 : n的, 并且至少为1 : 1。 所以, 当进程执行的时候, 当前进程的状态是什么, 这个线程当前执行到什么位置了当前需要访问哪些资源, 期间要访问哪些资源。 这个线程是属于哪个进程的, 这个线程需要被切换吗, 什么时候被切换等等这些问题。那么我们知道, 线程一定比进程更多。 线程不是一创建就直接退出了。 也不是一创建就完成了。 是创建的时候就开始了, 操作系统要调用这个线程, 运行这个线程, 切换这个线程。 但是, 我们知道, 线程的个数是非常多的。 那么这么多的线程 , 这么多的操作需要执行。 所以操作系统要进行调度, 就一定需要管理起来。 而如何管理呢? 就是先描述再组织!

tcb

        对于大部分操作系统来说, 就是描述struct tcb。 就比如windows。 对于线程来说, 线程是属于一个进程的。 而一个进程的线程有很多, 有需要组织起来, 那么我们操作系统就需要描述一个数据结构组织线程, 然后将这个数据结构和一个进程组织起来, 还要和调度队列联系起来。 如果我们的线程出现问题, 那么又会影响我们的进程, 影响我们的调度队列等等。 这就太复杂了。 但是呢, 又不得不这么干, 所以windows就这么干了, 也就是struct tcb; 

模拟线程

        对于计算机世界来说, 我们要管理线程, 那么就势必要先描述在组织。 但是linux程序员们并不想windows程序员们一样重新描述和组织线程。 没有人规定必须用新的描述方式和组织方式管理线程。 linux的设计者们考虑到既然线程也有上下文, 也有各种状态。 进程task_struct也有, 所以就没有必要再单独为进程重新描述,重新组织了。 所以linux的设计者们就直接服用进程数据结构和管理算法了。 ——struct task_struct——模拟线程。 

        那么这样做之后, 如何区分线程, 进程呢?——如果我们的数据结构内部有一份资源, 那么就是进程。 如果里面有多个PCB, 那么这个整体就是线程。 而且未来我们不区分这个PCB是不是进程, 是不是线程, 我们统一叫做执行流。 而什么叫做进程呢?页表、 地址空间、 所有的线程、 一点点物理地址这些合起来才是进程(分配资源的基本实体), PCB就是执行流, 只不过以前进程只有一个PCB, 也就是只有一个执行流, 而现在线程有多个PCB, 多个执行流。

上面的tcb, 和模拟线程是两钟描述线程的方案。 但是对于模拟线程这个方案, 因为进程和线程是类似的。 所以线程如果再重新描述一次就势必会造成程序的复杂, 当出现问题的时候就不好处理, 但是如果使用模拟线程的方法, 程序就会更加简洁。 出现的问题一定会更少, 维护成本降低, 所以他的健壮性一定要比所有的操作系统要强, 而且强的不是一丁半点。 

tcb与模拟线程的区别

        无论是使用方案1还是方案2,, 都是具体的实现方案。 那么说linux中没有真正意义上的线程, 不是真的说linux没有线程。 对于tcb来说, tcb是将线程的概念体现在了代码上面。 而我们的linux并没有将线程的概念体现在代码上面, 而是体现在了人们的大脑当中!我们的windows通过tcb遵守了线程的概念。 我们的linux也通过模拟线程遵守了线程的概念!!并且, 我们是使用进程的内核数据结构模拟的线程。 

        操作系统, 什么叫做操作系统, 我们在学校上的课, 实际上是规定一款操作系统应该是什么样子。 或者说一款操作系统在设计上应该符合什么概念。 它是一款操作系统设计的指导手册, 但是具体我们在实现的时候, 不同的操作系统有不同的方案。

        题外话:我们的cpu在执行的时候, 当它看到一个pcb的时候, 执行一部分代码时, cpu执行的代码, 是进程的代码, 还是线程的代码呢? 其实cpu无法区分到底是进程还是线程。 但是站在上帝的视角我们知道, cpu执行的执行流, 一定是小于等于进程的。 在大部分OS中, 执行流小于进程就是线程, 等于就是进程(线程的粒度更细)。 在linux中的执行流被称为“轻量级执行流”。

        在我们国家, 承担分配社会资源的基本实体是什么呢?——家庭。 自古到今, 我们的社会都是以家庭为单位来向社会进行资源申请的。 然后呢, 我们就会发现, 在我们的家庭里:我们的爷爷奶奶每天就是打太极拳, 逛公园, 或者看别的老人下棋啥的, 目的就是把自己的身体养好。 我们的爸爸妈妈呢, 每天就是工作, 赚钱养家。 小孩在家就是学习。 那么我们会看到, 在这个家庭里面, 每一个人天然的就会存在一种小人物。 但是, 不管每一个人有着怎么样的小任务, 他们一定会有一个共同的任务——把自己家的日子过好。 只不过每一个人为了把日子过好, 领取的任务是不同的。 所以我们把家庭整体叫做进程, 把里面的爸爸妈妈, 爷爷奶奶, 我们自己叫做一个线程。 每一个线程和每一个进程的关系就是:线程是在进程的内部进行, 而且每一个线程能够做什么, 和我们进程所拥有的资源是有关的!! 而如果这个家庭里只有一个人, 就是我们进程的情况!!

如何分配线程

        如何分配线程, 这个就需要用到我们页表的知识。 我们要先谈页表结构, 页表的工作原理。 再来谈如何分配线程, 看下面一张图:

        当我们的程序加载到物理内存的时候, 页表有映射,那么从物理内存当中读取到cpu里面的地址是什么呢? 读到的是虚拟地址cpu读到虚拟地址后, cpu再从地址空间找到对应的位置, 通过页表映射就能够执行这个进程了。 

        那么, 我们以32位为例, 谈一谈虚拟地址是如何转化到物理地址的。 如果在32位条件下虚拟地址有多少位的呢? 答案也是32位。  那么, 首先我们对应的虚拟地址有32位, 并且这里需要知道的是这32位虚拟地址不是一个整体。 这32位虚拟地址我们把它转化为了10 + 10 + 12。 

        而且, 页表不是一整块的。 如果页表是一整块的, 我们知道, 页表中的条目最多2 ^ 32个, 如果是整块的, 那么我们一个条目假设有10字节那么我们的内存就爆了, 所以, 页表一定不是一整块的。 页表是分为三个板块, 其中有一级页表、二级页表、还有一个页框级别页表。 就如同下图:

        什么意思呢, 意思就是说, 我们未来从cpu内读到的某个虚拟地址, 这个虚拟地址一共有32位。 假如这个虚拟地址是1000 0000 1000 0010 0100 1000 0010 0010. 那么它其实存放的规则是按照10 + 10 + 12来存放的。 如下:

       

        并且把他们分成第一个部分, 第二个部分, 第三个部分。所以, 将来会用我们第一个部分查找我们的第一季页表。 用第二部分查找我们的第二级页表。 第三部分, 查找我们的最后一个页表。 而查找的过程就是先将对应的各个部分转化为十进程。 再由十进制找到三个页表中的下标索引。 而我们的页表的前20位, 对应的就是先查第一级页表, 再查第二级页表。 查完后, 就已经能够查找到我们的物理内存对应的页框了!!

        然后, 我们的第三个表, 指向的就是页框内具体的偏移量。 也就是说, 我们通过二级页表查找到了具体的哪一个页框, 然后得到了这个页框的地址, 假如是0x0012ff40。 那么再通过最后12位来获取偏移量。 最后0x0012ff40 + 虚拟地址最后12位。 就能得到最终的结果。 

        那么我们思考一下, 这个时候的页框, 最大是多少呢? 

我们知道我们的页表当中也有一些权限字段, 是可读还是可写等等。 一共会有1024个二级页表。 假设我们一个二级页表项是4个字节。 所以一个二级页表4 * 1024byte, 即4kb。 所以将来一个页表是可以放到一个页框里面的。 那么我们一个进程, 最多会有多少4kb呢? 因为页目录最多1024个, 所以我们最终要乘以1024. 即最多会有4 * 1024kb = 4MB。 那么4MB大不大呢? 这个空间挺大的, 但是这个空间和上面的虚拟地址空间比起来已经很小很小了。 而且, 我们一个进程, 会把整个地址空间用完吗? 不会的。另外, 我们的内核地址空间, 是不需要给每个进程都维护一份的。 并且, 我们的每个进程不一定把每个地址空间全部弄完。 所以, 大部分进程根本就不能把第一个一级页表全部用完。 所以我们想说的是, 二级页表不一定全部存在。 (在大部分情况下都是不全的。但是, 其实创建一个进程依旧是一个很“重”的工作。)

        那么看这个问题, 这里有int a = 10; 按道理来说, int类型有4字节, 那么就有4个地址。 但是为什么&a只拿到了一个地址呢? 这是因为我们有类型的存在。 我们的int类型, 只需要知道首地址, 然后之后的4个字节就按照偏移量向后找就行。 c/c++里面的自定义的类,归根结底就是我们一大堆内置类型的集合。 所以, 对于自定义的累, 取地址我们就会拿到第一个地址。 所以我们的起始地址 + 类型 = 起始地址 + 偏移量(这也是x86cpu的特点)

        所以, 如何理解资源分配? 线程资源分配的本质, 不就是分配地址的空间范围吗? 我们的线程分配资源, 本质就是把我们的地址空间划分一部分。 那么这个划分难不难呢? 怎么划分呢? 就比如我们有10个函数, 这个函数有没有地址? 代码有没有地址? 那么我们的某个线程使用了这个函数, 使用了某个代码, 那么他就天然的具有地址了。 

线程和进程的切换问题

        我们说线程比进程更加轻量化,为什么呢?

  •         a:创建和释放更加轻量化(生死问题)
  •         b: 切换更加轻量化(运行问题)

        总结就是整个生命周期线程都比进程更加轻量化。

        我们知道的是线程他自己肯定要有自己的上下文。 但是线程在切换的时候, 他对应的页表需要切换吗? 不需要。 地址空间需要切换吗? 不需要。 所以, 线程在切换的时候, 只是在局部切换, 它的页表和地址空间都不需要切换。 那么, 为什么说它的切换效率更高呢? 

        这个涉及到了cpu的知识。 就是当线程在运行的时候, 其实本质上就是进程在运行, 线程是进程的执行分支。在cpu当中, 除了有寄存器, cpu还会有一个硬件级别的cache。 cpu认为和物理内存交互太慢了, 所以就在自己里面集成了一块cache空间。 这块空间相对于寄存器很大, 相对于内存不大。 这部分cache被称为缓存的热数据。 

        那么, 线程切换, 在同一个进程内的线程切换 cache内的数据不需要或者说很少需要重新缓存。 但是进程切换, 那么cache内的数据要丢失并且重新缓存, 数据从冷变热, 需要花费时间。所以, 线程的切换要更加轻量, 而进程要更加重。

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2204048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vulnhub靶场案例渗透[6]- DC6

文章目录 1. 靶场搭建2. 信息收集2.1 确定靶机ip2.2 主机信息收集2.3 主机目录扫描2.4 网站用户名和密码爆破 3. 反弹shell4. 提权 1. 靶场搭建 靶场源地址 检验下载文件的检验码&#xff0c;对比没问题使用vmware打开 # windwos 命令 Get-FileHash <filePath> -Algori…

RTSP 音视频play同步分析

基础理论 RTSP RTP RTCP SDP基础知识-CSDN博客 关于RTP的时间戳知识点回顾 时间戳单位&#xff1a;时间戳计算的单位不是秒&#xff0c;而是采用采样频率的倒数&#xff0c;这样做的目的是为了使时间戳单位更为精准。比如说一个音频的采样频率为8000Hz&#xff0c;那么我们可…

【华为】基于华为交换机的VLAN配置与不同VLAN间通信实现

划分VLAN&#xff08;虚拟局域网&#xff09;主要作用&#xff1a; 一、提高网络安全性 广播域隔离访问控制增强 二、优化网络性能 减少网络拥塞提高网络可管理性 sysytem-view #进入系统视图配置参数 vlan batch 10 20 #批量创建vlan LSW3: int g0/0/1 port…

「实战应用」如何用图表控件LightningChart可视化天气数据?(一)

LightningChart.NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科学…

scau:面向对象java实验作业1-2 猜数字游戏

题目名称实验1-2 猜数字游戏题目关键字数据类型 基本输入输出 控制语句 方法题目录入时间2022/10/10 11:01:37题目内容 使用Java程序&#xff0c;项目名称&#xff1a;GuessNumberGame&#xff0c;类根据自己需要定义。 程序开始运行后&#xff0c;允许玩家进行多次猜数字的游…

C++从入门到起飞之——(multi)set与(multi)map的的使用 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. 序列式容器和关联式容器 2. set系列的使⽤ 2.1 set和multiset参考⽂档 2.2 set类的介绍 2.3 se…

Effective C++笔记之二十四:stack overflow

溢出&#xff08;Stack Overflow&#xff09;是指程序运行过程中&#xff0c;栈空间被耗尽&#xff0c;导致无法继续分配栈内存的错误。C程序中&#xff0c;栈用于存储函数调用的局部变量、返回地址、函数参数等。当栈空间耗尽时&#xff0c;会引发栈溢出&#xff0c;通常导致程…

java的Maven项目的ehcache缓存学习记录

java的ehcache缓存学习记录 第1步:pom.xml增加ehcache的依赖项 <!--ehcache缓存--><dependency><groupId>net.sf.ehcache</groupId</

Renesas R7FA8D1BH (Cortex®-M85) 上超声波测距模块(HC-SR04)驱动开发

目录 概述 1 软硬件 1.1 软硬件环境信息 1.2 开发板信息 1.3 调试器信息 2 硬件架构 2.1 硬件框架结构 2.2 测距模块&#xff08;HC-SR04&#xff09;介绍 2.2.1 HC-SR04特性 2.2.2 HC-SR04操作时序 2.2.3 计算距离 3 软件实现 3.1 FSP配置项目 3.1.1 配置IO口的外…

springboot 整合spring ai实现 基于知识库的客服问答

rag 需求产生的背景介绍&#xff1a; 在使用大模型时&#xff0c;常遇到的问题之一是模型可能产生幻觉&#xff0c;即生成的内容缺乏准确性。此外&#xff0c;由于大模型不直接访问企业的专有数据&#xff0c;其响应可能会显得泛泛而谈&#xff0c;不够精准或具体&#xff0c;…

Vue包的安装使用

文章目录 vue介绍一、灵活易用1.渐进式框架2.简洁的语法 二、高效的响应式系统1.数据驱动2.响应式原理 三、强大的组件化开发1.组件化思想2.组件通信 四、丰富的生态系统1.插件和库2.社区支持 安装依赖删除新增文件夹components设置(1)home.vue(2)data.vue(3)zero.vue router配…

Visual Studio Code 中通过鼠标滚轮调整字体大小并使用 Ctrl+W 关闭文档窗口【最详细】

1. 使用鼠标滚轮调整字体大小 希望通过鼠标滚轮与 Ctrl 键组合来放大或缩小编辑器的字体大小&#xff0c;按照以下步骤进行设置&#xff1a; 打开 Visual Studio Code。 进入设置页面&#xff1a; 点击左下角的齿轮图标&#xff0c;然后选择“设置”。 或者直接使用快捷键 …

最新Prompt预设词指令教程大全ChatGPT、AI智能体(300+预设词应用)

使用指南 直接复制在AI工具助手中使用&#xff08;提问前&#xff09; 可以前往已经添加好Prompt预设的AI系统测试使用&#xff08;可自定义添加使用&#xff09; SparkAi系统现已支持自定义添加官方GPTs&#xff08;对专业领域更加专业&#xff0c;支持多模态文档&#xff0…

科研绘图系列:R语言绘制中国地理地图

文章目录 介绍加载R包导入数据图a图b图c图d系统信息介绍 文章提供了绘制图a,图b和图d的数据和代码。该图展示了不同省份的物种分布情况。 加载R包 library(geojsonsf) library(sf) library(ggplot2) library(RColorBrewer) library(ggspatial) library(</

C++AVL树详解

什么是AVL树 AVL树是最先发明的⾃平衡⼆叉查找树&#xff0c;AVL是⼀颗空树&#xff0c;或者具备下列性质的⼆叉搜索树&#xff1a;它的 左右⼦树都是AV树&#xff0c;且左右⼦树的⾼度差的绝对值不超过1。AVL树是⼀颗⾼度平衡搜索⼆叉树&#xff0c; 通过控制⾼度差去控制平衡…

python的介绍以及基本操作

python的介绍 &#xff08;1&#xff09;python是一门编程语言&#xff08;比如&#xff1a;java、c、c、.net、go等都是编程语言&#xff09; python 也是胶水语言 &#xff08;2&#xff09;python是一门面向对象&#xff0c;解释型的动态类型的编程语言&#xff0c; a、什…

select、epoll相关

select函数&#xff1a; int select(int nfds, // 监控的文件描述符集里最大文件描述符加1fd_set *readfds, // 监控有读数据到达文件描述符集合&#xff0c;引用类型的参数fd_set *writefds, // 监控写数据到达文件描述符集合&…

【零散技术】一分钟完成Odoo悬挂网站备案号

序言:时间是我们最宝贵的财富,珍惜手上的每个时分 目录 1.激活开发者模式 2.修改视图 Odoo套上域名是常见的需求&#xff0c;当我们兴致勃勃的做好 域名申请&#xff0c;网站备案&#xff0c;域名解析&#xff0c;SSL证书申请&#xff0c;Nginx转发后&#xff0c;就可以通过域…

横向移动与痕迹清理

目录 横向移动漏洞利⽤服务利⽤IPC横向计划任务横向计划任务横向WMI横向SMB横向DCOM横向WinRM横向PSEXEC横向其他⽅式横向 软件部署利⽤GPO组策略横向 密码喷洒密码策略检查喷洒主机喷洒⽤户名喷洒密码喷洒hash喷洒服务 痕迹清除OPSEC清除webshell清除隧道⼯具清除落地样本清除…

由于找不到krpt.dll,无法继续执行代码该怎么办?总结三种简单有效修复方法

1. krpt.dll 简介 1.1 定义 krpt.dll 是一个 Windows 动态链接库文件&#xff08;Dynamic Link Library&#xff09;&#xff0c;这种类型的文件包含可由多个应用程序共享的函数和资源。它是Windows操作系统中的一个重要组件&#xff0c;对于系统的正常运行起着至关重要的作用…