考研408——计算机组成原理

文章目录

计算机组成原理

十进制倍数单位

符号kMGTPEZY
中文简称
科学计数法10³10⁶10⁹10¹²10¹⁵10¹⁸10²¹10²⁴

机器字长、指令字长、存储字长

1. 机器字长(Machine Word Length)

  • 定义:指计算机CPU一次能直接处理的二进制数据的位数,通常与CPU的寄存器位数、运算器位数一致。

2. 指令字长(Instruction Word Length)

  • 定义:一条指令所包含的二进制位数,即指令的编码长度。

3. 存储字长(Memory Word Length)

  • 定义:存储器(如内存)一次读写操作所能的二进制位数,即存储单元的宽度。

C语言中的类型转换

整形类型之间的相互转换

1. 有符号和无符号之间的转换

int main() {
    short x = -4320;
    unsigned short y = (unsigned short)x;
    
    unsigned short u = 4320;
    short v = (unsigned short)u;
}

像上面这样同种整型变量,有符号和无符号之间的转化,不会对二进制数据本身做任何改动,仅仅是改变解释方式

2. 不同整型变量之间的转换

int main() {
    short x = -4320;
    unsigned int y = (unsigned int)x;
    
    unsigned short x = 4320;
    int y = (int)x;
    
    int x = -4320;
    unsigned short y = (unsigned short)x;
    
    unsigned int x = 4320;
    short y = (short)x;
}

在C 语言中,若我们将大字长变量强转成一个小字长变量,那么系统就要把多余的高位部分直接截断,低位直接赋值;

如果我们将小字长变量强转成一个大字长变量:

  • 若原数字是无符号数,进行零扩展(即高位直接补零)
  • 若原数字是带符号数,进行符号扩展(即高位直接补符号位)

拓展完成之后,根据目的类型是否有符号,按照对应的方式进行解释

整数与浮点数之间的类型转换

浮点型转整型,只能保留原数据的整数部分,所以可能会丢失精度

整形转浮点型,由于float的数据位只有23位,而int有32位,因此int转float可能会丢失精度。由于double的数据位有52位,所以int 转float不可能丢失精度
在这里插入图片描述

PSW寄存器

PSW寄存器的全称叫做程序状态字寄存器,这个寄存器对程序员是可见的

PSW寄存器中的标志位介绍

(1) 零标志 ZF
ZF = 1 表示结果为 0,ZF = 0 表示结果非 0

(2) 符号标志 SF
表示有符号整数加减运算结果的符号位,因此直接取结果的最高位作为 SF。

(3) 溢出标志 OF
溢出标志(OF,Overflow Flag)用于判断带符号数运算是否发生溢出,其核心计算公式基于运算时最高位进位(Cₙ)次高位进位(Cₙ₋₁) 的逻辑关系,具体如下:

OF = C n ⊕ C n − 1 \text{OF} = \text{C}_n \oplus \text{C}_{n-1} OF=CnCn1

  • 其中 ⊕ \oplus 表示“异或”运算(两值不同时结果为1,相同时为0)。
  • C n \text{C}_n Cn最高位(符号位)的进位输出(对于n位运算,如8位运算的第7位进位)
  • C n − 1 \text{C}_{n-1} Cn1次高位(数值部分最高位)的进位输出(如8位运算的第6位进位)

提示:注意 OF 是判断有符号数是否溢出的,对于无符号数 OF 不能用作溢出的判断,因为如果两个高位为 0 的数相加,得到的结果高位为 1,对于无符号数这种情况是可以成立的。

(4) 进/借位标志 CF
OF = C i n ⊕ C o u t \text{OF} = \text{C}_{in} \oplus \text{C}_{out} OF=CinCout

  • C i n \text{C}_{in} Cin表示执行的运算类型,Cin = 0 表示加法运算,Cin = 1表示减法运算
  • C o u t \text{C}_{out} Cout表示最高位进位(符号位进位)

当执行加法操作时 C i n = 0 \text{C}_{in}=0 Cin=0 OF = C o u t \text{OF} =\text{C}_{out} OF=Cout
当执行减法操作时, C i n = 1 \text{C}_{in}=1 Cin=1 OF = C o u t ‾ \text{OF} =\overline{\text{C}_{out}} OF=Cout

无符号数加减运算溢出可以通过 CF 标志位体现。但是无符号数不常分析溢出,也没有像 OF 这样专门的溢出标志位。这其中主要原因是:对于 n 位无符号数,其算术运算实际是一种以 2ⁿ 为模的回绕算术。例如无符号数表示的地址,当计算得到的高地址溢出后会回绕到低地址,结果仍在 2ⁿ 域内。

提示:SF、OF 标志位只对有符号数运算有意义;CF 标志位只对无符号数运算有意义。

经典例题

在这里插入图片描述
在这里插入图片描述

浮点数类型

在这里插入图片描述

注意:float类型阶码的偏置值是127!double阶码偏置值是1023

浮点数表示范围

  • 全0阶码全0尾数:+0/-0。零的符号取决于符号s,一般情况下+0和-0是等效的。
  • 全1阶码全0尾数:+∞/-∞。+∞在数值上大于所有有限数,-∞则小于所有有限数。引入无穷大数的目的是,在计算过程出现异常的情况下使得程序能继续进行下去。
  • 全1阶码非0尾数: NaN(Not a Number)。表示一个没有定义的数,称为非数。
  • 全0阶码非0尾数:非规格化数。非规格化数的特点是阶码为全0,尾数高位有一个或几个连续的0,但不全为0。因此,非规格化数的隐藏位为0,且单精度和双精度浮点数的指数分别为-126或-1022。非规格化数可以用于处理阶码下溢

在这里插入图片描述

下面这道例题考的就是非规格化的浮点数表示

在这里插入图片描述
在这里插入图片描述

Cache行结构

Cache行结构= 数据位 + LRU位 + 脏位 + 有效位 + Tag标记位
在这里插入图片描述

  • 标记(Tag)位:主要用来区分同一组中的不同数据块
  • 有效位:大小为每块 1bit。用来表明该 Cache 存储块中的数据是否有效
  • 脏位:大小为每块 1bit,用来表明该 Cache 存储块中的数据被调入内存之后是否被修改过
  • LRU 标记位:大小为每块 l o g 2 log_2 log2(同一组内的 Cache 块数)bit。仅当 Cache 采取 LRU 替换策略(后面介绍)时才需要附设(假设同一组内有4个cache块,那LRU的标记位就是 l o g 2 4 = 2 log_24=2 log24=2位 )

Cache和主存的映射方式

1. 直接映射

直接映射的基本思想是把主存的每一块映射到固定且唯一的 Cache 块中,其映射关系如下:

Cache 块号 = 主存块号 %  Cache 总块数 \text{Cache 块号 = 主存块号} \% \text{ Cache 总块数} Cache 块号 = 主存块号% Cache 总块数

直接映射地址结构如图3.33所示。当 Cache 有 2 M 2^M 2M 块,每块大小为 2 N 2^N 2N 字节时,则访存地址的低 N N N 位为块内地址,中间 M M M 位为 Cache 块号,剩下的高位为标记位,用于判断该 Cache 块对应哪一个主存块。

在这里插入图片描述
在直接映射中,如果先后调入cache的两个主存块,映射得到的cache块号是同一个怎么办?

非常简单,后面的主存块直接在对应的位置将前面的主存块给覆盖掉。所以这就会导致cache的空间利用率不高,进而使得直接映射的命中率非常低

2. 全相连映射

之所以叫全相联映射,主要是因为它引入了一种特殊的存储器——相连存储器。这个存储器特殊在哪呢?请看全相连映射的具体流程
在这里插入图片描述

在全相联映射中,你只需要告诉cache一个标记位,它立即就能给你找到对应的cache块,就不用像直接映射那样再去计算块号了。

那有人就说了,这全相联映射咋就这么牛逼呢,它为啥就不用计算,直接就能映射出结果呢?这么强的功能,当然是要付出代价的,相联存储器用复杂的内部构造换来的优越的性能,这很公平

3. 组相连映射

其实就是把前面俩结合了一下,组间采用直接映射,组内采用全相联映射
在这里插入图片描述
这时候有人就说了,这不对吧,这看起来咋和直接映射这么像啊。确实很像,他们最大的区别就在于中间的部分,一个是cache组号,一个是cache块号。所谓的cache块号,那确实是精确到某个cache块的,但是cache组号只能精确到一个组。每个组里面会有若干个cache块,具体要哪个块,进入组之后再通过标记位映射得到

比如下图就是一个二路组相连映射的示意图(所谓的2路组相连,意思就是说一个组里面有俩cache块)

在这里插入图片描述

cache中比较器的个数

值得注意的是,一个组里面有几个主存块,就需要几个比较器,比如上面是二路组相连,就需要两个比较器。

特别的,如果Cache采用的是全相联映射,那么所有的Cache块都在一个组里。所以有多少个Cache块,就要多少个比较器

虚拟地址与物理地址之间的转换

快表(TLB)

1.快表的结构
在这里插入图片描述

1.为什么要引入快表?
在程序运行过程中,我们往往需要将程序中的逻辑地址(进程地址空间中的虚拟地址)转化成存储器认得的物理地址。这一过程需要我们拿着逻辑地址去查页表获得。而进程的页表是在内存中的,查页表本身就是一次访存操作,也需要占用一个机器周期(存取周期)

为了加快地址转换的速度,我们就将经常被访问的那些页表项放到快表中,快表和Cache一样,都是独立在主存之外的相连存储器,虽然容量小,但是访问速度比主存要快得多。引入快表之后,每当MMU拿到一个新的逻辑地址,第一步就是要先去快表中查找,如果快表里面有(快表命中了),那么就不用访存了,我们就实现了快速完成地址转换的目的。

2.快表的工作流程

前面说了,快表里面存的主要就是逻辑地址和物理之间的映射关系。在页表中,逻辑页号可以通过(当前页表项地址-页表起始地址)÷ 页表项大小获得,但是在快表中就没法这么做,因为快表太小了,表示不了那么多页号。那怎么办呢?Cache中同样存在这样的问题,其解决方式就是引入三种不同的主存映射方式:直接映射、全相联映射和组相连映射。

快表的解决方式和Cache一模一样,也是通过引入这三种主存映射方式来解决的。在实际应用过程中,一般都用的是组相连映射

现在我们就以组相连映射为例,来简单说明一下查快表的过程
1.首先CPU将逻辑地址交给MMU,MMU拿着逻辑地址去查快表

假如说虚拟地址位数是24位,一页大小是4KB。TLB一共有8个表项,采用2路组相连映射,因此TLB会把虚拟地址划分成下面三块
在这里插入图片描述
首先MMU会读取虚拟地址中间两位组号,确定当前虚拟地址位于哪一个组中。然后去对应组里面挨个比对TAG标记位

  • 如果里面有比对成功的,那就说明TLB命中了,直接将表项中的物理页框号 与 逻辑地址的页内地址部分 拼接形成物理地址,输出到MAR中
  • 如果里面比对都失败了,说明TLB没有命中,那么我们要转头去页表中查(页表起始地址+虚页号×页表项大小)这个位置处的页表项,去里面找对应的物理页框号
    • 如果查页表的时候发现,目标页表项的有效位为0,说明当前数据并不在内存中。这时候就要触发缺页中断,让操作系统先将对应的页框从外存中调入内存

下面这个图画的极好,基本上地址相关的所有问题答案都在里面

在这里插入图片描述

经典例题
在这里插入图片描述

页表的结构

页表里面存的是啥?

先说结论:页表项中存的有

  1. 页面的有效位(用来表示该页面在不在内存中)
    • 如果页面在内存中,那么页表项中存的就是逻辑块号对应的物理块号(逻辑块号=(当前页表项地址-页表起始地址) / 页表项大小)
    • 如果页面不在内存中,那么页表项中存的就是该逻辑块数据在外存中的地址(磁盘块号)
  2. 页面置换算法位
    • 如果页面置换算法采用的是LRU,那么就会有 l o g N log N logN bit 的LRU算法位
    • 如果页面置换算法用的是CLOCK算法,那么就会有一个额外的访问位(时钟指向该页面时,如果访问位是1,那么会将访问位由1改成0,如果访问位是0,则会将其换出去)
    • 如果页面置换算法用的是改进版的CLOCK算法,那么就会有一个额外的访问位和一个额外的修改位(脏位)

大家潜意识里肯定会觉得:页表是逻辑地址空间和物理地址空间的桥梁,所以页表中存的肯定就是逻辑地址与物理地址之间的映射关系。

这个话当然没错,但是现在我要问你一个问题:如果虚拟地址对应的资源不在内存中,而是在磁盘中,那这个数据就没有所谓的物理地址,那它对应的页表项中填的是啥呢?填的就是这个资源的外存地址!

所以到这里我们应该明白,页表项中存储的不只是虚拟地址对应的物理地址,还要存资源对应的外存地址!

那什么时候看物理地址,什么时候看外存地址呢?显然,这个资源在内存中的时候看物理地址,资源不在内存中的时候看外存地址。那我们咋知道这个资源在不在内存中呢?为此,工程师向页表项加了一个有效位,规定资源在内存中时有效位为1,不在则有效位为0。

假如说一个进程对应的资源在磁盘中有三个磁盘块,但是操作系统只给该进程分配了两个页框,即最多允许该进程把俩磁盘块调入内存,这就会导致进程无法一次性把所有资源都调入内存。

当进程需要用到前两个磁盘块中的内容时,此时操作系统分配给进程的空间还没满,因此直接调入就行。但当进程需要用到第三个磁盘块中的资源时,操作系统分配给进程的空间已经满了,这时候为了把第三个磁盘块调入内存,操作系统就要采用页面置换算法,从该进程已经调入内存的两个物理块中挑一个换出去,把第三块磁盘块换进来。

那到底应该把哪个页面换出呢?这个就取决于我们的页面置换算法了,如果采用LRU算法,那就要留 log N 位作为LRU的标记位,如果采用CLOCK算法,那就要留出访问位和修改位

下面就是采用CLOCK算法进行页面置换时,页表的结构

在这里插入图片描述

在这里插入图片描述

CPU里面的地址寄存器中,存的是虚拟地址还是物理地址?

PC里面存的是虚拟地址。CPU在间址周期内,对指令的地址码进行间址操作,最终获得的有效地址也是虚拟地址

获得虚拟地址之后,CPU会将获得的虚拟地址送入MMU(内存管理单元)中,MMU会自动去查快表、查页表,将虚拟地址转化成物理地址(这一过程由硬件自动完成)。转化完成之后,CPU会将生成的物理地址送入MAR中,方便后续的访存操作

因此CPU里面的MAR和页表基址寄存器,存的都是物理地址

页面级别的权限管理

1.在MMU内进行地址转换期间,会不会进行访问权限的检查?
当然会!页表中有专门的权限控制位。当MMU拿着虚拟地址去查页表,找到对应的页表项之后,首先就会去查看该页表项的权限位,检验此次访存操作的权限是否符合要求。如果没有越权,才会读取表中的物理页框号,完成地址转换

2.MMU在查页表的时候,它怎么知道当前CPU正在执行的指令要对这个页面进行读操作还是写操作呢?

CPU在将虚拟地址交给MMU的同时,也会告诉MMU此次访存的操作类型,MMU 再结合页表权限位做校验—— 操作类型不是 MMU “猜” 的,而是 CPU 直接通过硬件信号传递给MMU的

3.页表的权限位设置

页表项(如x86的页表项PTE)中通常包含以下关键权限字段:

字段含义控制场景
存在位(Present)标记该页是否在物理内存中若为0,触发页缺失(需从磁盘换入内存)
读/写位(Read/Write)0=只读,1=可读写控制进程是否能修改该页面的内容
用户/特权位(User/Supervisor)0=仅内核(特权级)可访问,1=用户进程(非特权级)可访问隔离内核空间与用户空间
可执行位(Execute Disable)标记页面是否可执行防止数据段被当作代码执行(如防御缓冲区溢出攻击)

4.页面权限校验的执行流程
当CPU访问一个内存地址时,MMU(内存管理单元)会按照以下步骤校验权限:

  1. 地址转换阶段:MMU从页表中解析出物理地址后,提取该页表项的权限位。
  2. 权限匹配校验
    • 读/写校验:若CPU发起写操作,但页表项的“读/写位”为0(只读),则触发写保护异常(如x86的#GP异常)。
    • 特权级校验:若进程运行在用户态(非特权级),但页表项的“用户/特权位”为0(仅内核可访问),则触发越权异常(如x86的#GP异常)。
    • 可执行校验:若CPU执行指令的地址所在页的“可执行位”为禁用,则触发执行权限异常(如x86的#PF异常,附带执行权限标志)。
  3. 异常处理:若权限不匹配,CPU会中断当前执行流,进入操作系统的异常处理程序(如Linux的do_page_fault函数),根据异常类型进行处理(如终止进程、换入页面等)。

5.页面级权限管理和文件级权限管理的区别

文件权限 vs 页面权限:不同层面的保护

维度文件权限(如Linux的rwx)页面权限(内存页表的权限位)
管理对象磁盘上的文件实体内存中的物理页面
作用时机访问磁盘文件时(如打开、读写文件)访问内存地址时(CPU执行指令、读写数据)
核心目标保护文件不被非法用户/进程篡改保护内存不被非法进程篡改、越权访问
实现方式存在文件元数据(如inode)中存在页表项中,由MMU硬件校验

简单来说:

  • 文件权限 是“外防”:防止用户/进程非法访问磁盘上的文件;
  • 页面权限 是“内防”:防止进程在内存中非法访问其他进程的空间,或自身进程越权访问

6.页面权限控制实例
操作系统需要通过页面权限实现以下关键保护:

  1. 进程隔离:每个进程的内存空间是独立的,我们可以通过页表权限控制来确保 进程A无法访问进程B的内存(比如当进程A访问只属于进程B的内存块时,就会触发越权异常)

  2. 内存区域保护

    • 内核区:当进程处于用户态时,不允许对内核区内的任何页面进行任何操作
    • 代码段(.text):只允许进程对代码段进行读操作和执行操作,不允许进程对代码段进行读操作,防止被恶意篡改;
    • 数据段(.data、.bss):只允许进程对数据段进行读写操作,不允许对数据段进行执行操作(防止注入的恶意代码执行)
    • 栈空间:同样只允许进程进行读写操作,还会有边界保护(防止栈溢出越界)
  3. 共享内存控制:对于进程间共享的内存区域,页表权限可以精细控制“哪些进程可读、哪些进程可写”。

    • 比如你运行了一个文本编辑器(进程A)和一个恶意程序(进程B):

      • 通过文件权限控制 可以确保进程B不能非法修改你编辑器的配置文件;
      • 通过页面权限控制 可以确保进程B不能直接篡改进程A的内存(比如修改编辑器的代码逻辑,让它偷偷发送你的数据)。
    • 如果没有页面权限,进程间的内存就是“裸奔”的,系统安全性会完全崩溃

页面级的权限管理是内存安全的核心防线之一,和文件权限属于不同层面的保护机制,缺一不可

指令集体系结构

指令集体系结构(ISA)是软件与硬件的接口规范,规定了CPU能识别和执行的基本操作及交互规则

核心规定内容

  1. 指令相关定义:包括指令的类型(数据传输、算术逻辑、控制转移等)、格式( opcode、操作数地址码的位数和布局)、功能描述(每条指令的具体执行效果)。
  2. 寄存器组设计:指定通用寄存器、专用寄存器(如程序计数器PC、状态寄存器SR)的数量、位数、命名及用途,寄存器是CPU内部数据临时存储的核心。
  3. 寻址方式:定义CPU获取操作数的方法,如立即寻址、直接寻址、间接寻址、变址寻址等,决定了指令如何定位数据来源。
  4. 数据类型与存储格式:规范支持的基本数据类型(整数、浮点数、字符等),以及数据在内存中的存储方式(如字节序、对齐规则)。
  5. 异常与中断处理:规定异常(如指令错误、算术溢出)和中断(如外部设备请求)的触发条件、响应机制及处理流程。
  6. 内存访问与I/O接口:明确内存地址空间的划分,以及CPU与外部设备(I/O)的交互方式(如专用I/O指令、内存映射I/O)。

指令格式

操作码+地址码+寻址方式

CPU的不同类型

  1. 单周期 CPU:全部操作在一个“大时钟周期”内完成,每条指令只占用 1 个时钟周期 → CPI = 1。
  2. 多周期 CPU:把取指、译码、执行等分解到多个时钟周期,不同指令用的周期数不同,平均 CPI > 1。
  3. 基本流水线 CPU:理想情况下流水段数为 k,装满后每拍流出 1 条指令 → CPI = 1。
  4. 超标量流水线 CPU:一次可发射 n(n > 1) 条指令,理想 CPI = 1/n < 1

指令周期

在这里插入图片描述

指令周期、工作周期、机器周期、时钟周期之间的关系

一个指令周期,包括:取指、间址、执行、中断四个工作周期
一个工作周期内有若干的机器周期
一个机器周期内有若干个时钟周期

CPU 工作周期及其访存目的

CPU 工作周期访存目的
取指周期取指令
间址周期获得操作数的有效地址
执行周期取操作数、存操作数
中断周期保存断点、执行中断服务程序、恢复断点

CPU中其余各周期的定义

周期名称特点
指令周期从将这个指令从内存中取出来开始,到这个指令执行完成,所需要的时间
取指周期从内存中读出一个指令字的最短时间
存取时间发起一次内存读/写到完成读/写的时间
存取周期连续完成两次内存读/写操作,之间的“最短”时间间隔,通常存取周期大于存取时间,因为内存有冷却时间
机器周期执行指令周期当中一步相对完整的操作所用的时间,因为 CPU 速度比主存快,因此机器周期一般设置为存取周期
时钟周期时钟周期是主频的倒数,是 CPU 中最小的时间单位,时钟周期以相邻状态单元间组合逻辑电路的最大延迟为基准确定

指令流水线

指令流水线的基本结构

在这里插入图片描述

  1. 取指令(IF):负责将指令从指令存储器中取出,通常在IF段结束时,PC的值就会自动 + “1”。
  2. 指令译码、取操作数(ID):控制器对指令字中的操作码进行译码,同时从通用寄存器中取出操作数。
  3. 执行或访存地址运算(EX):执行计算或计算地址。
  4. 存储器访问(MEM):对主存进行读或写操作。
  5. 结果写回(WB):将指令执行的结果写回通用寄存器中

普通流水线、超标量流水线、超流水线、超长指令字对比

在这里插入图片描述

流水线的冒险与处理

指令相关与流水线冲突的概念

指令相关:是指两条指令之间存在某些依赖关系,某指令的某个阶段必须等到它前面的某条指令的某个阶段完成后才能开始。

  • 指令相关包括结构相关、数据相关和控制相关,指令相关会导致流水线冲突(冒险)。

流水线冲突:由于指令相关的存在,流水线出现“阻塞”或“暂停”,进而导致下一条指令不能在预期的时钟周期内加载到流水线中。

  • 流水线冲突包括结构冲突(结构冒险)、数据冲突(数据冒险)、控制冲突(控制冒险)3种

结构冒险

看下面的场景:
在这里插入图片描述
假设计算机的指令和数据通常都存放在同一存储器中,在上图中的第4个时钟周期,LOAD指令进入MEM段,需要访问内存,将某条指令从内存加载到CPU的暂存寄存器中。而此时第i + 3条指令正好进入IF段,也需要访问内存,将某条指令从内存加载到CPU的指令寄存器中。

这俩都要访存,但是内存并不能支持这两条指令同时访存,此时就发生了访存冲突。我们把像这样的,由于不同指令在同一时刻争用同一功能部件而形成的冲突,叫做结构冲突(也叫资源冲突,即由硬件资源竞争造成的冲突)

结构冲突的解决方案也很简单,既然只能让一个指令访存,那我就让另外一个指令的流水线整体向后延迟一个时钟周期,比如下图所示
在这里插入图片描述

数据冒险

看下面的例子:

考虑下列两条指令
I1  add R1,R2,R3    # (R2)+(R3)→R1
I2  sub R4,R1,R5    # (R1)-(R5)→R4

在写后读(RAW)冲突中,指令I2的源操作数是指令I1的目的操作数。正常的读/写顺序是由指令I1先写入R1,再由指令I2来读R1。在非流水线中,这种先写后读的顺序是自然维持的。但在流水线中,由于重叠操作,读/写的先后顺序关系发生了变化:
在这里插入图片描述
R1中的数据在第5个CLK才更新到位,但是sub指令在第3个CLK就对R1进行了读取操作,这就导致sub指令读到的是老的R1,是不符合要求的R1。

像上面这样,当后面指令用到前面指令的结果时,由于前面指令的结果还没有产生,从而使得后面指令读到错误结果的情况,就叫做数据冲突(数据冒险)

在以非乱序执行的流水线(按序执行的流水线)中,所有数据冒险都是由于前面指令写结果之前,后面指令就需要读取而造成的,这种数据冒险称为写后读(Read After Write,RAW)冲突

如何解决数据冲突?

(1)延迟执行后续指令
在这里插入图片描述
(2)使用转发旁路技术
正常的执行顺序,需要让前一条指令把结果算出来之后,将结果写入寄存器中,然后下一条指令再从同样的寄存器中把结果算出来。这个流程在流水线模式中明显是太长了,不能与流水线相匹配。

为了解决这个问题,工程师们在ALU之间引入一条直接的转发通路,使得数据通路中生成的中间数据可以直接转发到 ALU 的输入端。这样前面指令计算完毕之后,就可以直接将计算出来的结果通过转发通路,发到下条指令的ALU输入端,这就能赶得上了。

在这里插入图片描述
(3)在相邻两条指令之间插入一条nop指令
在这里插入图片描述

为什么Load-use类型的数据冒险不能通过旁路转发技术解决?

什么是load-use指令?

  • Load-Use 冒险是 流水线处理器 中的一种典型 数据冒险(Data Hazard),核心是:一条 load 指令(从内存/寄存器读取数据)的结果,被紧随其后的指令(use 指令)直接依赖,但流水线的并行执行导致 use 指令在 load 指令完成数据读取前就需要使用该数据,最终引发数据错误或流水线阻塞。

  • load 指令(需经历 MEM 阶段才能拿到数据)的结果,被下一条指令(use 指令)在 EX 阶段使用时,就会触发冒险。比如下面就是一个典型的load-use指令

lw $t0, 0($a0)   # load:从内存地址($a0+0)读取数据,写入$t0(需MEM阶段完成数据读取)
add $t1, $t0, $t2 # use:用$t0和$t2做加法(EX阶段需要$t0的值)

为什么Load-use冒险不能通过旁路转发技术解决?
我们使用旁路转发技术,主要是解决下面情景中的数据冲突

在这里插入图片描述
但是在load-use冒险中,load指令在EX阶段结束时并不没有将数据读到寄存器中,得等到MEM阶段结束之后才行。即在EX阶段结束时,寄存器的值还是老数据,没法进行转发。这时候我们只能将use指令整体延迟几个时钟周期再执行了

在这里插入图片描述

控制冒险

看下面的例子

int a=1;
if(a==0) 
	a++;
cout << a << endl;

由于指令往往是顺序执行的,所以在if(a==0) 这条语句执行的过程中,我们往往会将其后续相邻的指令 a++;提前读入PC中,但是在if(a==0) 这条指令的EX阶段,我们突然发现a!=0,这样我们就没法执行a++指令,那前面a++的取指和译码相当于就都白做了

上面这样的情况就叫做控制冲突

如何解决控制冲突?

  • 延迟损失多少时间片,就插入多少条nop指令
  • 对转移指令进行分支预测,尽早生成转移目标地址
    • 分支预测分为简单(静态)预测和动态预测。若静态预测的条件总是不满足,则按序继续执行分支指令的后续指令。动态预测根据程序转移的历史情况,进行动态预测调整,有较高的预测准确率。

经典例题

在这里插入图片描述

在这里插入图片描述

CPU的结构

CPU由运算器和控制器组成,而运算器和控制器的具体组成结构见下表

运算器控制器
算术逻辑单元 ALU控制单元 CU
通用寄存器 GPR程序计数器 PC
程序状态字寄存器 PSW指令寄存器 IR
累加寄存器 ACC指令译码器 ID
移位器时序系统
计数器微操作信号发生器
暂存寄存器存储器数据寄存器 MAR
存储器地址寄存器 MDR

看完是不是很懵逼,感觉很不好记对吗?确实很好记,只要记住三句话:

  1. 所有和计算相关的部件都属于运算器
  2. PC、IR、MAR、MDR属于控制器,除此之外的所有寄存器都属于运算器
  3. 其余CPU内的部件都属于控制器

哪些寄存器对程序员是可见的?

  • 在运算器中,除了暂存寄存器之外的所有寄存器 都是可见的
    • 通用寄存器组(GPR)、基址/变址寄存器(BR/IX)、标志(状态)寄存器(PSW寄存器)等
  • 在控制器中,除了PC之外的所有寄存器都是不可见的
    • 比如指令寄存器IR、地址寄存器MAR、数据寄存器MDR,以及控制器里的微指令寄存器等

数据通路

数据通路是指令执行期间数据流经的所有硬件资源集合

SISD、SIMD、MIMD介绍

SISD:单指令流单数据流
SIMD:单指令流多数据流
MIMD:多指令流多数据流

好了,看了上面的简单介绍,我们就很容易看出,要理解SISD、SIMD、MIMD,最关键的点就在于如何理解指令流和数据流

所谓的单指令流,就是给计算机一个指令列表,计算机从第一条指令开始按照顺序依次执行列表中的指令。而所谓的多指令流,就是计算机在同一个时刻,可以同时执行多条指令。

至于数据流,我们重点就是要理解什么是多数据流:所谓的多数据流,就是计算机能在同一时刻同时处理多个数据。 有的人这时候就会说,你这话说了跟没说似的,计算机CPU里面有那么多运算器,肯定在同一时刻不止处理一条数据啊。是的,这样空洞的概念介绍效果很差,下面我们就来举例子介绍

就拿SIMD来说,它是单指令流多数据流。有的同学就会说,单指令流的计算机,同一时刻只能处理一条指令,一条指令的执行,还需要多个数据流来支持吗?没错,看下面这段代码

for(int i=0;i<10;i++)
	arr[i]+=10;

如果是计算机采用SISD——单指令流单数据流,那就是我们脑子里想的那样,依次对arr数据中的每个元素都加10。但如果计算机采用SIMD,那CPU就会把arr数组中的10个元素分别读到10个不同的加法器中,然后让10个加法器同时对其中的数据进行加10操作。这样就可以在一个机器周期内完成对arr数组中10个元素的加10操作。

上面说的这种,对不同数据进行同一操作(用一条指令并行处理多个同类型数据)的情况,在计算机中非常常见(尤其是在GPU内)。为此工程师设计出了专门用于处理这类计算的处理器——向量处理器

用户需要将连续的标量数据(如数组元素)打包成“一个向量”,然后将向量送到向量处理器中。向量处理器会根据向量指令一次性对整个向量执行相同操作(如加减乘除、逻辑运算)

向量处理器的典型应用场景

  • 科学与工程计算:如气象预测、流体力学模拟、量子计算等,涉及大量矩阵运算和数值积分。
  • 多媒体处理:如视频编解码、图像滤波、音频处理,需对像素、采样点执行批量相同操作。
  • 人工智能训练:早期深度学习模型的矩阵乘法、卷积运算,现在部分GPU仍沿用向量处理核心。

SIMD最擅长处理for循环语句,最不擅长处理case/switch语句,因为这些语句的执行具有很强的顺序性,它指定xx数据的运算必须要在xx数据运算的前面,相当于人为规定了一个单数据流,这时候多数据流就排不上用场了

再比如说MIMD,其实就是能够支持多条指令在计算机中并行执行(这其实是现代计算机刚需的,所以现在计算机基本用的都是MIMD结构)

硬件多线程技术

为什么要引入硬件多线程技术?

引入硬件多线程技术之前,在传统 CPU 中,线程切换的开销虽然比进程小得多,但计算机内线程切换实在是太频繁了,实际算下来,其实仍然是一笔不小的开心。为了减少线程切换过程中的开销,工程师们引入了硬件多线程技术

硬件多线程技术是如何减少线程切换过程中的开销的?

硬件多线程采用的是以空间换时间的策略,为每个线程都提供了单独的线程上下文(为每个线程都提供了单独的通用寄存器组和PC)。

当线程切换时,如果是软件多线程切换,则需要由操作系统先保存当前线程的上下文,再执行调度算法,选择一个就绪的线程上处理机,最后再将选中的线程上下文写入CPU的通用寄存器和PC中。

而如果是硬件多线程切换,当一个线程暂停时,我们只需将当前线程的寄存器组状态从正在使用改成未使用(这就相当于将当前线程的状态从执行态改成了就绪态),将另一组寄存器的状态改成正在使用,就可瞬间切换到另一个就绪线程,让运算单元持续工作。

这种切换无需操作系统介入,大幅缩短切换间隙,从根本上减少CPU闲置时间,最终就能实现“单个核心处理更多任务”的目标。

硬件多线程的实现方式

硬件多线程有 3 种实现方式:细粒度多线程、粗粒度多线程和同时多线程(SMT)。其中细粒度多线程、粗粒度多线程只能实现指令级并行(即支持同一线程内的不同指令并发执行),同事多线程可以实现线程级并行(支持来自不同进程的不同指令并发执行)

1. 细粒度多线程

多个线程之间轮流交叉执行指令,多个线程之间的指令是不相关的,可以乱序并行执行。在这种方式下,处理器能在每个时钟周期切换线程。例如,在时钟周期 i,将线程 A 中的多条指令发射执行;在时钟周期 i + 1,将线程 B 中的多条指令发射执行。
在这里插入图片描述

2. 粗粒度多线程

连续几个时钟周期都执行同一线程的指令序列,仅在当前线程出现了较大开销的阻塞时,才切换线程,如 Cache 缺失。在这种方式下,当发生流水线阻塞时,必须清除被阻塞的流水线,新线程的指令开始执行前需要重载流水线,因此,线程切换的开销比细粒度多线程更大。
在这里插入图片描述

上述两种多线程技术都实现了指令级并行,但线程级不并行

3. 同时多线程

同时多线程(SMT)是上述两种多线程技术的变体。它在实现指令级并行的同时,实现线程级并行,也就是说,它可以支持在同一个时钟周期中,发射多个不同线程中的多条指令执行
在这里插入图片描述

SMP、UMA和NUMA

SMP:即共享内存多处理器,顾名思义,就是共享单一物理地址空间的多处理器。不同处理器之间通过共享内存互相通信

UMA介绍

单一地址空间的多处理器有两种类型:

  • 统一存储访问(UMA)多处理器。每个处理器对所有存储单元的访问速度大致相同,即访存时间与哪个处理器提出访存请求及访问哪个字无关。
  • 非统一存储访问(NUMA)多处理器。某些存储器的访存速度要比其他的快

早期的计算机,内存控制器没有整合进 CPU,访存操作需要经过北桥芯片(集成了内存控制器,并与内存相连),CPU 通过前端总线和北桥芯片相连,这就是统一存储访问(UMA)构架。

为什么要引入NUMA?

随着 CPU 性能提升由提高主频转到增加 CPU 数量(多核、多 CPU),越来越多的 CPU 对前端总线的争用使得前端总线成为瓶颈。为了消除 UMA 架构的瓶颈,非统一存储访问(NUMA)构架诞生

NUMA的内部结构

NUMA框架中,内存控制器被集成到 CPU 内部,每个 CPU 都有独立的内存控制器。每个 CPU 都独立连接到一部分内存,CPU 直连的这部分内存被称为本地内存。CPU 之间通过 QPI 总线相连。CPU 可以通过 QPI 总线访问其他 CPU 的远程内存。与 UMA 架构不同的是,在 NUMA 架构下,内存的访问出现了本地和远程的区别,访问本地内存明显要快于访问远程内存。

操作共享变量时的同步问题

由于可能会出现多个处理器同时访问同一共享变量的情况,在操作共享变量时需要进行同步,否则,一个处理器可能会在其他处理器尚未完成对共享变量的修改时,就开始使用该变量。常用方法是通过对共享变量加锁的方式来控制对共享变量互斥访问。在一个时刻只能有一个处理器获得锁,其他需要操作该共享变量的处理器必须等待,直到该处理器解锁该变量为止。

总线

经典例题

在这里插入图片描述
这个题要特别注意,数据总线和地址总线是可以分开的!
在这里插入图片描述

IO方式

常见的IO方式有三种,分别是程序查询、程序中断和DMA方式,下面是它们仨的对比

核心维度对比

对比维度程序查询程序中断DMA(直接内存访问)
CPU参与度全程主动查询I/O设备状态,需等待仅在I/O完成后响应中断,无需持续等待仅初始化和结束时参与,数据传输由DMA控制器独立完成
CPU利用率极低,查询期间CPU无法执行其他任务较高,等待期间CPU可处理其他程序最高,CPU完全解放,专注核心计算
响应及时性实时性好,查询到状态就处理有中断延迟(需保存现场、优先级判断)实时性较好,传输由硬件独立控制,延迟低
硬件复杂度最简单,无需额外硬件中等,需中断控制器、中断向量表较复杂,需专用DMA控制器及配套电路
适用场景低速、简单设备(如LED、键盘),数据传输量小中低速设备(如串口、打印机),数据量中等高速、大数据量设备(如硬盘、显卡、网卡)

关键特点补充

  1. 程序查询
    • 原理简单,软件实现容易(循环查询设备状态寄存器)。
    • 缺点是“忙等”浪费CPU资源,不适用于高频数据传输。
  2. 程序中断
    • 采用“异步通知”机制,CPU与I/O设备并行工作。
    • 缺点是中断响应和处理会带来额外开销,频繁中断可能影响系统性能。
  3. DMA
    • 数据直接在内存和I/O设备间传输,绕开CPU。
    • 缺点是硬件成本高,初始化配置相对复杂,需协调地址冲突。

中断

中断的类型

①根据异常事件来源的不同,可分为内部异常和外部中断

  1. 内部异常(Exception): 通常是指CPU内部引起的异常事件,也被称为内部中断或者软件中断, 异常可进一步划分为
    • 故障(Fault)
    • 自陷(Trap)
    • 终止(Abort)
  2. 外部中断(Interrupt):外部设备向CPU发出的中断请求,请求CPU先停下来手里的活,帮忙先处理下比较紧急的外部中断,处理好之后再回去接着忙自己的事

内部中断中,故障和终止有啥区别?

故障是指令级别的错误,操作系统层面就能处理,不用重启系统
终止是计算机硬件级别的错误,此时肯定系统就崩了,需要修复后重启系统

②按中断请求是否可被屏蔽分类,可分为可屏蔽中断和非屏蔽中断。

③按能否直接提供中断服务地址分类,可分为向量中断和非向量中断。
(1) 向量中断:向量中断是指中断事件可以提供中断服务入口地址的中断。
(2) 非向量中断:非向量中断是指中断事件不能直接提供中断服务程序入口地址的中断。

④ 按中断过程能否被打断分类,可分为单重中断和多重中断。
(1) 单重中断:单重中断指 CPU 执行中断服务程序过程中不能被其他中断请求打断的中断。
(2) 多重中断:多重中断指 CPU 执行中断服务程序过程中可以去响应更高优先级的中断请求的中断。又称为中断嵌套

中断的处理流程

CPU在某个时刻收到了来自中断源的中断请求,在当前指令周期结束之后,开始进行中断响应

中断响应第一步:执行中断隐指令
首先我们要执行中断隐指令

  1. 关中断
  2. 保存断点(PC)和程序状态字(PSW)
  3. 将中断处理程序的起始地址装入PC

中断响应第二步:保存现场和中断屏蔽字
紧接着我们就去执行对应的中断处理程序了。那是不是说我直接就执行了呢?也不是,在执行完中断隐指令之后,执行具体的中断处理程序之前,我们还要做一件事情,那就是——保存现场和中断屏蔽字

(如果是多重中断,这里要开中断)

中断响应第三步:执行具体中断程序

(如果是多重中断,这里要关中断)

中断响应第四步:恢复现场和中断屏蔽字

中断响应第五步:开中断

中断响应第六步:中断返回

上面我们说的是单重中断的处理流程,如果系统支持多重中断,那么在保存现场和中断屏蔽字之后要开中断,恢复现场和中断屏蔽字之前要关中断

CPU在关中断状态下,是不是没法收到任何中断信号?

首先要纠正一个说法上的错误,CPU在关中断状态下,依然可以收到任何中断信号,这些信号只是被屏蔽了,导致CPU不能响应而已。

当然,也有些比较紧急的中断(非屏蔽中断NMI),比如硬件故障,这种高优先级的中断是不能被屏蔽的,即使处于关中断状态,CPU依然要响应

什么是中断响应阶段?

就是中断隐指令干的那三件事

  1. 关中断
  2. 保存断点和程序状态字
    • 将当前PC和PSW寄存器的值压入栈中
  3. 将中断程序服务地址送入PC

这里要特别注意,中断响应阶段干的活全都是由硬件自动完成的,所以也叫中断隐指令,保存现场和中断屏蔽字的活是由CPU执行中断服务程序完成的,并不属于中断响应阶段

中断屏蔽字

DMA

DMA的传送方式

我们知道,DMA控制器和CPU都与主存直接相连,都能对主存进行存取操作。但是一般的主存并不能同时支持这俩同时访存,因此我们就需要想办法让他们错开访问

方法一:DMA优先策略
最直接的思路就是,只要DMA控制器发信号(IO设备发送了DMA请求),CPU在当前工作周期结束之后,立即停止访存,把内存让给DMA控制器。等DMA传输结束之后,DMA控制器再给CPU发送一个“我传完了”的信号,CPU再重新开始访存

但是这样的思路有一个很大的缺陷,那就是一旦CPU把主存让给了DMA控制器,在接下来的很长一段时间内,CPU都不能访存了。一旦CPU执行了某条需要访存的指令,那就会陷入阻塞,一直等到DMA把主存还给CPU。在这段时间内,CPU啥都干不了

方法二:交替访存
将CPU的工作周期分两半,前半段允许CPU访存,后半段允许DMA访存
在这里插入图片描述
这种方法最大的缺点就是增加了一个工作周期的时长,从而就会使得CPU每秒执行的指令数减半,相当于直接把计算机的运行效率砍半

交替访存比较适合于CPU工作周期比主存存取周期长的情况。这种情况下,CPU的工作周期就大致等于访存时间+运算时间。CPU运算的时候用不到主存,这时候我们就把主存让给DMA控制器。CPU访存的时候,再把控制权拿回来,这样就不会被卡住

方法三:周期窃取
这个思路就是结合了一下方法1和方法2。首先看方法1,它最大的缺点就在于:让权给DMA之后,CPU阻塞的时间太长了。既然如此,那我就不让CPU让那么长时间,一次就让给DMA控制器1到2个周期,时间到了立马收回。有的人就说了,DMA要传的数据那么多,你时间给少了传不完咋办?很简单,一次传不完,那就让DMA控制器多借几次。

总结一下,在周期窃取策略中,我们依然秉持着DMA优先的策略,一旦DMA控制器发起了DMA请求,CPU就要尽快把主存让给DMA:

  1. 如果CPU在收到DMA请求时没有访存,那没啥好讲的,立即让权
  2. 如果CPU在收到DMA请求时正在访存,那就等CPU这个存取周期结束之后再让权
  3. 如果CPU在收到DMA请求时正在发起访存(想要访问,还没开始访问),那CPU就主动退让,把控制权交给DMA控制器

主存让给DMA控制器之后,DMA控制器只能传一到两个访存周期,时间一到立马就还给CPU。如果没传完,就下次再发起DMA请求

在这里插入图片描述

【四轴飞行器】非线性三自由度四轴飞行器模拟器研究(Matlab代码实现)内容概要:本文围绕非线性三自由度四轴飞行器模拟器的研究展开,重点介绍基于Matlab代码实现的四轴飞行器动力学建模与仿真方法。研究构建了考虑非线性特性的飞行器数学模型,涵盖姿态动力学与运动学方程,实现了三自由度(滚转、俯仰、偏航)的精确模拟。文中详细阐述了系统建模过程、控制算法设计思路及仿真结果分析,帮助读者深入理解四轴飞行器的飞行动力学特性与控制机制;同时,该模拟器可用于算法验证、控制器设计与教学实验。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的高校学生、科研人员及无人机相关领域的工程技术人员,尤其适合从事飞行器建模、控制算法开发的研究生和初级研究人员。; 使用场景及目标:①用于四轴飞行器非线性动力学特性的学习与仿真验证;②作为控制器(如PID、LQR、MPC等)设计与测试的仿真平台;③支持无人机控制系统教学与科研项目开发,提升对姿态控制与系统仿真的理解。; 阅读建议:建议读者结合Matlab代码逐模块分析,重点关注动力学方程的推导与实现方式,动手运行并调试仿真程序,以加深对飞行器姿态控制过程的理解。同时可扩展为六自由度模型或加入外部干扰以增强仿真真实性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值