Linux初识线程

news2024/12/23 0:52:41

前言

前面在介绍进程的时候,说过进程的内核表述是"进程是承担资源分配的基本实体",但是我们至今都没有介绍如何理解他?本期我们就会介绍!

目录

前言

一、再谈地址空间和页表

1、OS对物理内存的管理

• 为什么4KB是OS进行I/O的基本单位?

2、再谈页表

• 二级页表

• 如何找到一个变量的所有字节?

• 虚拟地址是如何转为物理地址的?

• 理解动态内存管理

• 为什么对常量不能修改?

• 浅理解文件缓冲区

二、初识线程

1、线程的概念

2、Linux下线程

3、进程和线程的关系

4、有进程了,为什么还要有线程?

5、如何理解线程调度的成本更低?

三、线程总结

1、线程的优点

2、线程的缺点

3、线程异常

4、线程的用途

5、Linux进程VS线程

1、线程的私有数据

2、多线程的共享数据


一、再谈地址空间和页表

这是我们第四次也是最后一次谈地址空间了!

第一次,是在初识地址空间引入的;

第二次,在动静态库引入了虚拟地址空间的地址来自编译后的源文件;

第三次,是在信号引入内核太和用户态的时候引入了OS内核;

本次,我们将介绍物理内存的管理、二级页表;


1、OS对物理内存的管理

当一个进程被运行时,他的代码和数据要加载到内存;但是OS如何知道要把数据load到物理内存的那个位置?也就是OS如何知道哪些位置是可用的呢?其实OS也是会对物理内存进行管理的,如何管理?先描述,再组织!

OS会把物理内存"划分"成一个个的数据块,每个内存块的大小默认是 4KB。把这个数据块叫做页框/页帧

此时对物理内存的管理变成了,对一个个页框/页帧的管理!所以先用一个struct page的结构体进行对页框/页帧描述,再用数组对一个个的struct page对象进行组织!即对物理内存的管理就变成了数组的管理!

struct page的结构体大致如下:

struct page
{
    int flag; // 是否被占用、是否是脏页、是否被绑定
    int mode; // 权限
    //.....
};

这里看到这个数据块的大小默认是 4KB,是不是感觉有点熟悉呀!我们前面在文件系统介绍了磁盘的I/O基本单位就是4KB(8个扇区)!其实 4KB 是OS进行I/O的基本单位

• 为什么4KB是OS进行I/O的基本单位?

OS的I/O无非两方面:1、内存换出到磁盘 2、磁盘加载到内存!

第一个原因是,磁盘中的数据也是以4KB的数据块进行存储的,物理内存也是以4KB的页进行管理的,所以这样设计提高了数据的传输效率!

第二个原因是,局部性原理。局部性原理又分为时间局部性和空间局部性!

• 时间局部性原理:当前正在访问的资源在未来还会访问。

• 空间局部性原理:未来访问的资源是当前访问资源的临近资源。

比如,父子进程共享一个全局变量,当父子进程不对这个全局变量修改时,他们共享!一旦其中一个进程进行写入时,OS就要发生写时拷贝,也就是要开辟物理内存,然后拷贝改全局变量的数据!然而,虽然只是修改一个变量但是在写时拷贝的时候会将那一个4KB的数据块进行整体拷贝,这样比你修改一个我给你拷贝一个的效率高,其实可以理解为一种预加载机制!


2、再谈页表

我们以前都是简单的说,页表是一张进行虚拟地址到物理地址转换的表!但是,不知道你想过没有,我们以32位的机器为例,虚拟地址是2^32个,也就是页表左边的虚拟地址就是2^32个,再加上右边对应的物理地址,2^32个,再加上标记为2个!而32位每个指针4字节,标记为都算成一个字节,也就是每一行是10个字节,一共2^32个,计算下来就是40GB,你还啥都没干,就搞了个页表就40GB,你的内存在大也没有40GB吧!所以我们以前对页表的理解是太简单了,这里需要我们重新认识页表了!

• 二级页表

我们这里以32位,为例。虚拟地址其实就是32位的一个二进制。其中,将32位分成了三段,分别是10位,10位,12位

前10位一共有2^10=1024个虚拟地址,他是用来在页目录中做索引的!其中每个页目录中的内容是指向一张页表!该页表的大小是2^10=1024,然后取中间的10位,中间10位的范围正好是[0,1023],所以中间的10位是获取对应页表的下标的!而每个页表的元素都指向一个页框的起始地址!

最后的12位的范围就是[0,4095]正好可以把一个数块的每一个字节都获取到!所以,最后12位是页内偏移

而我们把上述的页表称为二级页表!其实页目录和页表即前10位和中间10位的目的是在搜索页框!

这样下来,每个页表的元素算4字节,1024*4KB=4MB+4KB的页目录,这可比前面的40GB小太多了,而且内存中也可以放下了!

• 如何找到一个变量的所有字节?

我们目前,可以通过目录+页表搜索到对应页框的起始地址,然后加上页内偏移找到对应的起始字节!但是我们平时的数据可不是一个字节啊!例如int类型的变量是4个字节,如何找到呢?这个问题呢,很好解决!每一个变量是不是有类型啊,每个类型是不是在语言层面就规定好了几个字节,所以找到也框中数据的起始字节后,根据类型,获取类型对应的大小偏移对应的字节即可!

• 虚拟地址是如何转为物理地址的?

我们前面就介绍过,CPU读取的都是虚拟地址!当CPU读取到虚拟地址后,由内部的寄存器MMU(Memory Management Unit,内存管理单元)进行根据页目录和页表进行搜索页框,找到对应的叶匡起始地址,然后由页内偏移找到具体的物理地址!很好,但是CPU如何找到页目录表呢?其实在CPU内部有一个CR3寄存器,是存储当前进程的页目录表的!OK,这样CPU拿到虚拟地址后出来的就直接是物理地址!其实如果你拆开过电脑的话,你也会看见CPU人家是直接和物理内存通过总线连接的!

• 理解动态内存管理

当我们在动态的(malloc/new等)申请堆上的空间时操作系统并没有立即给你申请物理内存空间(申请了不一定立即用),而是先给你申请虚拟地址以及构建页目录和页表,此时页表中没有实际的物理页框的地址等我们实际用的时候,查页表+寻物理地址,发现此时的页表中的物理地址没有,此时就会发生缺页中断,OS发出对应的中断信号,然后陷入内核态,根据CPU寄存器中的中断号,执行中断向量表中的对应方法。这里就是申请物理内存,构建页表信息!然后用户就可以正常的使用了,这个过程很快用户几乎没有感知!

和动态申请内存这种赌博的方式的操作,OS中很常见的例如:写实拷贝、文件IO、动态内存管理!

• 为什么对常量不能修改?

我们前面都是记住了,不能对常量修改!比如:

char* ptr = "hello world”;

*ptr = "hehe";

就不可以被修改!但是为什么呢?当把他加载到内存之后,我们有了对应的虚拟地址+页表就可以找到他的物理地址,那不就可以随便修改了吗?为什么不能修改呢?

其实,页表中还有标记位!比如:是否命中、RWX权限、U/K权限、访问位等!

当CPU执行代码是拿到虚拟地址,根据CR3+MMU转换为物理地址时,会检查页表对应的标记位,这里的对常量修改发现他的权限是R,此时发生异常中断,即OS对该进程发送信号,然后陷入内核态对该进程的信号保存,当有内核态切换为用户态时,检测信号处理信号即发生段错误!

到这里我们对地址空间和页表所有的相关东西就打通了,再次回顾这种设计,我们发现 用户根本不知道,也不需要知道地址空间之后的事情,只需要正常使用即可,当发生非法操作时OS可以通过查页表进行拦截,而不影响物理内存

其实这里的 虚拟地址空间 本质就是在物理内存和用户直接添加的一个软件层,这样做的好处就是当多个进程访问物理内存时可以有效的保护物理内存,同时也为用户提供了统一的地址空间!即做到了完美的物理与虚拟的解耦

这种设计思想其实在计算机界是非常出名的,“所有问题都可以加一层软件层解决”,这种设计思想不仅用于 OS,还适用于 网络 ,比如赫赫有名的 OSI 七层模型

• 浅理解文件缓冲区

文件缓冲区其实本质也是一段物理内存!将这段物理内存申请之后,不将他映射到页表中,而是将他挂接到前面介绍的虚拟文件系统的struct file对象中!然后struct file对象和文件描述符表中的fd_array进行映射!当然我们这只是非常简单的理解,真实的比这个复杂的多!


二、初识线程

1、线程的概念

• 教材观点

线程是进程内部的一个执行流,执行的力度比进程更细、调度成本更低

• 内核观点

进程是承担资源分配的基本实体,线程在进程内部运行,是CPU调度的基本单位

结合上述的观点,可以得到下面的图:

那既然线程是进程内部的一个执行流,那一个进程是不是可以有多个执行流,即有多个线程!那OS要不要对这么多的线程管理呢?答案毫无疑问是要的!如何管理?先描述,再组织!所以,就得有描述线程的结构体,这个结构体叫线程控制块 TCB(Thread Control Block),然后用某种数据结构组织起来即可!

其中,对于上述的管理思想各个平台的实现是不一样的!windows就是老老实实的干了

但是Linux可不是这样实现的!下面我们呢就来介绍一下Linux中的线程 !

2、Linux下线程

Linux并不是和上面的windows一样实现的!而是复用了进程的PCB等!原因是,线程是进程内部的一个执行流,和进程共用进程的大部分资源,也就是线程是进程内部的一个PCB,而在CPU眼中是按照PCB调度的,不管线程还是进程都是执行流,所以Linux干脆不叫线程,而叫做轻量级进程(LWP)!所以Linux中的线程是由进程模拟实现的!这样做的好处就是可以和进程一样使用和管理,大大减小了维护成本!

3、进程和线程的关系

我们以前都是说,进程=内核数据结构+代码和数据!而上面刚说完,进程是资源分配的基本实体!这两个概念会不会有冲突啊?不会,它两其实说的是一个意思,进程运行起来,要想OS申请内核数据结构,物理内存!这些不就是资源嘛!OS中资源分配是由进程为基本单位的,所以进程是资源分配的实体/基本单位!线程是进程中的一个执行流即一个PCB当进程中只有一个执行流即只有一个线程时,此时的线程就是进程!因为线程是进程中的执行流,他执行时的资源都是进程的!而在CPU眼中,无论进程还是线程,调度只认 PCB,即线程,所以线程是CPU调度的基本单位!

总结:

1、进程中的一个执行流叫线程

2、Linux中的线程是由进程模拟实现的

3、一个进程中至少有一个执行流(线程)

4、Linux中,CPU眼中的PCB比进程的更加轻量化(调度/切换成本更低)

5、线程透过进程的虚拟地址空间,可以看到进程的大部分资源,将进程的资源合理的分配给每个执行流,就形成了线程的执行流

4、有进程了,为什么还要有线程?

前面也说过了,进程在运行前要加载个中数据,以及创建各种数据结构,而这些都本质是资源!所以,进程的创建成本是非常高的!而线程而言,创建就是创建一个PCB,使用的是进程申请的资源!所以创建线程的成本是很低的!其次,进程的调度成本比线程的高(这点很重要,下面专门谈);最后,销毁而言,进程是要销毁各种数据结构和各种数据资源!但是线程销毁的话,只需要把PCB删除即可,成本比进程低的多!

5、如何理解线程调度的成本更低?

线程调度的话,不需要管CR3的页目录表,因为他共用的就是进程的!进程调度的话,就比线程多一个寄存器的事情,为什么说线程的调度比进程更低呢?确实站在上层看,没什么差别!它的原因是在底层!

一是线程的上下文切换的开销小

由于共享了进程的内存空间和资源,因此只需要保存和恢复线程特有的上下文,这比进程切换时需要保存和恢复整个进程的上下文要小得多。因此,线程切换的开销更小。

二是局部缓存性

CPU在调度的时候,为了提高效率根据局部性原理,会把一些可能访问的数据(“热数据”)提前预加载到CPU中的cache中,线程切换调度的话,另一个线程可能也会用到这些热数据,所以就不需要在重新到内存加载这些热数据了,既提高了效率也降低了调度成本!但是进程切换调度的时候并不会用到原先cache中的数据,就会重新到内存加载这些热数据,所以没有线程的切换调度效率高了!

三、线程总结

1、线程的优点

• 创建一个新线程的代价比创建一个新进程的小的多

• 与进程直接切换相比,线程之间的切换需要OS做的工作很少

• 线程占用的资源比进程少

• 能充分利用多处理器的并行数量

• 在等待慢速 I/O 操作结束时间,程序可执行其他的计算任务

• 计算密集型应用、为了能在多处理器系统上运行,将计算分解到多个线程中实现

• I/O密集型应用,为了提高性能,将提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作;

2、线程的缺点

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

3、线程异常

• 单个线程如果出现,除0/野指针等异常,此时OS会给当前线程的进程发送信号导致,当前进程异常终止(也就是进程奔溃)

• 线程是进程的一个分支,线程出现异常,就类似于进程直接出现异常,进程会触发异常机制想改进程发送信号,终止当前的进程;进程终止了,该进程内的所有线程也就是随之推出了!

4、线程的用途

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

5、Linux进程VS线程

• 进程是资源分配的基本单位

• 线程是调度的基本单位

1、线程的私有数据

线程共享进程的数据,但是也拥有自己的部分数据:

• 线程ID

• 一组寄存器

• 栈

• errno

• 信号屏蔽字

• 调度优先级

其中上面用红色标记出来的这两个是最重要的!

一组寄存器体现出来的是硬件的上下文数据线程是动态运行的

体现的是每个线程在运行时都会有临时数据,这些数据都保存在各自的栈区!

2、多线程的共享数据

多个线程在同一个进程内部,所以共享同一个地址空间,即共享数据段和代码段,如果定义一个函数,在各个线程中都可以调用,如果定义一个全局变量,在各个线程中都可以访问到,除此之外,各个线程还共享以下进程资源和环境:

• 文件描述符表

• 每种信号的处理方式(SIG_IGN、SIG_DFL、或者自定义信号的处理函数)

• 当前工作目录

• 用户 id 和 组id

进程和线程的关系图:

结束语:焦虑也没用,冲就完事了!

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

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

相关文章

RocketMQ之发送消息源码分析

RocketMQ之send()源码分析 一、代码序列图 二、关键步骤分析 1、向namesrv拉取队列信息 2、选择目标队列 3、向broker发送消息 三、代码学习 1、代码结构 (设计模式) 2、工具类和方法

HarmonyOS应用开发环境搭建

本文主要讲述的是HarmonyOS应用开发环境的搭建,HUAWEI DevEco Studio是基于IntelliJ IDEA Community开源版本打造,为运行在HarmonyOS系统上的应用和服务提供一站式的开发平台。具体下载链接DevEco Studio 一、下载 DevEco Studio 只需要下载对应的版本&…

Java程序到CPU上执行 的步骤

相信很多的小伙伴在最初学习编程的时候会容易产生一个疑惑❓,那就是编写的Java代码究竟是怎么一步一步到CPU上去执行的呢?CPU又是如何执行的呢?今天跟随小编的脚步去化解开这个疑惑❓。 在学习这个过程之前,我们需要先讲解一些与…

加密与安全_ sm-crypto 国密算法sm2、sm3和sm4的Java库

文章目录 sm-crypto如何使用如何引入依赖 sm2获取密钥对加密解密签名验签获取椭圆曲线点 sm3sm4加密解密 sm-crypto https://github.com/antherd/sm-crypto 国密算法sm2、sm3和sm4的java版。基于js版本进行封装,无缝兼容js版公私钥加解密。 PS: js版:h…

SpringBoot中利用EasyExcel+aop实现一个通用Excel导出功能

一、结果展示 主要功能:可以根据前端传递的参数,导出指定列、指定行 1.1 案例一 前端页面 传递参数 {"excelName": "导出用户信息1725738666946","sheetName": "导出用户信息","fieldList": [{&q…

2024 年高教社杯全国大学生数学建模竞赛C题—农作物的种植策略(讲解+代码+成品论文助攻,均已更新完毕)

2024数学建模国赛选题建议团队助攻资料-CSDN博客文章浏览阅读1k次,点赞20次,收藏24次。通过分析5个题目的特点,可知数学建模常用的模型大概可以分为五大类——https://blog.csdn.net/qq_41489047/article/details/141925859 本次国赛white学长…

Gitflow基础知识

0.理想状态 现状 听完后的理想状态 没使用过 git 知道 git 是什么,会用 git 基础流程命令 用过 git,但只通过图形化界面操作 脱离图形化界面操作,通过 git 命令操作 会 git 命令 掌握 gitflow 规范,合理使用 rebase 和解决…

ffmpeg安装测试(支持cuda支持SRT)

文章目录 背景安装ffmpeg直接下载可执行文件选择版本选择对应系统版本下载测试Linux下安装 查看支持协议以及编码格式 常见错误缺少 libmvec.so.1LD_LIBRARY_PATH 错误 GPU加速测试SRT服务器搭建下载srs5.0源码解压安装配置启动 SRT推流测试SRT播放测试 背景 在音视频开发测试中…

Kafka 分布式消息系统详细介绍

Kafka 分布式消息系统 一、Kafka 概述1.1 Kafka 定义1.2 Kafka 设计目标1.3 Kafka 特点 二、Kafka 架构设计2.1 基本架构2.2 Topic 和 Partition2.3 消费者和消费者组2.4 Replica 副本 三、Kafka 分布式集群搭建3.1 下载解压3.1.1 上传解压 3.2 修改 Kafka 配置文件3.2.1 修改z…

Java操作Elasticsearch的实用指南

Java操作Elasticsearch的实用指南 一、创建索引二、增删改查 一、创建索引 在ElasticSearch中索引相当于mysql中的表,mapping相当于表结构,所以第一步我们要先创建索引。 假设我们有一张文章表的数据需要同步到ElasticSearch,首先需要根据数据库表创建…

DisplayManagerService启动及主屏添加-Android13

DisplayManagerService启动及主屏添加-Android13 1、DisplayManagerService启动1.1 简要时序图 2、DEFAULT_DISPLAY主屏幕添加2.1 物理屏热插拔监听2.2 物理屏信息 3、默认屏幕亮度 1、DisplayManagerService启动 1.1 简要时序图 代码位置:frameworks/base/service…

git:基本操作(2)

目录 git操作(2) 1.版本回退 2.撤销修改 3.删除文件 git操作(2) 1.版本回退 git能够管理文件的历史版本,这也是版本控制器的重要能力,因此,git也提供了版本回退这样的功能。 执行git reset…

QT6聊天室项目 网络通信实现逻辑分析

实现逻辑 模块话网络通信设计分析 NetClient类 功能:负责与服务器进行通信httpClient:处理HTTP请求websocketClient:处理WebSocket通信 HTTP请求封装 设计请求和服务器响应的接口设计函数测试网络连接性设计处理的函数处理HTTP请求(后期实现…

C#/.NET/.NET Core推荐学习路线文档文章

前言 专门为C#/.NET/.NET Core推荐学习路线&文档&文章提供的一个Issues,各位小伙伴可以把自己觉得不错的学习路线、文档、文章相关地址分享出来🤞。 https://github.com/YSGStudyHards/DotNetGuide/issues/10 🏷️C#/.NET/.NET Cor…

智慧工地解决方案-2

### 1. 智慧工地解决方案概述 《智慧工地解决方案》针对传统工地的低效率和高风险问题,提出了一套集成现代技术的智能管理系统,以提升工地安防和生产效率。 ### 2. 工地现状与挑战 当前工地存在安全意识薄弱、管理粗放、环境污染监测困难等问题&#…

数据分析面试题:客户投保问题分析

目录 0 场景描述 1 数据准备 2 问题分析 2.1 计算小微公司的平均经营时长 2.2 计算小微公司且角色为投保人,保险起期在18年的总保费 2.3 假设,DWD_CUSTOMER_REL客户关联关系表中,存在部分客户保单数很多,部分客户保单数很少的情况,此时DWD_CUSTOMER_BASE表关联,程序…

Learn ComputeShader 10 HUD Overlay

前言: 1. HUD Overlay (Head-Up Display Overlay) 定义: HUD 是指游戏或应用程序中的一类叠加界面元素,通常显示在屏幕上,用于向用户提供实时信息。它通常显示关键信息而不会打断用户的主要活动或视线。应用场景: 常见于游戏、飞行模拟器和…

[项目][CMP][Page Cache]详细讲解

目录 1.申请内存2.释放内存3.框架 1.申请内存 当Central Cache向Page Cache申请内存时,Page Cache先检查对应位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个 比如:申请的是4页page,4页page后面…

【MRI基础】TI反转时间概念

在磁共振成像 (MRI) 中,反转时间 (TI) 是反转恢复脉冲序列中的一个特定参数。它表示施加 180 度反转脉冲(将纵向磁化翻转到相反方向)与随后的 90 度激励脉冲(将磁化翻转到横向平面以创建 MR 信号)之间的时间间隔。 MRI…

常见概念 -- 电层业务调制谱宽与光层通道谱宽

本文介绍了“电层业务调制谱宽”和“光层通道谱宽”这两个概念,并结合网管的配置界面解释二者的配置方法。 电层业务调制谱宽 电层业务调制谱宽与光线路码型唯一相关,光线路码型确定后谱宽随之确定。 电层业务调制谱宽是指某业务信号的损耗谱从峰值下…