计算机操作系统(一):硬件结构

硬件结构

CPU是如何工作的

图灵机工作方式

图灵机主要由两部分组成:

  • 纸带:由格子组成,可写入字符,格子类比内存,字符类比数据;
  • 读写头:分为存储单元、控制单元、运算单元
    • 存储单元:用于存放数据
    • 控制单元:用于识别字符功用
    • 运算单元:用于执行运算指令

例子:3=1+2

纸带上为12+,读写头依次读取,识别到数字存储,识别到运算符运算,最后数据为123

冯诺依曼模型

  • 运算器
  • 控制器
  • 存储器
  • 输入设备
  • 输出设备

组成如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkHlNMot-1690708501986)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/Von_Neumann_architecture.svg)]

内存Memory

image-20230713085850290

中央处理器Control Unit and Arithmetic Logic Unit

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h6zqpCn9-1690708501987)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E5%86%AF%E8%AF%BA%E4%BE%9D%E6%9B%BC%E6%A8%A1%E5%9E%8B.png)]

32位CPU单位计算量为4字节,64位CPU单位计算量为8字节,大数据运算,单位计算量大的CPU计算更快。

组成:

  • 寄存器:
    • 通用寄存器:用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
    • 程序计数器:用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。
    • 指令寄存器:用来存放当前正在执行的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。
  • 控制单元:负责控制 CPU 工作
  • 逻辑运算单元:负责数据计算
总线

总线用于 CPU 和内存以及其他设备之间的通信,告知CPU数据地址,数据内容,是读是写

  • 地址总线,用于指定 CPU 将要操作的内存地址;
  • 数据总线,用于读写的内存数据;
  • 控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;
输入、输出设备

输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备

线路位宽和CPU位宽

线路传输数据通过操作电压实现,低电压0,高电压1

image-20230713093134997

若是只有两条线路,传输0110需要两次才可以完成,所以线路的位宽最好一次就能访问到所有内存地址。

CPU 想要操作「内存地址」就需要「地址总线」:

  • 如果地址总线只有 1 条,那每次只能表示 「0 或 1」这两种地址,所以 CPU 能操作的内存地址最大数量为 2(2^1)个(注意,不要理解成同时能操作 2 个内存地址);
  • 如果地址总线有 2 条,那么能表示 00、01、10、11 这四种地址,所以 CPU 能操作的内存地址最大数量为 4(2^2)个。

32 位 CPU 一次最多只能操作 32 位宽的地址总线和数据总线

如果计算的数额不超过 32 位数字的情况下,32 位和 64 位 CPU 之间没什么区别的,只有当计算超过 32 位数字的情况下,64 位的优势才能体现出来

程序执行基本过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uY2en85Q-1690708501987)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/CPU%E6%89%A7%E8%A1%8C%E7%A8%8B%E5%BA%8F.png)]

最后能够回忆到以下内容

CPU 执行程序的过程如下:

  • 第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
  • 第二步,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由 CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4;
  • 第三步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行;

简单总结一下就是,一个程序执行的时候,CPU 会根据程序计数器里的内存地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。

CPU 从程序计数器读取指令、到执行、再到下一条指令,这个过程会不断循环,直到程序执行结束,这个不断循环的过程被称为 CPU 的指令周期

32位执行a=1+2

简要过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENftNevh-1690708501988)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E6%95%B0%E6%8D%AE%E6%AE%B5%E4%B8%8E%E6%AD%A3%E6%96%87%E6%AE%B5.png)]

编译器会把 a = 1 + 2 翻译成 4 条指令,存放到正文段中。如图,这 4 条指令被存放到了 0x100 ~ 0x10c 的区域中:

  • 0x100 的内容是 load 指令将 0x200 地址中的数据 1 装入到寄存器 R0
  • 0x104 的内容是 load 指令将 0x204 地址中的数据 2 装入到寄存器 R1
  • 0x108 的内容是 add 指令将寄存器 R0R1 的数据相加,并把结果存放到寄存器 R2
  • 0x10c 的内容是 store 指令将寄存器 R2 中的数据存回数据段中的 0x208 地址中,这个地址也就是变量 a 内存中的地址;

编译完成后,具体执行程序的时候,程序计数器会被设置为 0x100 地址,然后依次执行这 4 条指令。

指令

MIPS指令集

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2iDJzlW-1690708501988)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/MIPS%E6%8C%87%E4%BB%A4%E9%9B%86.png)]

R指令用于算数和逻辑操作,I指令用于数据传输、条件分支等,J指令用于跳转

具体比如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjXlUulG-1690708501988)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/add%E7%9A%84MIPS%E6%8C%87%E4%BB%A4.png)]

加和运算 add 指令是属于 R 指令类型:

  • add 对应的 MIPS 指令里操作码是 000000,以及最末尾的功能码是 100000,这些数值都是固定的,查一下 MIPS 指令集的手册就能知道的;
  • rs 代表第一个寄存器 R0 的编号,即 00000
  • rt 代表第二个寄存器 R1 的编号,即 00001
  • rd 代表目标的临时寄存器 R2 的编号,即 00010
  • 因为不是位移操作,所以位移量是 00000

把上面这些数字拼在一起就是一条 32 位的 MIPS 加法指令了,那么用 16 进制表示的机器码则是 0x00011020

指令周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrdelfGo-1690708501988)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/CPU%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F.png)]

  1. CPU 通过程序计数器读取对应内存地址的指令,这个部分称为 Fetch(取得指令)
  2. CPU 对指令进行解码,这个部分称为 Decode(指令译码)
  3. CPU 执行指令,这个部分称为 Execution(执行指令)
  4. CPU 将计算结果存回寄存器或者将寄存器的值存入内存,这个部分称为 Store(数据回写)
指令类型

指令从功能角度划分,可以分为 5 大类:

  • 数据传输类型的指令,比如 store/load 是寄存器与内存间数据传输的指令,mov 是将一个内存地址的数据移动到另一个内存地址的指令;
  • 运算类型的指令,比如加减乘除、位运算、比较大小等等,它们最多只能处理两个寄存器中的数据;
  • 跳转类型的指令,通过修改程序计数器的值来达到跳转执行指令的过程,比如编程中常见的 if-elseswitch-case、函数调用等。
  • 信号类型的指令,比如发生中断的指令 trap
  • 闲置类型的指令,比如指令 nop,执行后 CPU 会空转一个周期;

磁盘比内存慢几万倍

磁盘内存都同属存储器,计算机的存储器还有:

  • CPU寄存器
  • CPU Cache高速缓存
  • 内存
  • SSD/HDD硬盘

时钟周期

即CPU时钟周期,这与CPU的主频有关,比如我的12490f单核频率为4.6GHz,那么它的时钟周期大概为:1/4.6约等于0.22ns

各个存储器的读写速度

寄存器

半个时钟周期

CPU Cache

使用SRAM-Static Random Access Memory-静态随机存储器,1bit数据需要6个晶体管,所以断电后数据就会消失。

CPU的高速缓存分为L1、L2、L3三层,如果你使用的window系统,可以从任务管理器中查看到自己的三级缓存大小。

L1 高速缓存的访问速度几乎和寄存器一样快,通常只需要 2~4 个时钟周期,L1 高速缓存通常分成指令缓存数据缓存

L2 高速缓存每个CPU核心都会有,访问速度在 10~20 个时钟周期。

L3 高速缓存通常是多个 CPU 核心共用的,访问速度在 20~60个时钟周期。

内存

使用DRAM-Dynamic Random Access Memory-动态随机存取存储器,1bit数据需要1个晶体管和1个电容,电容的存在让数据能够存储,但需要定时刷新电容才会保证数据不会丢失。访问速度在200~300 个 时钟周期之间。

SSD/HDD硬盘

SSD也就是固态硬盘,材料结构与内存类似,访问速度比内存慢10~1000倍

HDD机械硬盘,远古产物,物理读写,速度比内存慢10w左右。

存储器的层次关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YsHBj72X-1690708501988)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E5%AD%98%E5%82%A8%E5%99%A8%E7%9A%84%E5%B1%82%E6%AC%A1%E5%85%B3%E7%B3%BB%E5%9B%BE.png)]

如何写出让CPU跑的更快的代码

明白了计算机存储设备的访问速度,只要使得数据更容易在高速缓存区找到,而不是去龟速的硬盘去找即可提高代码速度。我们需要知道Cache的数据存储机制。

CPU Cache结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhGuXWW0-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/Cache%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png)]

Cache Line 即 L1 Cache一次载入数据的大小。

Cache的数据访问逻辑

直接映射Cache-Direct Mapped Cache

即直接把内存块地址始终映射到一个CPU Cache Line地址,映射关系为取模运算,同时为避免多个内存块对应同一个CPU Cache Line地址,在对应的 CPU Cache Line 中我们还会存储一个组标记(Tag),用于区分不同的内存块。

除此之外,CPU Cache Line还有两个信息:数据(Data)有效位(Valid bit)

CPU从Cache读取数据会只寻找需要的一个数据片段,因此还会需要一个偏移量(Offset)

一个内存的访问地址,包括组标记、CPU Cache Line 索引、偏移量这三种信息,于是 CPU 就能通过这些信息,在 CPU Cache 中找到缓存的数据。而对于 CPU Cache 里的数据结构,则是由索引 + 有效位 + 组标记 + 数据块组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lFBIGi0j-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E7%9B%B4%E6%8E%A5Cache%E6%98%A0%E5%B0%84.png)]

CPU的缓存一致性

从Cache中读取是要比在内存中读取更加快速,提高代码的缓存命中率能够有效提高程序的性能。

数据不只有读操作,还有写操作,数据写入Cache后,与内存的数据不一致,此时需要同步操作。

Cache的写入策略

主流的CPU的缓存写入策略为:写直达和写回

写直达

即保持内存与Cache数据一致的最简单的方法——将数据同时写入Cache中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lRkmalCr-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E5%86%99%E7%9B%B4%E8%BE%BE.png)]

如此,无论数据在不在Cache里,每次写操作都会将数据写入到内存中,即是内存存在数据?

写回

在写回机制中,当发生写操作时,新的数据仅仅被写入 Cache Block 里,只有当修改过的 Cache Block「被替换」时才需要写到内存中,减少了数据写回内存的频率,这样便可以提高系统的性能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERv5yNzH-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E5%86%99%E5%9B%9E1.png)]

Cache Block是否为脏表示其与内存里的数据是否一致,添加是否为脏的条件判断能够对是否同步Cache与内存数据提供判断依据,减少不必要的同步操作。

缓存一致性问题

CPU的每个核心都拥有独立的Cache,核心与核心之间的数据无法一致,于是出现执行结果错误的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3ZpVc4y-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98%E4%BE%8B%E5%AD%902.png)]

写传播和事物的串行化

要解决缓存一致性问题,保证两点即可:

  1. 某个CPU核心Cache数据更新时,将更新同步到其他核心的Cache,此称之为写传播
  2. 某个CPU核心对于数据的操作顺序信息,传到其他核心是一样的,也就是同步操作的顺序也需要一致,此称之为事物的串行化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PaWOCakJ-1690708501989)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E4%BA%8B%E4%BB%B6%E9%A1%BA%E5%BA%8F%E9%97%AE%E9%A2%98-20230716104948646.png)]

做到事物串行化,需要引入,如果两个 CPU 核心里有相同数据的 Cache,那么对于这个 Cache 数据的更新,只有拿到了「锁」,才能进行对应的数据更新。

总线嗅探与MESI协议

总线嗅探即在各个CPU核心Cache之间穿起一条总线,当嗅探到数据变化时,将此作为事件传播到其他拥有相同数据的核心Cache进行数据更新。

但是无时不刻进行嗅探(监听),无论其他核心Cache是否有相同数据都需要发出一个事件,此会增加总线负载且无法保证事物串行化,于是出现MESI协议。

MESI 协议即:

  • Modified,已修改
  • Exclusive,独占
  • Shared,共享
  • Invalidated,已失效

表示Cache Line的四种不同状态。

我们举个具体的例子来看看这四个状态的转换:

  1. 当 A 号 CPU 核心从内存读取变量 i 的值,数据被缓存在 A 号 CPU 核心自己的 Cache 里面,此时其他 CPU 核心的 Cache 没有缓存该数据,于是标记 Cache Line 状态为「独占」,此时其 Cache 中的数据与内存是一致的;
  2. 然后 B 号 CPU 核心也从内存读取了变量 i 的值,此时会发送消息给其他 CPU 核心,由于 A 号 CPU 核心已经缓存了该数据,所以会把数据返回给 B 号 CPU 核心。在这个时候, A 和 B 核心缓存了相同的数据,Cache Line 的状态就会变成「共享」,并且其 Cache 中的数据与内存也是一致的;
  3. 当 A 号 CPU 核心要修改 Cache 中 i 变量的值,发现数据对应的 Cache Line 的状态是共享状态,则要向所有的其他 CPU 核心广播一个请求,要求先把其他核心的 Cache 中对应的 Cache Line 标记为「无效」状态,然后 A 号 CPU 核心才更新 Cache 里面的数据,同时标记 Cache Line 为「已修改」状态,此时 Cache 中的数据就与内存不一致了。
  4. 如果 A 号 CPU 核心「继续」修改 Cache 中 i 变量的值,由于此时的 Cache Line 是「已修改」状态,因此不需要给其他 CPU 核心发送消息,直接更新数据即可。
  5. 如果 A 号 CPU 核心的 Cache 里的 i 变量对应的 Cache Line 要被「替换」,发现 Cache Line 状态是「已修改」状态,就会在替换前先把数据同步到内存。

疑问:3里——则要向所有的其他 CPU 核心广播一个请求,要求先把其他核心的 Cache 中对应的 Cache Line 标记为「无效」状态,然后 A 号 CPU 核心才更新 Cache 里面的数据——标记无效的是所有核心还是所有同为S的核心?

VivioJS MESI 帮助 (tcd.ie)

CPU是如何执行任务的

Cache伪共享及解决方法

当多个线程同时读写同一个Cache Line的不同变量时,导致CPU Cache失去应有的作用,这种现象称为伪共享。

出现问题的根本在于多个线程需要的数据在同一个Cache Line中,只需要避免这些数据在同一个Cache Line中即可。

系统层面:

Linux内核使用__cacheline_aligned_in_smp宏定义解决伪共享问题,多核系统中该宏定义是__cacheline_aligned,使得变量在Cache Line中是对齐的,即使用空间换取时间。

image-20230718210532410

应用层面:

Java的并发框架Disruptor中,有一个RingBuffer类会经常被多个线程使用

img

RingBufferPad中的7个long类型数据作为Cache Line的前置填充,RingBuffer中的7个long类型数据作为Cache Line的后置填充,即RingBufferFelds前后各被占了56位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SFmiT8de-1690708501990)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/%E5%A1%AB%E5%85%85%E5%AD%97%E8%8A%82.png)]

所以无论怎么加载Cache Line,整个Cache Line里都没有将会发生更新操作的数据,只要数据被频繁访问,不被淘汰,自然没有数据被换出的可能,也不会产生伪共享的问题(只是占用空间有点多)

CPU如何选择线程

Linux内核里的调度器,调度对象为task_struct,主要分为两类:

  • 实时任务:优先级在0~99,要尽快执行的任务
  • 普通任务:优先级在100~139。

Linux给任务分配优先级使用调度类,分为三种:

  • Deadline
  • Realtime
  • Fair

前两种的调度策略有:

  • SCHED_DEADLINE:是按照 deadline 进行调度的,距离当前时间点最近的 deadline 的任务会被优先调度;
  • SCHED_FIFO:对于相同优先级的任务,按先来先服务的原则,但是优先级更高的任务,可以抢占低优先级的任务,也就是优先级高的可以「插队」;
  • SCHED_RR:对于相同优先级的任务,轮流着运行,每个任务都有一定的时间片,当用完时间片的任务会被放到队列尾部,以保证相同优先级任务的公平性,但是高优先级的任务依然可以抢占低优先级的任务;

最后一种调度策略有:

  • SCHED_NORMAL:普通任务使用的调度策略;
  • SCHED_BATCH:后台任务的调度策略,不和终端进行交互,因此在不影响其他需要交互的任务,可以适当降低它的优先级。

什么是软中断

中断是计算机的一种异步的事件处理机制,可以提高系统的并发能力。

操作系统接收到中断请求会打断其他程序运行,中断处理程序需要尽可能快的执行完,以减少对正常进程运行调度的影响。当有多个中断请求时,会有中断请求丢失的情况,此为硬中断。

中断请求的处理程序应该要短且快,这样才能减少对正常进程运行调度地影响,而且中断处理程序可能会暂时关闭中断,这时如果中断处理程序执行时间过长,可能在还未执行完中断处理程序前,会丢失当前其他设备的中断请求。

Linux将中断的执行过程分为两个阶段:上半部分和下半部分。

上半部分硬中断,暂时关闭中断请求通道,负责处理跟硬件紧密相关或者时间敏感的事情。

下半部分软中断,负责延迟处理上半部分未完成的工作,一般以内核线程方式运行。

0.1+0.2!=0.3?

整型,像是int类型是32位,最高位作为符号标志位,正数为0,负号为1

负数在计算机内以补码表示,即:将正数的二进制全部取反加一

整型符号位二进制
5000000000000000000000000000000101
取反
11111111111111111111111111111010
加一
-511111111111111111111111111111011

这样-5+5=0

如果负数不是使用补码的方式表示,则在做基本对加减法运算的时候,还需要多出一步操作来判断是否为负数,对人来说很简单,对机器来说就会多出很多步骤。

绝大多数的计算机的浮点型,都采用的是IEEE制定的国际标准

浮点数分为三个部分:

  • 符号位:表示数字是正数还是负数;
  • 指数位:指定了小数点在数据中的位置;
  • 尾数位:小数点右边的数字,即小数部分。

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MxU1Ref6-1690708501990)(https://pillow-blog-pictures.oss-cn-shanghai.aliyuncs.com/float%E5%AD%98%E5%82%A8.png)]

0.1 和 0.2 这两个数字用二进制表达会是一个一直循环的二进制数,比如 0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),对于计算机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。

因此,IEEE 754 标准定义的浮点数只能根据精度舍入,然后用「近似值」来表示该二进制,那么意味着计算机存放的小数可能不是一个真实值。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值