Loading... ## 总览 ![image-20240923193559395.png][1] ## CPU 的功能和结构 ### CPU 的功能 1. **指令控制**。完成取指令,分析指令和执行指令的操作,即程序的顺序控制 2. **操作控制**。一条之离开那个的功能往往是由若干操作信号的组合来实现的。 CPU 管理并产生内存取出的每条指令 的操作信号,把各种操作信号送往相应的部件,从而控制这些部件按指令的要求进行动作。 3. **时间控制**。对各种操作加以时间上的控制。时间控制腰围每条指令时间顺序提供应有的控制信号。 4. **数据加工**。对数据进行算术和逻辑运算。 5. **中断处理**。对计算机运行过程中出现的异常情况和特殊请求进行处理。 #### 分工 前面提到过,CPU 是由运算器和控制器组成的,那么上述的 CPU 功能就需要这两个结构来完成。其中运算器只执行一个功能:**数据加工**。其余的操作都是由控制器完成: ![image-20240923194759978.png][2] ### CPU 的结构 #### 运算器的基本结构 ![image-20240923205321899.png][3] 1. **算术逻辑单元 (ALU)** :主要功能是进行算术 / 逻辑运算。 2. **通用寄存器组 (GPRs)**:如 `AX, BX, CX, DX, SP` 等,用于存放操作数 (包括源操作数,目的操作数以及中间结果) 和各种地址信息等。`SP` 是堆栈指针,用于只是栈顶的地址。 3. **暂存寄存器**:用于暂存从数据总线或通用寄存器送来的操作数。若使用通用寄存器则可能破坏其原有内容。暂存寄存器对应用程序员是**透明的**。 4. **累加寄存器 (ACC)** :它是一个通用寄存器,用于暂时存放 `ALU` 运算的结果信息,用于实现加法运算。 5. **程序状态字寄存器 (PSW)** :保留由算术/逻辑运算指令或测试指令的结果而建立的各种状态信息,如溢出标志 (OF) ,符号标志 (SF) ,零标志 (ZF) ,进位标志 (CF) 等。 每个标志位通常由一位触发器来保存,这些标志位组合在一起称为**程序状态字**。 6. **移位寄存器 (SR)** :不但可以用来存放操作数,而且在控制信号的作用下,寄存器中的数据可以根据需要向左或向右位移。 移位寄存器也可以充当暂存寄存器,可以在 `ALU` 输出稳定后再将数据传输至总线。 7. **计数器** :控制乘除运算的操作步数。 上述寄存器和 `ALU` 的连接方式被称为 CPU 内部单总线方式。其优点是结构简单,容易实现,但数据传输存在较多冲突的现象,性能较低。 除此之外,还有一种**专用数据通路方式**:根据指令执行过程中的数据和地址的流动方向安排连接线路,也就是每个寄存器都可以和 `ALU` 直接连接。 ![image-20240923195212901.png][4] > 此处 `AH, AL` 表示 `AX` 寄存器的高位和低位,其他寄存器也是类似的情况。 > > 每个寄存器和 `ALU` 的连线数和寄存器的容量有关,如 16bit 的寄存器就应该有 16 条线连着 `ALU` 的一端,图中是简化成了一根线。 但直接用导线连接的话又有问题:多个寄存器同时且一直向 `ALU` 传输数据。这个问题有以下解决方法: 1. 使用**多路选择器**,根据控制信号选择一路输出。 2. 使用**三态门**,可以控制每一路是否输出。 专用数据通路方式的优点是性能较高,基本不存在数据冲突。但其结构复杂,硬件量大,不容易实现。 #### 控制器的基本结构 ![image-20240923214748627.png][5] 1. **程序计数器 (PC)** :用于指出下一条指令在主存中的存放地址。CPU 就是根据 PC 的内容取主存中的指令,通常是顺序执行的,因此 PC **有自增的功能**。若 PC 和主存储器均按字节编址,则 PC 的位数等于主存储器地址位数。 2. **指令寄存器 (IR)** :用于保存当前正在执行的指令。IR 的位数等于指令字长。(**透明**) 3. **指令译码器 (ID)** :仅对操作码字段进行译码,向控制器提供特定的操作信号。 4. **微操作信号发生器** :根据 IR 的内容 (指令) ,PSW 的内容 (状态信息) 及时序信号,产生控制整个计算机系统所需的各种控制信号,其结构有组合逻辑型和存储逻辑型两种。 5. **时序系统** :用于产生各种时序信号,它们都是由**统一时钟 (CLOCK) 分频**得到。 6. **存储器地址寄存器 (MAR)** :用于存放要访问的主存储器单元的地址。MAR 的位数等于主存储器地址线数,反映了最多可寻址的存储单元的个数。(**透明**) 7. **存储器数据寄存器 (MDR)** :用于存放像主存储器写入的信息或从主存中读出的信息。MDR 的位数等于存储字长。(**透明**) #### 汇总 ![image-20240923215856299.png][6] ## 指令执行过程 ### 指令周期 **指令周期:** CPU 从主存中每取出并执行一条指令所需的全部时间。指令周期通常用若干**机器周期**来表示,机器周期又称为 **CPU 周期**。 一个机器周期又包含若干**时钟周期** (也称为**节拍,T 周期**或 **CPU 时钟周期**,它是 CPU 操作的**最基本单位**) 。 > CPU 的主频就是 1s 内 CPU 可以执行的时钟周期的数量。 ![image-20240924211047981.png][7] 每个指令周期内机器周期数可以不等,每个机器周期内的节拍数也可以不等。 ### 指令周期的流程 一个指令周期通常包含几个时间段 (执行步骤) ,每个步骤完成指令的一部分功能,几个依次执行的部分完成这条指令的全部功能。 ![image-20240924211444144.png][8] 流程图: ![image-20240924211750762.png][9] CPU 如何知道自己处于哪个周期呢? CPU 中有 4 个触发器,每个触发器可以存放 1 个二进制位,用来表示当前在 4 个周期中的哪一个: ![image-20240924212026777.png][10] ### 指令周期的数据流 #### 取指周期 ![image-20240924212134804.png][11] 1. 当前指令地址送至存储器地址寄存器,记作:$(PC) \rightarrow MAR$ 。 2. CU 发出控制信号,经控制总线传到主存。此处是读信号,记作:$1 \rightarrow R$ 。 3. 将 MAR 所指主存中的内容经数据总线送入 MDR,记作:$M(MAR) \rightarrow MDR$ 。 4. 将 MDR 中的内容 (此时是指令) 送入 IR ,记作:$(MDR) \rightarrow IR$ 。 5. CU 发出控制信号,形成下一条指令地址,也就是 PC 自增,记作:$(PC) + 1 \rightarrow PC$ 。 #### 间址周期 ![image-20240924212640139.png][12] 1. 将指令的地址码送入 MAR ,记作 $Ad(IR) \rightarrow MAR$ 或 $Ad(MDR) \rightarrow MAR$ 。 2. CU 发出控制信号,启动主存做读操作,记作:$1 \rightarrow R$ 。 3. 将 MAR 所致主存中的内容经数据总线送入 MDR ,记作:$M(MAR) \rightarrow MDR$ 。 或 将有效地址送至指令的地址码字段,记作:$(MDR) \rightarrow Ad(IR)$ 。 #### 执行周期 执行周期的任务是根据 IR 中的指令字的操作码和操作数,通过 ALU 操作产生执行结果。不同指令的执行周期操作不同,因此没有统一的数据流向。 #### 中断周期 **中断:** 暂停当前任务去完成其他任务。为了能够恢复当前任务,需要保存**断点**,一般使用堆栈来保存。假设 SP 指向栈顶元素,进栈操作为先修改指针,后存入数据。 ![image-20240924230909315.png][13] 1. CU 控制将 SP 减 1 ,修改后的地址送入 MAR ,记作:$(SP) - 1 \rightarrow SP, (SP) \rightarrow MAR$ 。 本质上是将断点存入某个存储单元,假设其地址为 $a$ ,科技做 $a \rightarrow MAR$ 。 2. CU 发出控制信号,启动主存做写操作,记作:$1 \rightarrow W$ 。 3. 将断电 (PC 内容) 送入 MDR ,记作:$(PC) \rightarrow MDR$ 。 4. CU 控制将中断服务程序的入口地址 (由向量地址形成部件产生) 送入 PC ,记作:向量地址 $\rightarrow PC$ 。 ### 指令执行方案 1. **单指令周期**:对所有的指令都选用相同的执行时间来完成。 - 指令之间串行执行,指令周期取决于执行时间最长的指令的执行时间。 - 对于哪些本来可以在更短时间内完成的指令,也要使用这个较长的周期来完成,因此会降低整个系统的运行速度。 2. **多指令周期**:对不同类型的指令选用不同的执行步骤来完成。 - 指令之间串行执行,但可选用不同个数的时钟周期来完成不同指令的执行过程。 - 需要更复杂的硬件设计。 3. **流水线方案**:在每一个时钟周期启动一条指令,尽量让多条指令同时运行,但各自处在不同的执行步骤中。 - 指令之间并行执行。 ## 数据通路 **数据通路:** 数据在功能部件之间传送的路径。 ### 单总线结构 ![image-20240925115340362.png][14] **内部总线:** 同一部件,如 CPU 内部连接各寄存器及运算部件之间的总线。 **系统总线:** 同一台计算机系统的各部件,如 CPU ,内存,通道和各类 I/O 接口间互相连接的总线。 1. **寄存器之间数据传送** 比如把 PC 内容送至 MAR ,实现传送操作的流程及控制信号为: ```text (PC) -> Bus PCout 有效, PC 内容送总线 Bus -> MAR MARin 有效,总线内容送 MAR ``` 也可写为 `(PC) -> Bus -> MAR` 。 ![image-20240925124843601.png][15] > 重点是描述清楚数据流向 2. **主存与 CPU 之间的数据传送** 比如 CPU 从主存读取指令,实现传送操作的流程及控制信号为: ```text (PC) -> Bus -> MAR PCout 和 MARin 有效,现行指令地址 -> MAR 1 -> R CU 发送读命令 (通过控制总线发出) MEM (MAR) -> MDR MDRin 有效 MDR -> Bus -> IR MDRout 和 IRin 有效,现行指令 -> IR ``` ![image-20240925124941263.png][16] 3. **执行算术或逻辑运算** 比如执行一条加法指令,微操作序列及控制信号为: ```tex Ad(IR) -> Bus -> MAR MDRout 和 MARin 有效,或 AdIRout 和 MARin 有效 1 -> R CU 发送读命令 MEM(MAR) -> 数据线 -> MDR MDRin 有效 MDR -> Bus -> Y MDRout 和 Yin 有效,操作数 -> Y (ACC) + (Y) -> Z ACCout 和 ALUin 有效,CU 向 ALU 发送加命令 Z -> ACC Zout 和 ACCin 有效,结果 -> ACC ``` ![image-20240925125329918.png][17] > 此处 Y 和 Z 都是暂存寄存器,因为 ALU 需要同时输入。 #### 例题 设有如图所示的单总线结构,分析指令 `ADD (R0), R1` 的指令流程和控制信号。 ![image-20240925130205401.png][18] 1. 分析指令功能和指令周期。 **功能:** `((R0)) + (R1) -> R0` **指令周期:** 取指周期,间指周期,执行周期。 2. 写出各阶段的指令流程。 - 取指周期:公共操作 | 时序 | 微操作 | 有效控制信号 | | :--: | :-----------------------------------: | :------------------------: | | 1 | `(PC) -> MAR` | PCout, MARin | | 2 | `M(MAR) -> MDR`<br />`(PC) + 1 -> PC` | MemR, MARout, <br />MDRinE | | 3 | `(MDR) -> IR` | MDRout, IRin | | 4 | 指令译码 | - | - 间址周期:完成取数操作,被加数在主存中,加数已经存放在寄存器 R1 中。 | 时序 | 微操作 | 有效控制信号 | | :--: | :-------------: | :------------------: | | 1 | `(R0) -> MAR` | R0out, MARin | | 2 | `M(MAR) -> MDR` | MemR, MARout, MDRinE | | 3 | `(MDR) -> Y` | MDRout, Yin | - 执行周期:执行加法并将结果存入主存中。 | 时序 | 微操作 | 有效控制信号 | | :--: | :---------------: | :---------------------------------------: | | 1 | `(R1) + (Y) -> Z` | R1out, ALUin, CU 向 ALU 发送 ADD 控制信号 | | 2 | `(Z) -> MDR` | Zout, MDRin | | 3 | `(MDR) -> M(MAR)` | MemW, MDRoutE, MARout | ### 专用通路结构 每个部件之间都有专门的数据通路。如 ![image-20240925203528468.png][19] 专用数据通路结构的数据传送流程和单总线结构几乎相同,知识需要根据图中的线路来判断是否可以传输数据。下面用一道例题来举例: #### 例题 下图是一个简化了的CPU与主存连接结构示意图(图中省略了所有的多路选择器)。其中有一个累加寄存器(ACC)、一个状态数据寄存器和其他 4 个寄存器:主存地址寄存器(MAR)、主存数据寄存器(MDR)、程序寄存器(PC)和指令寄存器(IR),各部件及其之间的连线表示数据通路,箭头表示信息传递方向。 ![image-20240925203859985.png][20] 1. 请写出图中a、b、c、d 4个寄存器的名称。 d 可以自动 +1 ,显然是 PC ; PC 内的内容是地址,需要送到 c ,且 c 能将信息传到主存,故 c 是 MAR ; b 需要传输数据到微操作发生器,显然是 IR ; a 能读写主存,又能传数据至 ALU ,故 c 为 MDR。 2. 简述图中取指令的数据通路。 ```c (PC) -> MAR // 此处考察重点不是控制信号,因此可以不写 1 -> R M(MAR) -> MDR (MDR) -> IR // 如果还想再详细点,可以加上下面两条 OP(IR) -> 微操作信号发生器 (PC) + 1 -> PC ``` 3. 简述数据再运算器和主存之间进行存 / 取访问的数据通路。 运算器是不能存储数据的,因此本题实质上是在考将存 / 取得数据放入 ACC 的流程。 设数据地址已经放入 MAR ,则有 ```text 取: M(MAR) -> MDR (MDR) -> ALU -> ACC 存: (ACC) -> MDR (MDR) -> M(MAR) ``` 4. 简述完成指令 `LDA X` 的数据通路 (`X` 为主存地址,`LDA` 的功能为 `(X) -> ACC`) 。 ```text X -> MAR M(MAR) -> MDR (MDR) -> ALU -> ACC ``` 5. 简述完成指令 `ADD Y` 的数据通路 (`Y` 为主存地址,`ADD` 的功能为 `(ACC) + (Y) -> ACC`) 。 ```text Y -> MAR M(MAR) -> MDR (MDR) -> ALU, (ACC) -> ALU ALU -> ACC ``` 6. 简述完成指令 `STA Z` 的数据通路 (`X` 为主存地址,`STA` 的功能为 `(ACC) -> Z`) 。 ```text Z -> MAR (ACC) -> MDR (MDR) -> M(MAR) ``` ## 控制器 ### 指令操作流程回顾 CU 发出一个**微命令**,可完成对应的**微操作**。如:一条微命令使得 $PC_{out}, MAR_{in}$ 有效,则对应的微操作为 $(PC) \rightarrow MAR$ 。 ![image-20240926111344649.png][21] - 一个节拍内可以并行完成多个 "相容" 的操作。 - 同一个微操作可能再不同指令的不同各阶段被使用 - 不同指令的执行周期所需要的节拍数各不相同。为了简化设计,选择**定长的机器周期**,以可能出现的最大节拍数为准。(通常以访存所需节拍数作为参考) - 若实际所需的节拍数较少,可将微操作安排在机器周期末尾几个节拍上进行。 ### 硬布线控制器 控制器设计的核心思想就是根据**指令操作码,目前的机器周期,节拍信号和机器状态条件**,确定现在这个节拍下**应该发出哪些微命令**。 ![image-20240926112705604.png][22] 所有指令的取指周期,$T_0$ 节拍内一定要完成 $(PC) \rightarrow MAR$ ,因此 $C_1$ 的逻辑表达式为 $C_1 = FE \cdot T_0$ 。 再例如 $M(MAR) \rightarrow MDR$ 微操作的逻辑表达式为: $$ FE \cdot T_1 + IND \cdot T_1(ADD + STA + LAD + JMP + BAN) + EX \cdot T_1(ADD + LDA) $$ 对应的电路图如下: ![image-20240926114225681.png][23] > 一般不考电路图,了解即可。 #### 设计步骤 1. 分析每个阶段的微操作序列。 取值,间指,执行,中断四个阶段。需要确定哪些指令在什么阶段,什么条件下会使用到的微操作。 2. 选择 CPU 的控制方式。 采用定长机器周期还是不定长机器周期?每个机器周期安排几个节拍? 3. 安排微操作时序。 假设采用同步控制方式 (定长机器周期) ,每个机器周期内有三个节拍。如何用 3 个节拍完成整个机器周期内的所有微操作? 4. 电路设计。 确定每个微操作命令的逻辑表达式,并用电路实现。 设计硬布线控制器需要罗列出**所有**指令在各个阶段的微操作序列,这样才可以知道在什么情况下需要用到这个微操作。 #### 微操作时序的安排 ##### 原则 1. 微操作的**先后顺序不得随意更改**。 2. **被控对象不同**的微操作尽量安排在**一个节拍**内完成。 3. 占用**时间较短**的微操作尽量安排在**一个节拍**内完成,并**允许有先后顺序**。 ##### 取指周期 | 节拍 | 微操作 | 注释 | | :---: | :---------------------------- | :------------- | | $T_0$ | $(1) PC \rightarrow MAR$ | | | $T_0$ | $(2) 1 \rightarrow R$ | 存储器空闲即可 | | $T_1$ | $(3) M(MAR) \rightarrow MDR$ | 需要访存 | | $T_1$ | $(6) (PC) + 1 \rightarrow PC$ | 被控对象不同 | | $T_2$ | $(4) MDR \rightarrow IR$ | | | $T_2$ | $(5) OP(IR) \rightarrow ID$ | 占用时间短 | - $M(MAR) \rightarrow MDR$ 从主存中取数据,用时较长,因此必须使用一个时钟周期才能保证微操作的完成。(涉及访存的都需要用一个节拍) - $MDR \rightarrow IR$ 是 CPU 内部寄存器的数据传送,速度很快,因此可以在一个时钟周期内可以紧接着完成 $OP(IR) \rightarrow ID$ ,也就是一次可以同时发出两个微命令。 ##### 间址周期 | 节拍 | 微操作 | 注释 | | :---: | :--------------------------- | :------------- | | $T_0$ | $(1) Ad(IR) \rightarrow MAR$ | | | $T_0$ | $(2) 1 \rightarrow R$ | 被控对象不一样 | | $T_1$ | $(3) M(MAR) \rightarrow MDR$ | 需要访存 | | $T_2$ | $(4) MDR \rightarrow Ad(IR)$ | | ##### 执行周期 执行周期的指令各不相同,需要对每个都单独规划。这里简单列出一些指令: ![image-20240926121624652.png][24] ##### 中断周期 中断周期的三个任务: 1. 保存断点 2. 形成中断服务程序的入口地址 3. 关中断 | 节拍 | 微操作 | 注释 | | :---: | :---------------------------- | :----------- | | $T_0$ | $(1) a \rightarrow MAR$ | | | $T_0$ | $(2) 1 \rightarrow W$ | | | $T_0$ | $(3) 0 \rightarrow EINT$ | 关中断 | | $T_1$ | $(4) (PC) \rightarrow MDR$ | | | $T_2$ | $(4) MDR \rightarrow M(MAR)$ | 需要访存 | | $T_2$ | $(5) 向量地址 \rightarrow PC$ | 不同被控对象 | 这些操作都是由终端隐指令完成的。(终端隐指令是由硬件完成的一系列操作) #### 电路设计 1. 列出操作时间表 ![image-20240926125456560.png][25] ![image-20240926125535706.png][26] > 间指周期标志是标记是否需要多级间址。 ![image-20240926125715400.png][27] 2. 写出微操作命令的最简表达式 根据第一步列出的操作时间表,就可以写出各个命令的逻辑表达式。以 $M(MAR) \rightarrow MDR$ 为例,得到的逻辑表达式如下: $$ FE \cdot T_1 + IND \cdot T_1(ADD + STA + LAD + JMP + BAN) + EX \cdot T_1(ADD + LDA) $$ 3. 画出逻辑图 根据逻辑表达式画出电路图即可。 ![image-20240926114225681.png][28] #### 总结 **设计步骤**: 1. 分析每个阶段的微操作序列 2. 选择 CPU 的控制方式 3. 安排微操作时序 4. 电路设计 - 列出操作时间表 - 写出微操作命令的最简表达式 - 画出逻辑图 **特点**: - 指令越多,设计和实现就越复杂,因此一般用于 RISC 。 - 扩充指令困难。每扩充一条新指令就需要大改控制器的设计。 - 由于使用纯硬件实现控制,因此执行速度很快。微操作控制信号由组合逻辑电路即时产生。 ### 微程序控制器 **微程序:** 由伪指令序列组成,每一种指令对应一个微程序,微程序是对**指令执行步骤**的描述。 **主要思想:** 采用 "存储程序" 的思想,CPU 出厂前将所有指令的微程序存入**控制器存储器**中。 > 微命令与微操作一一对应,伪指令中可能包含多个微命令。 #### 基本结构 1. **微地址形成部件**。用于产生咸初始和后续微地址,以保证微指令的连续执行。 2. **微指令地址寄存器**。接受微地址形成部件送来的微地址,为读取微指令做准备。 3. **控制寄存器**。微程序控制器的核心部件,用于存放各指令对应的微程序。 4. **微指令寄存器**。其位数等于微指令字长。 ![image-20240926200201760.png][29] 微命令与微操作一一对应,一个微命令对应一根输出线。 #### 工作过程 执行指令的流程如下: 1. 执行取指令公共操作。在机器开始运行时,自动将取指微程序的入口地址送入 CMAR ,并从 CM 中读出相应的微指令并送入 CMDR 。取指微程序的入口地址一般为 CM 的 0 行单元。取指微程序执行完成后,从主存中过去出的机器指令就已经存入指令寄存器中了。 2. 由机器指令的操作码字段通过微地址形成部件产生该机器指令所对应的微程序的入口地址,并送入 CMAR 。 3. 从 CM 中逐条读取对应的微指令并执行。 4. 执行完对应于一条机器指令的一个微程序之后,活到取指微程序的入口地址,继续 1 以完成取下一条机器指令的公共操作。 ![image-20240926205324660.png][30] - 取指周期的微程序通常是公用的,因此如果某指令系统中有 n 条机器指令,则 CM 中微程序段的个数至少为 n+1 个。 - 一些早期的 CPU ,物联网设备的 CPU 可能不提供间接寻址和中断功能,因此这类 CPU 可能不包含间址周期,中断周期的微程序段。 > 物理上取指周期,执行周期看起来像是两个微程序,但逻辑上应该把它们看作是一个整体。因此 "**一条指令对应一个微程序**" 的说法是正确的。 #### 微指令的设计 - 微命令与微操作一一对应,一个微命令对应一根输出线。 - 有的微命令可以并行执行,因此一条微指令可以包含多个微命令。 ##### 微指令的格式 > 相容性微命令:可以并行完成的微命令; > > 互斥性微命令:不允许并行完成的微命令。 1. **水平型微指令**:一条微指令能定义**多个可并行**的微命令。 基本格式: ![image-20240927201103519.png][31] - **优点**:微程序短,执行速度快。 - **缺点**:微指令长,编写微程序较麻烦。 2. **垂直型微指令**:一条微指令只能定义**一个**微命令,由微操作码字段规定具体功能。 基本格式: ![image-20240927201827518.png][32] - **优点**:微指令短,简单,规整,便于编写微程序。 - **缺点**:微程序长,执行速度慢,工作效率低。 3. **混合型微指令**:在垂直型的基础上增加一些不太复杂的并行操作。 微指令较短,仍便于编写;微程序也不长,执行速度较快。 ##### 微指令的编码方式 > 如何设计水平型位置令的操作控制部分,使其可以表示一系列的控制信号? 微指令的编码方式又称为微指令的控制方式,它是指如何对微指令的控制字段进行编码,以形成控制信号。编码的目标是在保证速度的情况下,尽量缩短微指令字长。 1. **直接编码 (直接控制) 方式** 在微指令的操作控制字段中,**每一位代表一个微操作命令**。 ![image-20240927210037877.png][33] - **优点**:简单,只管,执行速度快,操作并行性好。 - **缺点**:微指令字长过长, $n$ 个微命令就要求微指令的操作字段有 $n$ 位,导致 CM 需要的容量极大。 2. **字段直接编码方式** 将微指令的控制字段分成若干 "段" ,**每段经译码后发出控制信号**。 ![image-20240927211129978.png][34] 微命令字段分段原则: - **互斥性**微命令分在**同一段**内,**相容性**微命令分在**不同段**内。 - 每个小段中包含的信息为不能太多,否则将增加译码线路的复杂性和译码时间。 - 一般每个小椴还要留出一个状态,表示本字段不发出任何微命令。通常用全为 0 表示不操作。 例如当某字段的长度为 3 位的时候,最多只能表示 7 个互斥的微命令。 **优点**:可以缩短指令字长 **缺点**:要通过译码电路后再发出微命令,因此比直接编码的方式慢。 3. **字段间接编码方式** 一个字段的某些微命令需要由另一个字段中的某些微命令来解释。由于不是考字段直接译码发出的微命令,故称为字段间接编码,又称隐式编码。 ![image-20240927211537880.png][35] - **优点**:可进一步缩短指令字长。 - **缺点**: 削弱了微指令的并行控制能力,故通常作为字段直接编码的一种辅助手段。 **例题:** 某计算机的控制器采用微程序控制方式,微指令中的操作控制字段采用**字段直接编码法**,共有 33 个微命令,构成 5 个互斥类,分别包含 7、3、12、5 和 6 个微命令,则操作控制字段至少有多少位? 解:第一个互斥类有 7 个微命令,需要留出一位表示状态不操作,因此需要表示 8 种不同的状态,即需要 3 个二进制位。 以此类推,后面四个互斥类分别需要 2, 4, 3, 3 个二进制位。 因此操作控制字段至少需要 $3 + 2 + 4 + 3 + 3 = 15$ 位。 > 比直接编码方式少用了 18 个二进制位。 ##### 微指令的地址形成方式 1. 由微指令的**下地址字段**指出。 微指令格式中设置一个下地址字段,由微指令的下地址字段直接给出后继微指令的地址。这种方式又称为**断定方式**。 2. 根据机器指令的**操作码**形成。 当机器指令取至指令寄存器后,微指令的地址由操作码经**微地址形成部件**形成。 3. 增量计数器法。 $(CMAR) + 1 \rightarrow CMAR$ 。 4. 分支转移。 转移方式:指明判别条件;转移地址:指明转移成功后的去向。 > 类似 jmp 指令 ![image-20240927225540591.png][36] 5. 通过测试网络。 > 了解即可 ![image-20240927225229470.png][37] 6. 由硬件产生微程序入口地址。 第一条微指令地址是由专门的硬件产生的,即用专门的硬件记录取指周期微程序首地址。 除此之外,中断周期中也是由硬件产生中断周期微程序首地址。 **例题:** 某计算机采用微程序控制器,共有 32 条指令,公共的取指令微程序包含 2 条微指令,各指令对应的微程序平均由 4 条微指令组成,采用断定法(下地址字段法)确定下条微指令地址,则微指令中下地址字段的位数至少是多少位? 解:取指阶段的微程序是共用的,题目并没有给间址和中断微程序的信息,而这两部分是可以没有的,因此总共需要 $32 \times 4 + 2 = 130$ 条微指令。 采用断定法确定微指令的地址,因此表示 130 条指令至少需要 8 位二进制位。 故下地址字段的位数最少为 8 位。 #### 微程序控制单元的设计 **设计步骤**: 1. **分析每个阶段的微操作序列**。 以取指周期为例: | 节拍 | 微操作 | 注释 | | :---: | :---------------------------------- | :--------------------------------------------- | | $T_0$ | $PC \rightarrow MAR$ | 微指令 $a$ | | $T_0$ | $1 \rightarrow R$ | 微指令 $a$ | | $T_1$ | $Ad(CMDR) \rightarrow CMAR$ | 需要用 $T_1$ 节拍来确定下一条微指令的地址 | | $T_2$ | $M(MAR) \rightarrow MDR$ | 微指令 $b$ | | $T_2$ | $(PC) + 1 \rightarrow PC$ | 微指令 $b$ | | $T_3$ | $Ad(CMDR) \rightarrow CMAR$ | 需要用 $T_3$ 节拍来确定下一条微指令的地址 | | $T_4$ | $MDR \rightarrow IR$ | 微指令 $c$ | | $T_4$ | $OP(IR) \rightarrow$ 微地址形成部件 | 微指令 $c$ | | $T_5$ | 微地址形成部件 $\rightarrow CMAR$ | 根据指令操作码确定其执行周期微指令序列的首地址 | 显然微程序控制器的速度比硬布线控制器更慢。 2. **写出对应机器指令的微操作命令以及节拍安排**。 - 写出每个周期所需要的微操作。(参考硬布线) - 补充微程序控制器特有的微操作: **取指周期**: $Ad(CMDR) \rightarrow CMAR$ ,每条微指令结束之后都要进行。 $OP(IR) \rightarrow$ 微地址形成部件 $\rightarrow CMAR$ ,取指周期的最后一条微指令完成后,要根据指令操作码确定其执行周期的微程序首地址。 **执行周期**: $Ad(CMDR) \rightarrow CMAR$ ,每条微指令结束之后都要进行。 > 暂不考虑间址周期和中断周期。 3. **确定微指令格式**。 根据微操作个数决定采用何种编码方式,以确定微指令的操作控制字段的位数。 根据 CM 中存储的微指令总数,确定微指令的顺序控制字段的位数。 最后按照操作控制字段位数和顺序控制字段位数就可确定微指令字长。 4. **编写微指令码点**。 根据操作控制字段每一位代表的微操作命令,编写每一条微指令的码点。参考上面微指令的编码方式。 ##### 分类 1. 静态微程序设计和动态微程序设计 - 静态:微程序无需改变,采用 ROM 作为 CM 。 - 动态:可以通过改变微指令和微程序改变机器指令,有利于仿真。采用 EPROM 作为 CM 的介质 2. 毫微程序设计 简单了解即可。**毫微指令与微指令的关系**好比**微指令与机器指令的关系**,毫微程序设计就是用毫微程序去解释微程序。 ### 硬布线与微程序的比较 | | 微程序控制器 | 硬布线控制器 | | :------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | 工作原理 | 微操作控制信号以为程序的形式存放在控制存储器中,执行指令时读出即可 | 微操作控制信号由组合逻辑电路根据当前的指令码,状态和时序即时产生 | | 执行速度 | 慢 | 快 | | 规整性 | 较规整 | 繁琐,不规整 | | 应用场合 | CISC CPU | RISC CPU | | 易扩充性 | 易扩充修改 | 困难 | ### 总结 ![image-20240928210734554.png][38] ## 指令流水线 ### 流水线的定义 一条指令的执行过程可以分成多个阶段 (或过程) 。根据计算机的不同,具体的分法也不同。 | 取指 | 分析 | 执行 | | :--: | :--: | :--: | - **取指**:根据 PC 内容访问主存储器,取出一条指令送入 IR 中。 - **分析**:对指令操作码进行译码,按照给定的寻址方式和地址字段中的内容形成操作数的有效地址 EA ,并从有效地址 EA 中取出操作数。 - **执行**:根据操作码字段,完成指令规定的功能,即把运算结果写道通用寄存器或主存中。 > 每个阶段用到的硬件都不一样 设取指,分析,执行三个阶段的用时都相等,用 $t$ 表示,按照以下几种执行方式分析 $n$ 条指令的执行时间: 1. **顺序执行方式**:总耗时 $T = n \times 3t = 3nt$ 。 ![image-20240928232535637.png][39] 传统冯·诺依曼机采用顺序执行方式,又称**串行执行方式**。 - **优点**:控制简单,硬件代价小。 - **缺点**:执行指令的速度较慢,在任何时刻,处理机中只有一条指令在执行,各功能部件的利用率很低。 2. **一次重叠执行方式**:总耗时 $T = 3t + (n - 1) \times 2t = (1 + 2n)t$ 。 ![image-20240928232740688.png][40] - **优点**:程序的执行时间缩短了 $1 \over 3$ ,各功能部件的利用率也明显提高。 - **缺点**:硬件开销增加,控制过程也比顺序执行更复杂。 3. **二次重叠执行方式**:总耗时 $T = 3t + (n - 1) \times t = (2 + n)t$ 。 ![image-20240928232959800.png][41] 与顺序执行相比,指令的执行时间缩短了接近 $2 \over 3$ ,但这是一种理想的指令执行方式。 > 此处每条指令分为三个阶段,一般会分为 5 各阶段。 ### 流水线的表示方法 1. **指令执行过程图** ![image-20240928233311529.png][42] 主要用于分析指令执行过程以及影响流水线的因素。 2. **时空图** ![image-20240928233405696.png][43] 主要用于分析流水线的性能。 ### 流水线的性能指标 #### 吞吐率 单位时间内流水线所完成的任务数量,或是输出结果的数量。 设任务数为 $n$ ,处理完成 $n$ 个任务所用的时间为 $T_k$ ,则计算流水线吞吐量 (TP) 的基本公式为 $TP = \frac{n}{T_k}$ 。 **理想情况**下,流水线的时空图如下: ![image-20240928233739038.png][44] 设一条指令的执行分为 $k$ 个阶段,每个阶段耗时 $\Delta t$ ,可得 $T_k = (k + n - 1)\Delta t$ ,此时流水线的实际吞吐率为 $TP = \frac{n}{(k + n - 1)\Delta t}$ 。 当连续输入的任务数 $n \rightarrow \infty$ 时,最大吞吐量为 $TP_{max} = \frac{1}{\Delta t}$ 。 > 一般取 $\Delta t$ 为一个时钟周期 (最理想的情况) 。 - 装入时间:$k$ 个阶段所对应的硬件全部投入工作所需要的时间。 - 排空时间:$k$ 个阶段对应的硬件全部完成工作所需要的时间。 #### 加速比 完成同样一批任务,不使用流水线所用的时间与使用流水线所用的时间之比。 设 $T_0$ 表示不适用流水线时的执行时间,即顺序执行所使用的时间,则计算流水线加速比 (S) 的基本公式为 $S = \frac{T_0}{T_k}$ 。 **理想情况**下,流水线的时空图如下: ![image-20240929013127448.png][45] 设一条指令的执行分为 $k$ 个阶段,每个阶段耗时 $\Delta t$ ,可得 $T_k = (k + n - 1)\Delta t$ ,此时流水线的实际加速比为 $S = \frac{kn\Delta t}{(k + n - 1)\Delta t} = \frac{kn}{k + n - 1}$ 。 当连续输入的任务数 $n \rightarrow \infty$ 时,最大加速比为 $S_{max} = k$ 。 #### 效率 流水线的设备利用率称为流水线的效率。 在时空图上,流水线的效率定义为完成 $n$ 个任务占用的时空区有效面积与 $n$ 个任务所用的时间与 $k$ 个流水段所围成的时空区总面积之比。 流水线效率 (E) 的一般公式为 $E = \frac{\text{时空区有效面积}}{\text{时空区总面积}} = \frac{T_0}{kT_k}$ 。 **理想情况**下,流水线的时空图如下: ![image-20240929013702381.png][46] 设一条指令的执行分为 $k$ 个阶段,每个阶段耗时 $\Delta t$ ,可得 $T_k = (k + n - 1)\Delta t$ ,此时流水线的效率为 $E = \frac{k(k-1)\Delta t}{k(k + n - 1)\Delta t} = \frac{k - 1}{k + n - 1}$ 。 当连续输入的任务数 $n \rightarrow \infty$ 时,效率 $E = 1$ 。 ### 流水线的影响 #### 机器周期的设置 一个经典的五段式指令流水线: ![image-20240929212646588.png][47] - 流水线每一个功能段部件后面都要有一个**缓冲寄存器**,或称为**锁存器**。其作用是**保存本流水段的执行结果**,提供给下一流水段使用。 - RISC 的操作数一定来自于寄存器,即使在主存中也要先读入寄存器。 为了方便流水线的设计,会将每个阶段的耗时去程一样,以最长耗时为准。此处应将机器周期设置为100ns 。 #### 影响流水线的因素 1. **结构相关 (资源冲突)** 由于多条指令在同一时刻争用统一资源而形成的冲突称为结构相关。 ![image-20240929213637032.png][48] 解决方法: - 后一相关指令暂停一个周期。 - 资源重复配置:使用数据存储器 + 指令存储器,从而解决冲突。 2. **数据相关 (数据冲突)** 在一个程序中,存在必须等前一条指令执行完才能执行后一条指令的情况,则这两条指令为数据相关。 ![image-20240929214010639.png][49] 解决办法: - 把遇到数据相关的指令及其后续指令都暂停一至几个时钟周期,知道数据相关问题消失后再继续执行。 可分为使用硬件阻塞 (stall) 和软件插入 "NOP" 两种方法。 - 数据旁路技术 (转发机制) 。 在第一条指令计算完后,迁出一条线直接将运算结果输入第二条指令的 ALU 中,即迁一条数据旁路出来,从而避免数据冲突。 - 编译优化:通过编译器调整指令顺序来解决数据相关。 第一种插入空操作的方法,将空操作换成其他可并行的指令。 3. **控制相关 (控制冲突)** 当流水线遇到转移指令和其他改变 PC 值得指令而造成断流时,会引起控制相关。 ![image-20240929214802258.png][50] 解决方法: - **转移指令分支预测**。 可以使用简单预测 (永远猜 true 或 false) ,动态预测 (根据历史情况动态调整) - **预取转移成功和不成功两个控制流方向上的目标指令**。 需要付出代价,比如增加硬件复杂度。 - **加快和提前形成条件码**。 类似并行加法器的实现。 - **提高转移方向得猜准率**。 第一种分支预测方法的优化。 #### 小结 ![image-20240929215335136.png][51] ### 五段式指令流水线 ![image-20240930003511758.png][52] > 只有上一条指令进入 ID 段后,下一条指令才可以开始 IF 段,否则会覆盖 IF 段锁存器的内容。 - $R_s$ 指源操作数 (source) - $R_d$ 指目的操作数 (destination) #### 运算类指令执行过程 1. IF 取指 根据 PC 从指令 Cache 取指令至 IF 段的锁存器。 2. ID 译码 & 取数 取出操作数至 ID 段锁存器。 3. EX 执行 运算,并将结果存入 EX 段锁存器。 4. ~~M 访存~~ 运算类指令执行过程中一般不需要访存,所以该段为空。但为了流水线线的统一,M 段还是需要占用一个时钟周期。 5. WB 写回寄存器 将运算结果写回指定寄存器。 | 运算类指令距离 | 指令的汇编格式 | 功能 | | ----------------------------- | -------------- | ------------------- | | 加法指令 (两个寄存器相加) | `ADD Rs,Rd` | `(Rs) + (Rd) -> Rd` | | 加法指令 (寄存器与立即数相加) | `ADD #996,Rd` | `996 + (Rd) -> Rd` | | 算数左移指令 | `SHL Rd` | `(Rd) << 2 -> Rd` | #### LOAD 指令执行过程 > 通常,RISC 处理器只有 LOAD 和 STORE 指令才能访问主存。 1. IF 取指 根据 PC 从指令 Cache 取指令至 IF 段的锁存器。 2. ID 译码 & 取数 将基址寄存器的值放到锁存器 A ,将偏移量的值放入 Imm 。 3. EX 执行 运算,得到有效地址。 4. M 访存 从数据 Cache 中取数并放入锁存器。一般需要访存的时候都可以从 Cache 中取到相应的数据。 5. WB 写回寄存器 将取出的数写回寄存器。 指令的汇编格式和功能: ```assembly LOAD Rd, 996(Rs) # (996 + (Rs)) -> Rd # 或简写为 LOAD Rd, mem # (mem) -> Rd ``` #### STORE 指令执行过程 > 通常,RISC 处理器只有 LOAD 和 STORE 指令才能访问主存。 1. IF 取指 根据 PC 从指令 Cache 取指令至 IF 段的锁存器。 2. ID 译码 & 取数 将基址寄存器的值放到锁存器 A ,将偏移量的值放入 Imm ,将要存的数放到 B 。 3. EX 执行 运算,得到有效地址,并将锁存器 B 的内容放到锁存器 Store 内。 4. M 访存 写入数据 Cache 。 5. ~~WB 写回寄存器~~ 空段 指令的汇编格式和功能: ```assembly STORE Rs, 996(Rd) # Rs -> (996 + (Rd)) # 或简写为 STORE Rs, mem # Rs -> (mem) ``` #### 条件转移指令执行过程 1. IF 取指 根据 PC 从指令 Cache 取指令至 IF 段的锁存器。 2. ID 译码 & 取数 将进行比较的两个数放入锁存器 A, B ,偏移量放入 Imm 。 3. EX 执行 运算,比较两个数。 4. M 访存 将目标 PC 值写回 PC 。写回 PC 的功能段称为 WrPC 段,其耗时很短,因此可以和 M 段合并执行。 5. ~~WB 写回寄存器~~ 空段 指令的汇编格式和功能: ```assembly beq Rs, Rt, #偏移量 # 若(Rs) == (Rt), 则 (PC) + 指令字长 + (偏移量 × 指令字长) -> PC;否则 (PC) + 指令字长 -> PC bne Rs, Rt, #偏移量 # 若(Rs) != (Rt), 则 (PC) + 指令字长 + (偏移量 × 指令字长) -> PC;否则 (PC) + 指令字长 -> PC ``` > 通常 IF 段结束之后 PC 就会自动 "+1" ,因此上面没有写这个过程。 #### 无条件转移指令执行过程 1. IF 取指 根据 PC 从指令 Cache 取指令至 IF 段的锁存器。 2. ID 译码 & 取数 偏移量放入 Imm 。 3. EX 执行 将目标 PC 值写回 PC 。WrPC 段越早完成,就越能避免控制冲突。当然,也可以在 WB 段时间内才修改 PC 的值。 4. ~~M 访存~~ 空段 5. ~~WB 写回寄存器~~ 空段 指令的汇编格式和功能: ```assembly jmp #偏移量 # (PC) + 指令字长 + (偏移量 × 指令字长) -> PC ``` #### 例题 ![image-20240930010424807.png][53] ### 流水线的分类 > 了解即可 1. **部件功能级,处理机级和处理机间级流水线** 根据**流水线使用的级别**的不同,流水线可分为部件功能级,处理机级和处理机间级流水线。 - **部件功能级流水**就是将复杂的算术逻辑运算组成流水线工作方式。例如,可将浮点加法操作分成求阶差,对阶,尾数相加,结果规格化这四个子过程。 - **处理机级流水**是把一条指令的解释过程分成多个子过程。如之前的取指,译码,执行,访存,写回这五个子过程。 - **处理机间级流水**是一种宏流水,其中每一个处理机完成某一专门任务,各个处理机所得到的结果需要存放在与下一个处理机所共享的存储器中。 2. **单功能流水线和多功能流水线** 按流水线可以完成的功能,流水线可分为但功能流水线和多功能流水线。 - **单功能流水线**指只能实现一种固定的专门功能的流水线。 - **多功能流水线**指通过各段间的不同连接方式可以同时或不同时地实现多种功能地流水线。 3. **动态流水线和静态流水线** 按**同一时间内各段之间的连接方式**,流水线可分为静态流水线和动态流水线。 - **静态流水线**指在同一时间内,流水线的各段只能按同一种功能的连接方式工作。 - **动态流水线**指在同一时间内,当某些段正在实现某种运算时,另一些段却正在进行另一种运算。这样对提高流水线效率很有好处,但会是流水线控制变得很复杂。 4. **线性流水线和非线性流水线** 按流水线的**各个功能段之间是否有反馈信号**,流水线可分为线性流水线和非线性流水线。 - **线性流水线**中,从输入到输出,每个功能段只允许经过一次,不存在反馈回路。 - **非线性流水线**存在反馈回路,从输入到输出过程中,某些功能段将数次通过流水线,这种流水线适合进行线性递归的运算。 ### 流水线的多发技术 1. **超标量技术** 通过编译优化技术把可并行执行的指令搭配起来 (空分复用) : ![image-20240929225136133.png][54] - 每个时钟周期内可**并发多条独立指令**。 - 需要配置多个功能部件,比如多个 ALU 。 - 不能调整指令的执行顺序。 2. **超流水技术** 在**一个时钟周期**内**再分段**,使得**一个功能部件**在一个时钟周期内能**使用多次** (时分复用) : ![image-20240929225614859.png][55] - 理论上流水线速度是原来速度的 3 倍。 - 不能调整指令的执行顺序。 - 靠编译程序解决优化问题。 3. **超长指令字** 由编译程序挖掘出指令间**潜在的并行性**,将**多条能并行操作**的指令组合称**一条**具有**多个操作码字段**的**超长指令字** (可能达到几百位) : ![image-20240929225757016.png][56] - 采用多个处理部件。 ### 总结 ![image-20240929225952472.png][57] ## 多处理器 > 大纲只要求掌握基本概念,一位置只考选择题 ![image-page-0002.jpg][58] ### 硬件多线程 | | 细粒度多线程 | 粗粒度多线程 | 同时多线程 (SMT) | | ------------ | ---------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------ | | 指令发射 | 轮流发射个线程的指令<br />(每个时钟周期发射一个线程) | 连续几个时钟周期都发射同一线程的指令序列,流水线阻塞时切换另一个线程 | 一个时钟周期内同时发射多个线程的指令 | | 线程切换频率 | 每个时钟周期切换一次线程 | 只有流水线阻塞的时候才会切换一次线程 | NULL | | 线程切换代价 | 低 | 高,需要重载流水线 | NULL | | 并行性 | 指令级并行,线程间不并行 | 指令级并行,线程间不并行 | 指令级并行,线程级并行 | [1]: https://blog.domineto.top/usr/uploads/2024/09/1308724326.png [2]: https://blog.domineto.top/usr/uploads/2024/09/3798851601.png [3]: https://blog.domineto.top/usr/uploads/2024/09/139218988.png [4]: https://blog.domineto.top/usr/uploads/2024/09/3954360267.png [5]: https://blog.domineto.top/usr/uploads/2024/09/3971810855.png [6]: https://blog.domineto.top/usr/uploads/2024/09/1408751071.png [7]: https://blog.domineto.top/usr/uploads/2024/09/3025275496.png [8]: https://blog.domineto.top/usr/uploads/2024/09/2331771386.png [9]: https://blog.domineto.top/usr/uploads/2024/09/2192357131.png [10]: https://blog.domineto.top/usr/uploads/2024/09/1868842393.png [11]: https://blog.domineto.top/usr/uploads/2024/09/2360186677.png [12]: https://blog.domineto.top/usr/uploads/2024/09/1758417198.png [13]: https://blog.domineto.top/usr/uploads/2024/09/474719152.png [14]: https://blog.domineto.top/usr/uploads/2024/09/4080035580.png [15]: https://blog.domineto.top/usr/uploads/2024/09/3046440587.png [16]: https://blog.domineto.top/usr/uploads/2024/09/466535843.png [17]: https://blog.domineto.top/usr/uploads/2024/09/1247885447.png [18]: https://blog.domineto.top/usr/uploads/2024/09/2496172084.png [19]: https://blog.domineto.top/usr/uploads/2024/09/501354585.png [20]: https://blog.domineto.top/usr/uploads/2024/09/2446093546.png [21]: https://blog.domineto.top/usr/uploads/2024/09/3122589389.png [22]: https://blog.domineto.top/usr/uploads/2024/09/1190179906.png [23]: https://blog.domineto.top/usr/uploads/2024/09/641269646.png [24]: https://blog.domineto.top/usr/uploads/2024/09/156140697.png [25]: https://blog.domineto.top/usr/uploads/2024/09/3794486164.png [26]: https://blog.domineto.top/usr/uploads/2024/09/1928271939.png [27]: https://blog.domineto.top/usr/uploads/2024/09/2041629724.png [28]: https://blog.domineto.top/usr/uploads/2024/09/641269646.png [29]: https://blog.domineto.top/usr/uploads/2024/09/938896892.png [30]: https://blog.domineto.top/usr/uploads/2024/09/1876096928.png [31]: https://blog.domineto.top/usr/uploads/2024/09/1069262732.png [32]: https://blog.domineto.top/usr/uploads/2024/09/2520336739.png [33]: https://blog.domineto.top/usr/uploads/2024/09/4166537365.png [34]: https://blog.domineto.top/usr/uploads/2024/09/1247095310.png [35]: https://blog.domineto.top/usr/uploads/2024/09/4171890329.png [36]: https://blog.domineto.top/usr/uploads/2024/09/2334488402.png [37]: https://blog.domineto.top/usr/uploads/2024/09/2449834661.png [38]: https://blog.domineto.top/usr/uploads/2024/09/2248826987.png [39]: https://blog.domineto.top/usr/uploads/2024/09/3956407433.png [40]: https://blog.domineto.top/usr/uploads/2024/09/3969591485.png [41]: https://blog.domineto.top/usr/uploads/2024/09/852459518.png [42]: https://blog.domineto.top/usr/uploads/2024/09/201105887.png [43]: https://blog.domineto.top/usr/uploads/2024/09/1350680770.png [44]: https://blog.domineto.top/usr/uploads/2024/09/3042077391.png [45]: https://blog.domineto.top/usr/uploads/2024/09/737090519.png [46]: https://blog.domineto.top/usr/uploads/2024/09/2003219941.png [47]: https://blog.domineto.top/usr/uploads/2024/09/2955855614.png [48]: https://blog.domineto.top/usr/uploads/2024/09/2224396289.png [49]: https://blog.domineto.top/usr/uploads/2024/09/2741182886.png [50]: https://blog.domineto.top/usr/uploads/2024/09/1605426959.png [51]: https://blog.domineto.top/usr/uploads/2024/09/2132069139.png [52]: https://blog.domineto.top/usr/uploads/2024/09/2863054335.png [53]: https://blog.domineto.top/usr/uploads/2024/09/2652333342.png [54]: https://blog.domineto.top/usr/uploads/2024/09/125931174.png [55]: https://blog.domineto.top/usr/uploads/2024/09/2824487337.png [56]: https://blog.domineto.top/usr/uploads/2024/09/1656657581.png [57]: https://blog.domineto.top/usr/uploads/2024/09/3368890761.png [58]: https://blog.domineto.top/usr/uploads/2024/09/3907255936.jpg 最后修改:2024 年 09 月 30 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏