Linux相关概念和易错知识点(27)(认识线程、页表与进程地址空间、线程资源划分)

目录

1.认识线程

(1)进程与线程的关系

(2)最小执行流

(3)轻量级进程(LWP)

①对task_struct的理解

②轻量级进程

③LWP和TCB的区别

2.页表与进程地址空间

(1)页表的结构

①虚拟地址和物理地址的作用

②页框和page

③多级页表

a.第一级:页目录

b.第二级:页表

c.第三级:页内偏移量(严格意义上不算一级)

④多级页表的作用

(2)虚拟地址 -> 物理地址的过程

(3)懒加载和缺页中断

(4)MMU和快表(TLB)

(5)高速缓存cache和局部性原理

(6)线程的优缺点

①优点

②缺点

3.线程资源划分

(1)代码块

(2)其它独立资源


1.认识线程

(1)进程与线程的关系

一个进程是为了完成一整个任务,而线程是完成这个任务的最小执行单位。以一个直观的说法,宏观上看这个进程是一个公司,这个公司里面有人力、资源等,就像进程那样掌管着进程地址空间。这个公司存在的使命就是完成一个大型项目。要完成一个大项目,还需要有员工分工,这些员工就是线程。每个员工作为最小执行单元完成分配给自己的子任务,期间公司会给这些员工分配资源(如电脑、办公桌),对应起来就是进程给线程分配资源(如进程地址空间的划分)。

我们前面之所以感受不到线程是因为我们创建的都是单(多)进程单线程,但除此之外还有单(多)进程多线程等待我们学习。

(2)最小执行流

进程是一个执行起来的程序,它是承担分配系统资源的基本实体,它承担完成任务的使命,但并不是进程直接去处理任务,它只负责分配资源,处理任务需要交给线程。线程就是执行流,执行粒度比进程要更细,是进程内部的一个执行分支。

就好比工人和它们小组长的关系,组长需要向上(系统)交代,完成一整个任务,但并不是组长去完成这一整个任务,它不会直接参与,而是会把任务拆开分配给工人,让工人去做。整个过程中组长就是进程,而工人就是线程,相对整个产业来看,工人又是最底层最基础的劳动力,因此线程被称为最小执行流,线程是OS调度的基本单位。

这个时候我们就发现之前我们对进程的理解只停留在对小组长进行交流,很局限,而学习线程我们可以进一步去管理工人。

(3)轻量级进程(LWP)

①对task_struct的理解

如果Linux支持线程,那么线程就必须要被OS管理,这会创造TCB(T指的是Thread,TCB和PCB相对应)这个数据结构,和PCB同时存在于系统之中,对Linux设计者来说这太复杂。

线程的本质是在进程内部运行,且粒度要更细Linux下的线程是用进程模拟实现的,复用了历史代码。

在Linux中,task_struct不再简单代表PCB,同一个进程可以有多个task_struct,我们可以简单认为一个task_struct对应一个线程。一个进程地址空间里面所有的task_struct都属于一个进程。之前我们直接让PCB和task_struct划等号,是因为我们之前遇到的都是单线程,一个进程要有task_struct,但从来没说只有一个。task_struct描述的是执行流,task_struct里面拥有一个线程需要的页表、分配的地址空间的描述。

一个进程的所有线程共享同一进程地址空间,同时代码区也被肢解分给不同线程执行,即线程在进程的地址空间内运行。

②轻量级进程

线程直接进程的资源用,相对而言进程就需要承担开辟资源的责任。我们已经知道task_struct是用来描述线程的了,它在一定程度上相当于TCB(但不等于,这点很重要)。但真正的PCB在哪呢?实际上,Linux里不存在真正意义上的PCB,而是当前进程下所有task_struct的总和描述了一个PCB等效的信息,这也是task_struct和TCB不同的点。

站在系统角度来看这个进程,没有专门管理整个进程的PCB,而是众多task_struct中的其中一个。在Linux中,task_struct既不是TCB,也不是PCB,其对应的线程也被统一称为轻量级进程LWP。

在这里,我们需要严谨的区分一个概念了。由于Linux下的task_struct不是TCB,那么用它来描述的虽然可以被称为线程,但从严谨的角度出发Linux中没有真正意义上的线程,因为线程的概念及其管理的数据结构TCB是用task_struct模拟实现的,task_struct和TCB是有区别的。

③LWP和TCB的区别

接下来我们需要回答两个问题,多个LWP是如何结合起来描述一整个PCB的等效信息呢?还有为什么LWP不能称为TCB呢?

最关键的一点是同一进程的所有task_struct共享一套进程地址空间mm_struct。虽然实例化出了多个task_struct数据结构,但它们的mm_struct指向同一块空间,它们的进程地址空间的划分并不独立,而是耦合的。也就是说,当我们修改其中一个task_struct里面描述进程地址空间的信息时,其它task_struct的相关信息也会更新。同样的,所有task_struct的页表是一样的,映射位置都一致,但由于空间划分的耦合,正常情况下读写内存不会冲突。

因此,task_struct和TCB存在一定的区别,TCB并不侧重于资源的共享,而task_struct之间互相耦合,最终也描述了PCB应该描述的信息,因此不需要专门存储一个PCB了。这样一来就能回答上面两个问题了,这也是为什么Linux中的线程要被叫做轻量级进程,其根源也是task_struct的特殊性。

例如:对于3线程的进程来说,整个进程有3个task_struct,也就是有3个LWP,这三个task_struct共同完成了对整个进程以及进程内线程的全面描述。

注意:LWP是轻量级进程,代表的是一个实体。而task_struct不叫LWP,只是描述和管理LWP的数据结构。同理,task_struct也不完全等同于PCB或者TCB。

2.页表与进程地址空间

(1)页表的结构

①虚拟地址和物理地址的作用

不同进程有不同的生命周期,这会导致物理内存可能有碎片。虚拟地址将不连续的物理地址转换成连续的,并且实现了系统层面和硬件的解耦合。

②页框和page

物理内存和文件都是以4KB的数据块划分的,通过块内的碎片减少块间的碎片,用空间换时间。

物理内存将划分好的4KB的数据块叫做页框,所有内存都被划分多个页框。

哪一个页框要刷新,哪些页框被用了?OS要对内存进行管理,管理方式就是对页框先描述再组织。这个描述页框的结构体就是struct page。

page里面有位图unsigned long flags,1表示该页框被使用。一个约40字节的page描述一个4KB的页框。OS还会维护一个struct page* mem_map[N]用来管理众多的page,用数组增删查改来管理内存。类似于偏移量,物理地址和mem_map之间能相互转换。物理地址 = mem_map下标 * 4KB,下标 = mem_map物理地址 / 4KB。OS只需要知道下标就能找到对应的page,进而获取页框的使用情况。这个转换过程通过位操作就能实现,这里不展开。

4GB内存的管理需要一百多万个page,一个page大概40B,所有page的占用空间不超过40MB,大概千分之一的空间被占用,不算严重。对于现在普遍的16GB、32GB电脑,同样占用很小。因此用page来管理页框的占用比较合理。

③多级页表

page利用了位图大大压缩了空间占用,仅需1bit就可标志1B的使用情况。但页表就没这么好压缩了。页表左侧为虚拟地址,右侧为物理地址,页表一行就算不计入标记位都至少要8B,而这8B仅描述了1B的地址映射,显然这要占很大的空间,比存数据的内存都要大了。因此我们接下来要用多级页表来解决这个问题。

下面以一个指针4字节(32位系统)的标准来介绍

当拿到虚拟地址之后,32bit会被拆为10、10、12三份,经过三级找到对应的物理地址。

a.第一级:页目录

页目录表里面有1024项,虚拟地址的高10位会作为页目录表的下标匹配对应项,而下标对应的某一项存的内容叫页目录表项,是该虚拟地址对应的物理地址的高10bit,同时这个物理地址会指向下一级页表的物理地址的起始位置,这个起始位置的物理地址高位就是页目录表项存的值,低位为0。

例如:

0111010101 0101001010 101011010101会在页目录表中找到下标为0111010101对应的项,这个项里面存的是对应物理地址的高10位,为0010110101,之后会到下一级页表中查找,这个页表的物理地址就是0010110101 0000000000 0000000000。

b.第二级:页表

由上级页目录表项指向,每个页表1024项,由虚拟地址的中间10bit作为下标匹配,对应项存的内容叫页表项,同样标记了转换后的物理地址的中间10bit。同理,这个物理地址会继续指向下一级的物理地址的起始位置,高20位已确定,低12位自动补0。

同样是刚才的例子:

在物理地址为0010110101 0000000000 0000000000的页表中,虚拟地址的中间10位0101001010作为下标查找该表,得到的项的内容为0011010111。至此,得到的物理地址为0010110101 0011010111,接下来要继续向下一级查找,这一级的起始物理地址为0010110101 0011010111 0000000000。

c.第三级:页内偏移量(严格意义上不算一级)

转换的物理地址的高20位我们已经得到了,还剩下12bit,12bit的地址能够标识2 ^ 12bit数据(即4KB)。也就是说,一个页表项里存的值结合之前获得的地址可以标识内存中的一个具体的页框的起始地址,剩下的12bit就是以页框为起始地址的偏移量。也就是说,32位系统下最后12bit的虚拟地址就是物理地址的最后12bit,这里所谓的第三级其实并不存在,只需要两级页表即可转换虚拟地址为物理地址。

接着刚才的例子:

找到0010110101 0011010111 0000000000对应地址,这就是某一个页框的起始地址。虚拟地址101011010101会作为该页框的偏移量。至此,虚拟地址能够转为物理地址0010110101 0011010111 101011010101。

④多级页表的作用

当系统拿到虚拟地址要转换为物理地址时,它必须采用多级页表,否则页表占用空间会非常大。在32位系统下,多级页表其实只有两级,上面也说了页内偏移量不需要访问任何空间,不算做一级。每一级都开辟1024块空间,按每项4字节,页表总大小在1024 * 1024 * 4 = 4MB左右,完全处于可用状态。另外一个进程不可能使用全部地址,意味着页表一定是不完整的,所以一个页表远远小于4MB。

(2)虚拟地址 -> 物理地址的过程

在了解多级页表的转换之后,我们再次从头到尾梳理一次系统是如何进行地址转换的。

我们程序的每一条指令都有其虚拟地址,在执行某一指令前,要先从虚拟 -> 物理地址,获取到内容后才能交给CPU处理。在CPU中,寄存器CR3存的是页目录表的物理起始地址,当要开始进行虚拟 -> 物理地址的转换时,CR3会直接找到页目录表的物理地址;寄存器EIP里面存的下一条要执行的指令的虚拟地址。CPU中的硬件MMU就拿着这两个地址,按照多级页表的查找方式转化成物理地址。这是硬件自动完成的,是用硬件电路来完成页表查找的过程。

MMU得到物理地址后就通过系统总线来物理寻址,操作是in / out(根据EIP指令决定)。MMU将其内容交给IR,这就是该指令的内容,之后将由CPU来进一步处理。之后EIP + MMU读到的指令的长度就可以得到下一条指令的地址,循环往复。也就是说,整个进程地址空间的存在,是靠软硬结合实现的。

为什么虚拟 -> 物理地址的转换一定要靠硬件?速度是非常重要的因素,这个工作太高频了,CPU不是外设,不需要IO,集成到硬件就是提升效率最好的办法。

(3)懒加载和缺页中断

我们怎么知道指定页框的使用情况?page可以解决这个问题。我们已经知道mem_map[N]存储了所有物理内存的页框使用情况,且访问效率很高,这就意味着OS可以随时知道物理内存页框的使用情况以及相关属性。另外虚拟 -> 物理地址的过程也很高效,所以OS可以先建立映射关系,即先创建好多级页表,内容均填充,但实际物理内存对应位置不一定要有数据。当访问对应物理内存时可以配合page、标志位即时加载数据到对应页框中。

举个例子,一万个虚拟地址 -> 物理地址映射的页表只加载了一千个页框的数据,当访问的地方没有加载时,就会触发错误软中断去进一步加载内容,这就叫缺页中断。

有个问题,如何区分缺页中断和越界访问呢?只需页号合法性检查即可,页表中有映射地址就是缺页中断,物理地址在不在映射范围内就是越界。

理解之后,我们还能进一步理解new和malloc的操作。

new只需要申请虚拟地址空间,维护好页表的映射关系,但是命中标志位设置为0。当使用该空间时OS才会中断,申请空间,就像懒加载那样。程序中申请内存的本质就是申请虚拟内存 + 修改页表映射,而不会真正开辟空间,这实现了进程管理和内存管理解耦

(4)MMU和快表(TLB)

MMU用于虚拟地址 -> 物理地址转换,若MMU转换时发现对应位置的权限不允许,就会报错,这会存储到寄存器中并作为进程的上下文数据当进程调度时会检查该寄存器,并发送信号终止程序,这也是权限越界导致程序崩溃的原因。

快表也叫转译后备缓冲器,MMU转换地址后会将已访问的物理地址历史记录存到TLB中,后续所有转换操作都会先到TLB找,如果表中没有再到多级页表中查找,并记录到TLB中,快表的出现可以加速寻址,提高转换效率。

(5)高速缓存cache和局部性原理

CPU有自己的高速缓存cache,cache缓存的的是代码块和数据块,区别于TLB缓存的是虚拟地址到物理地址的映射关系。当要取得虚拟地址对应的数据时,MMU会先到cache中找,找到了就放到IR中。如果没找到就会根据多级页表或者TLB访问物理内存,并一次性将大量代码和数据缓存。

例如我们需要10bit数据,OS就有可能缓存20B到cache中,这取决于CPU硬件设计。

TLB和cache都是虚拟地址 -> 物理地址并读取数据过程的产物,TLB用于地址的转换,cache用于快速读取数据。cache缓存有意义吗?这离不开局部性原理。

cache是基于概率的,当我们访问一块代码时,我们就很有可能访问附近的代码,这就叫局部性原理。cache的本质是预加载,效率的提高离不开预加载(局部性原理)。在更大的角度上讲,局部性原理适用于所有缓存,例如内存就是CPU和外设的缓存。

对于多进程来说,进程间切换时cache也会同时失效,而线程切换不会失效,因为同一进程下所有线程共享同一块代码段,cache存的数据都是有意义的。因此,线程的效率相对进程而言更高。同样,线程切换时页表、TLB也不会切换,这也会增加虚拟地址到物理地址的转换效率,只要切换进程,页表、TLB、cache全部都无效了。

(6)线程的优缺点

①优点

创建线程的代价比创建进程小,占用的资源少。

切换线程的成本消耗少,页表、TLB和cache都能延用,而不像进程那样需要重新加载。

等待慢速IO时可以执行其它计算任务,在IO密集型应用中提高性能。除此之外还能将IO操作重叠,不同线程可以等待不同IO,这能充分利用多处理器的可并行数量。

在多处理器系统上运行计算密集型应用,可以将计算分给多个线程实现,提高效率注意线程个数不是越多越好,当线程个数过多,计算问题就会转为线程的调度问题,导致切换成本高。推荐的线程个数是CPU的物理个数 * 核数。

②缺点

性能损失(调度消耗),健壮性降低,缺乏访问控制(某些OS调用会对进程造成影响),编程难度提高。

更重要的一个特性是,一个线程崩溃会导致整个进程崩溃。线程是进程的执行分支,线程干的就是进程干的。在OS看来触发错误并中断程序是直接作用于整个进程的,线程不过是进程下面打工的。只要CPU对应寄存器有标志了,OS会直接杀掉所有线程。

3.线程资源划分

(1)代码块

每一个线程执行自己的任务都是以函数为入口的,每一个函数都有一个入口地址,函数代码紧接着入口地址且函数的代码块是连续编址的。这就是说不同线程划分的资源本来就是分离的,不需要进一步处理。可以理解为,线程瓜分函数代码的途中就将进程地址空间的代码段分成了不同的独立的部分。

(2)其它独立资源

进程是资源分配基本实体,线程是资源调度的基本单位。在同一个进程中,线程共享进程数据,也就是说数据都能被所有线程访问到(代码段和数据段),这和进程的独立性有很大的区别。但相对而言线程也有自己的独立的数据:线程ID、一组寄存器(用于存储线程的上下文数据)、栈(独立栈,局部性的互不影响)、errno、信号屏蔽字、调度优先级。其实进程地址空间中有自己独立的空间,但这空间不是私有的,虽然进程将不同空间分块,但其它线程也可以互相访问,只要拿到相应的地址即可,这也带来了一定的风险。

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

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

相关文章

GitHub基本操作及Git简单命令

GitHub简介 GitHub就是一个远程仓库,远程仓库可以理解为就是一个可以保存自己代码的地方,在实际开发当中一个项目往往是有多个人来共同协作开发完成的,那么就需要一个统一代码保存的地方,而GitHub就是起到一个共享和汇总代码的作…

数据结构(陈越,何钦铭)第三讲 树(上)

3.1 树与数的表示 3.1.1 顺序查找 int SequentialSearch(List Tbl,ElementType K){int i;Tbl->Element[0]K;for(iTbl->Length;Tbl->Element[i]!K;i--);return i; } typedef struct LNode *List; struct LNode{ElementType Element[MAXSIZE];int Length; };3.1.2 二分…

【PYTORCH】官方的turoria实现中英文翻译

参考 https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html 背景 pytorch官方的是seq2seq是法语到英文,做了一个中文到英文的。 数据集 下载后解压,使用的data\testsets\devset\UNv1.0.devset.zh和UNv1.0.devset.en&#x…

仿叮咚买菜鸿蒙原生APP

# DingdongShopping 这是一个原生鸿蒙版的仿叮咚买菜APP项目 鸿蒙Next发布至今已经有一年多的时间了,但有时候我们想要实现一些复杂的功能或者效果,在开发文档上查阅一些资料还是比较费时的,有可能还找不到我们想要的内容。而社会层面上分享…

VSCode 接入DeepSeek V3大模型,附使用说明

VSCode 接入DeepSeek V3大模型,附使用说明 由于近期 DeepSeek 使用人数激增,服务器压力较大,官网已 暂停充值入口 ,且接口响应也开始不稳定,建议使用第三方部署的 DeepSeek,如 硅基流动 或者使用其他模型/插件,如 豆包免费AI插件 MarsCode、阿里免费AI插件 TONGYI Lin…

中上211硕对嵌入式AI感兴趣,如何有效规划学习路径?

今天给大家分享的是一位粉丝的提问,中上211硕对嵌入式AI感兴趣,如何有效规划学习路径? 接下来把粉丝的具体提问和我的回复分享给大家,希望也能给一些类似情况的小伙伴一些启发和帮助。 同学提问: 中上211,…

Linux 目录结构与基础命令学习记录

在 Linux 的学习旅程中,熟练掌握基础命令是开启高效操作与系统管理的钥匙。这些命令不仅能帮助我们在 Linux 系统中自由穿梭,还能深入了解系统的运行状态。以下是我对 Linux 基础命令的学习总结,希望能为大家的 Linux 学习提供帮助。 一、Lin…

python学opencv|读取图像(六十五)使用cv2.boundingRect()函数实现图像轮廓矩形标注

【1】引言 前序学习进程中,已经使用cv2.findContours()函数cv2.drawContours()函数实现图像轮廓识别和标注,这种标注沿着图像的轮廓进行,比较细致。相关文章链接为: python学opencv|读取图像(六十四)使用…

Visionpro 齿轮测量

效果展示 一、题目要求 求出最大值,最小值,平均值 二、分析 1.首先要进行模板匹配 2.划清匹配范围 3.匹配小三角的模板匹配 4.卡尺 5.用找圆工具 工具 1.CogPMAlignTool 2.CogCaliperTool 3.CogFindCircleTool 4.CogFixtureTool 三、模板匹…

【ISO 14229-1:2023 UDS诊断(会话控制0x10服务)测试用例CAPL代码全解析②】

ISO 14229-1:2023 UDS诊断【会话控制0x10服务】_TestCase02 作者:车端域控测试工程师 更新日期:2025年02月15日 关键词:UDS诊断、0x10服务、诊断会话控制、ECU测试、ISO 14229-1:2023 TC10-002测试用例 用例ID测试场景验证要点参考条款预期…

AlmaLinux使用Ansible自动部署k8s集群

一、环境准备 节点规划(最低要求) 1台Master节点(4核/8GB内存)2台Worker节点(2核/4GB内存)1台Ansible控制机(可复用Master节点) 系统配置 # 所有节点执行 sudo hostnamectl set-hos…

机器学习:十大算法实现汇总

机器学习十大算法代码实现:使用numpy、pandas,不调用机器学习相关库。 已将代码和相关文档上传到了github:golitter/Decoding-ML-Top10: 使用 Python 优雅地实现机器学习十大经典算法。 (github.com) 一元线性回归:机器学习&…

ffmpeg学习:ubuntu下编译Android版ffmpeg-kit

文章目录 前言一. 配置环境1.1 虚拟机版本1.2 安装Android环境1.2.1 Android SDK安装1.2.2 Android NDK安装 1.3 编译前的准备工作1.3.1 libtasn1-1安装1.3.2 meson安装1.3.3 harfbuzz下载 二. 编译ffmpeg-kit三. 总结 前言 ffmpeg-kit是一款跨多个平台的,用于在应…

Qt使用pri和pro文件进行模块化编程

假如我想要做一个功能,这个功能用代码模块化实现出来,方便将来移植,比如音视频播放器的界面,将来想要在其他工程使用时,只需要将widget提升为音视频播放界面即可。 当我们其他工程需要这个功能时,我们在调用…

C# windowForms 的DataGridView控件的使用

C# Windows Forms DataGridView 控件使用详解 DataGridView 是 Windows Forms 中用于显示和编辑表格数据的核心控件。它支持高度自定义的列类型、数据绑定、事件处理和丰富的样式配置。以下是其详细使用方法。 目录 基础使用 数据绑定 列类型与自定义

PyQt 界面编程:QDialog、QWidget、QMainWindow 的面向过程与面向对象编程

文章目录 一、PyQt简介二、面向过程编程三、面向对象编程(推荐)3.1 QWidget窗口3.2 QMainWindow窗口3.3 QDialog窗口文档: https://www.riverbankcomputing.com/static/Docs/PyQt5/ 一、PyQt简介 PyQt简介:PyQt 是一个用于创建图形用户界面(GUI)的 Python 库,它将 Qt …

Jvascript网页设计案例:通过js实现一款密码强度检测,适用于等保测评整改

本文目录 前言功能预览样式特点总结:1. 整体视觉风格2. 密码输入框设计3. 强度指示条4. 结果文本与原因说明 功能特点总结:1. 密码强度检测2. 实时反馈机制3. 详细原因说明4. 视觉提示5. 交互体验优化 密码强度检测逻辑Html代码Javascript代码 前言 能满…

智能车摄像头开源—8 元素处理

目录 一、前言 二、无元素状态 三、直线与弯道 四、十字与环岛 1、十字识别处理 2、环岛识别处理 五、坡道 六、障碍物 七、斑马线 八、入库 九、出界停车 一、前言 在写这篇文章之前,考虑了很久到底该写到什么程度,但思来想去,不同…

微信服务号推送消息

这里如果 没有 就需要点新的功能去申请一下 申请成功之后就可以设置模版消息 推送到用户接受的页面是 需要后端调用接口 传递token 发送给客户

matlab汽车动力学半车垂向振动模型

1、内容简介 matlab141-半车垂向振动模型 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略