Skip to content

Commit

Permalink
update img link
Browse files Browse the repository at this point in the history
  • Loading branch information
crisxuan committed Jan 29, 2023
1 parent 2294276 commit 8f48068
Show file tree
Hide file tree
Showing 37 changed files with 851 additions and 1,317 deletions.
25 changes: 14 additions & 11 deletions assembly/assembly01-basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@

所以你就能理解

<img src="http://www.cxuan.vip/image-20230118092915870.png" alt="image-20230118092915870" style="zoom:50%;" />
![](http://www.cxuan.vip/image-20230128210530837.png)

这本书是讲啥的了。

> 它主要是基于 MIPS 体系结构把冯诺依曼体系的五大组件进行了逐一的硬件实现 + 软件设计介绍,更为重要的是引入了诸多并行计算的内容,这是大部分教材中忽略或者内容较少的,会根据这个思路把并行相关的内容,结合 OpenMP, CUDA 和 Hadoop/Spark 整体融入到新书中,毕竟这是未来发展的趋势
还有这本书

<img src="http://www.cxuan.vip/image-20230118092932761.png" alt="image-20230118092932761" style="zoom:50%;" />
![](http://www.cxuan.vip/image-20230128210645920.png)

这本书又是讲啥的。

这本书是讲 RISC-V 指令集的,因为指令集的不同也区分了三个版本,三个版本???嗯,还有下面这个

<img src="http://www.cxuan.vip/image-20230118092948712.png" alt="image-20230118092948712" style="zoom:50%;" />
![](http://www.cxuan.vip/image-20230128210810425.png)

这本书是讲 ARM 指令集的。

Expand Down Expand Up @@ -86,7 +86,7 @@

C 编译器会接收其他操作并把其转换为`汇编语言`输出,汇编语言是机器级别的代码表示。我们之前介绍过,C 语言程序的执行过程分为下面这几步

![image-20230118093008167](http://www.cxuan.vip/image-20230118093008167.png)
![](http://www.cxuan.vip/image-20230118093008167.png)

下面我们更多的讨论都是基于汇编代码来讨论。

Expand Down Expand Up @@ -118,7 +118,7 @@ C 编译器会接收其他操作并把其转换为`汇编语言`输出,汇编

比如下面一个例子。

![image-20230118093022495](http://www.cxuan.vip/image-20230118093022495.png)
![](http://www.cxuan.vip/image-20230118093022495.png)

这是一段数值进行相加的操作,程序启动,在经过编译解析后会由操作系统把硬盘中的程序复制到内存中,示例中的程序是将 123 和 456 执行相加操作,并将结果输出到显示器上。由于使用机器语言难以描述,所以这是经过翻译后的结果,实际上每个指令和数据都可能分布在不同的地址上,但为了方便说明,把组成一条指令的内存和数据放在了一个内存地址上。

Expand All @@ -129,7 +129,7 @@ C 编译器会接收其他操作并把其转换为`汇编语言`输出,汇编

下面以条件分支为例来说明程序的执行过程(循环也很相似)

![image-20230118093032650](http://www.cxuan.vip/image-20230118093032650.png)
![](http://www.cxuan.vip/image-20230118093032650.png)

程序的开始过程和顺序流程是一样的,CPU 从 0100 处开始执行命令,在 0100 和 0101 都是顺序执行,PC 的值顺序+1,执行到 0102 地址的指令时,判断 0106 寄存器的数值大于 0,跳转(jump)到 0104 地址的指令,将数值输出到显示器中,然后结束程序,0103 的指令被跳过了,这就和我们程序中的 `if()` 判断是一样的,在不满足条件的情况下,指令会直接跳过。所以 PC 的执行过程也就没有直接+1,而是下一条指令的地址。

Expand All @@ -145,7 +145,7 @@ C 编译器会接收其他操作并把其转换为`汇编语言`输出,汇编

汇编代码需要经过 `汇编器` 编译后才产生二进制代码,这个二进制代码就是目标代码,然后由链接器将其连接起来运行。

![image-20230118093056675](http://www.cxuan.vip/image-20230118093056675.png)
![](http://www.cxuan.vip/image-20230118093056675.png)

汇编语言主要分为以下三类

Expand Down Expand Up @@ -177,7 +177,7 @@ CPU 是计算机的大脑,它也是整个计算机的核心,它也是执行
* 数据线
* 控制线

<img src="http://www.cxuan.vip/image-20230118094211136.png" alt="image-20230118094211136" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230118094211136.png"/>

CPU 与存储器之间的读写主要经过以下几步

Expand All @@ -199,7 +199,7 @@ CPU 与存储器之间的读写主要经过以下几步

通过我们上面的探讨,我们知道 CPU 通过`地址总线`来指定存储位置的,地址总线上能传送多少不同的信息,CPU 就可以对多少个存储单元进行寻址。

<img src="http://www.cxuan.vip/image-20230118094220678.png" alt="image-20230118094220678" style="zoom:50%;" />
![](http://www.cxuan.vip/image-20230128211458439.png)

上图中 CPU 和内存中间信息交换通过了 10 条地址总线,每一条线能够传递的数据都是 0 或 1 ,所以上图一次 CPU 和内存传递的数据是 2 的十次方。

Expand All @@ -219,7 +219,7 @@ CPU 与其他部件之间的控制是通过 `控制总线` 来完成的。有多

内存 IC 是一个完整的结构,它内部也有电源、地址信号、数据信号、控制信号和用于寻址的 IC 引脚来进行数据的读写。下面是一个虚拟的 IC 引脚示意图

![image-20230118094240801](http://www.cxuan.vip/image-20230118094240801.png)
![](http://www.cxuan.vip/image-20230118094240801.png)

图中 VCC 和 GND 表示电源,A0 - A9 是地址信号的引脚,D0 - D7 表示的是控制信号、RD 和 WR 都是好控制信号,我用不同的颜色进行了区分,将电源连接到 VCC 和 GND 后,就可以对其他引脚传递 0 和 1 的信号,大多数情况下,**+5V 表示1,0V 表示 0**

Expand All @@ -231,7 +231,7 @@ CPU 与其他部件之间的控制是通过 `控制总线` 来完成的。有多

下面是一次内存的读取过程。

![image-20230118094256309](http://www.cxuan.vip/image-20230118094256309.png)
![](http://www.cxuan.vip/image-20230118094256309.png)

来详细描述一下这个过程,假设我们要向内存 IC 中写入 1byte 的数据的话,它的过程是这样的:

Expand All @@ -243,3 +243,6 @@ CPU 与其他部件之间的控制是通过 `控制总线` 来完成的。有多

此篇文章我们主要探讨了指令集、指令集的分类,与汇编有关的硬件,总线都有哪些,分别的作用都是什么,然后我们以一次内存读取过程来连接一下 CPU 和内存的交互过程。

如果你在阅读文章的过程中发现错误和问题,请及时与我联系!

如果文章对你有帮助,希望小伙伴们三连走起!
60 changes: 31 additions & 29 deletions assembly/assembly02-register.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

认识寄存器之前,我们首先先来看一下 CPU 内部的构造。

<img src="http://www.cxuan.vip/image-20230118153331838.png" alt="image-20230118153331838" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230118153331838.png"/>

CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元和存储单元,这三部分由 CPU 内部总线连接起来。运算单元会和存储单元有很深的交流,比如运算单元计算完成后的数据会在存储单元中存储,存储单元中的数据会在运算单元中进行处理,在这个过程中,控制单元会负责对数据存储和数据处理进行控制。

Expand All @@ -48,7 +48,7 @@ CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元

寄存器是一块速度非常快的计算机内存,下面是现代计算机中具有存储功能的部件比对,可以看到,寄存器的速度是最快的,同时也是造价最高昂的。

![image-20230119071623979](http://www.cxuan.vip/image-20230119071623979.png)
![](http://www.cxuan.vip/image-20230119071623979.png)

我们以 intel 8086 处理器为例来进行探讨,8086 处理器是 x86 架构的前身。在 8086 后面又衍生出来了 8088 。

Expand All @@ -70,7 +70,7 @@ CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元

通用寄存器主要有四种 ,即 **AX、BX、CX、DX** 同样的,这四个寄存器也是 16 位的,能存放两个字节。 AX、BX、CX、DX 这四个寄存器一般用来存放数据,也被称为 `数据寄存器`。它们的结构如下

![image-20230119071717511](http://www.cxuan.vip/image-20230119071717511.png)
![](http://www.cxuan.vip/image-20230119071717511.png)

8086 CPU 的上一代寄存器是 8080 ,它是一类 8 位的 CPU,为了保证兼容性,8086 在 8080 上做了很小的修改,8086 中的通用寄存器 AX、BX、CX、DX 都可以独立使用两个 8 位寄存器来使用。

Expand All @@ -92,19 +92,19 @@ CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元

如下图所示。

![image-20230119071916082](http://www.cxuan.vip/image-20230119071916082.png)
![](http://www.cxuan.vip/image-20230119071916082.png)

合起来就是

<img src="https://s1.ax1x.com/2020/10/14/0I5v0H.png" alt="assembly02 005" border="0">
![](http://www.cxuan.vip/image-20230128215334857.png)

AX 的低位(0 - 7)位构成了 AL 寄存器,高 8 位(8 - 15)位构成了 AH 寄存器。AH 和 AL 寄存器是可以使用的 8 位寄存器,其他同理。

在认识了寄存器之后,我们通过一个示例来看一下数据的具体存储方式。

比如十进制数据 19 ,它在 16 位存储器中所存储的表示如下

![image-20230119072044268](http://www.cxuan.vip/image-20230119072044268.png)
![](http://www.cxuan.vip/image-20230119072044268.png)

寄存器的存储方式是先存储低位,如果低位满足不了就存储高位,如果低位能够满足,高位用 0 补全,在其他低位能满足的情况下,其余位也用 0 补全。

Expand Down Expand Up @@ -192,7 +192,7 @@ CPU 包含四个段寄存器,用作程序指令,数据或栈的基础位置

我们大家都知道, CPU 访问内存时,需要知道访问内存的具体地址,内存单元是内存的基本单位,每一个内存单元在内存中都有唯一的地址,这个地址即是 `物理地址`。而 CPU 和内存之间的交互有三条总线,即数据总线、控制总线和地址总线。

<img src="http://www.cxuan.vip/image-20230120105116070.png" alt="image-20230120105116070" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230120105116070.png" style="zoom:50%;" />

CPU 通过地址总线将物理地址送入存储器,那么 CPU 是如何形成的物理地址呢?这将是我们接下来的讨论重点。

Expand Down Expand Up @@ -222,7 +222,7 @@ cxuan 和你聊了这么久,你应该知道 8086 CPU 是 16 位的 CPU 了,

原来,8086 CPU 的内部采用两个 16 位地址合成的方式来传输一个 20 位的物理地址,如下图所示

<img src="http://www.cxuan.vip/image-20230120105203593.png" alt="image-20230120105203593" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230120105203593.png" style="zoom:50%;" />

叙述一下上图描述的过程

Expand All @@ -232,7 +232,7 @@ CPU 中相关组件提供两个地址:段地址和偏移地址,这两个地

下面是地址加法器的工作流程

![image-20230120123243525](http://www.cxuan.vip/image-20230120123243525.png)
![](http://www.cxuan.vip/image-20230120123243525.png)

其实段地址 * 16 ,就是左移 4 位。在上面的叙述中,物理地址 = 段地址 * 16 + 偏移地址,其实就是**基础地址 + 偏移地址 = 物理地址** 寻址模式的一种具体实现方案。基础地址其实就等于段地址 * 16。

Expand All @@ -248,7 +248,7 @@ CPU 中相关组件提供两个地址:段地址和偏移地址,这两个地

如图所示

![image-20230121085124315](http://www.cxuan.vip/image-20230121085124315.png)
![](http://www.cxuan.vip/image-20230121085124315.png)

这是两个 16 KB 的程序分别被装载进内存的示意图,可以看到,这两个程序的段地址的大小都是 16380。

Expand All @@ -266,35 +266,35 @@ CPU 中相关组件提供两个地址:段地址和偏移地址,这两个地
在 8086 CPU 中,由 `CS:IP` 指向的内容当作指令执行。如下图所示

![image-20230121090419136](http://www.cxuan.vip/image-20230121090419136.png)
![](http://www.cxuan.vip/image-20230121090419136.png)

说明一下上图

在 CPU 内部,由 CS、IP 提供的段地址和偏移地址,被送入加法器中被转换为物理地址,输入输出控制电路负责输入/输出数据,指令缓冲器负责缓冲指令,指令执行器负责执行指令。在内存中有一段连续存储的区域,区域内部存储的是机器码、外面是地址和汇编指令。

上面这幅图的段地址和偏移地址分别是 2000 和 0000,当这两个地址进入地址加法器后,会由地址加法器负责将这两个地址转换为物理地址。

![image-20230121090703510](http://www.cxuan.vip/image-20230121090703510.png)
![](http://www.cxuan.vip/image-20230121090703510.png)

然后地址加法器负责将指令输送到输入输出控制电路中

![image-20230121090710503](http://www.cxuan.vip/image-20230121090710503.png)
![](http://www.cxuan.vip/image-20230121090710503.png)

输入输出控制电路将 20 位的地址总线送到内存中。

![image-20230121090719845](http://www.cxuan.vip/image-20230121090719845.png)
![](http://www.cxuan.vip/image-20230121090719845.png)

然后取出对应的数据,也就是 **B8、23、01**,图中的 B8、BB 都是操作数。

![image-20230121090736384](http://www.cxuan.vip/image-20230121090736384.png)
![](http://www.cxuan.vip/image-20230121090736384.png)

控制输入/输出电路会将 B8 23 01 送入指令缓存器中。

![image-20230121090745556](http://www.cxuan.vip/image-20230121090745556.png)
![](http://www.cxuan.vip/image-20230121090745556.png)

此时这个指令就已经具备执行条件,此时 IP 也就是指令指针会自动增加。我们上面说到 IP 其实就是从 Code Segment 也就是 CS 处偏移的地址,也就是偏移地址。它会知道下一个需要读取指令的地址,如下图所示

![image-20230121090800285](http://www.cxuan.vip/image-20230121090800285.png)
![](http://www.cxuan.vip/image-20230121090800285.png)

在这之后,指令执行执行取出的 B8 23 01 这条指令。

Expand Down Expand Up @@ -346,7 +346,7 @@ mov al,[0]

栈的数据结构就是这样,你把书籍压入收纳箱的操作叫做`压入(push)`,你把书籍从收纳箱取出的操作叫做`弹出(pop)`,它的模型图大概是这样。

<img src="http://www.cxuan.vip/image-20230121091229010.png" alt="image-20230121091229010" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091229010.png" style="zoom:50%;" />


入栈相当于是增加操作,出栈相当于是删除操作,只不过叫法不一样。栈和内存不同,它不需要指定元素的地址。它的大概使用如下
Expand All @@ -365,7 +365,7 @@ l = pop();
在栈中,LIFO 方式表示栈的数组中所保存的最后面的数据(Last In)会被最先读取出来(First Out)。
<img src="http://www.cxuan.vip/image-20230121091242308.png" alt="image-20230121091242308" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091242308.png" style="zoom:50%;" />
#### 栈和 SS 寄存器
Expand All @@ -376,11 +376,11 @@ l = pop();
我这里首先有一个初始的栈,没有任何指令和数据。
<img src="http://www.cxuan.vip/image-20230121091513324.png" alt="image-20230121091513324" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091513324.png" style="zoom:50%;" />
然后我们向栈中 push 数据后,栈中数据如下
<img src="http://www.cxuan.vip/image-20230121091522188.png" alt="image-20230121091522188" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091522188.png" style="zoom:50%;" />
涉及的指令有
Expand All @@ -393,7 +393,7 @@ push ax
再向栈中 push 数据

<img src="http://www.cxuan.vip/image-20230121091544670.png" alt="image-20230121091544670" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091544670.png" style="zoom:50%;" />

其中涉及的指令有

Expand All @@ -404,7 +404,7 @@ push bx

现在栈中有两条数据,现在我们执行出栈操作

<img src="http://www.cxuan.vip/image-20230121091606666.png" alt="image-20230121091606666" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091606666.png" style="zoom:50%;" />

其中涉及的指令有

Expand All @@ -415,7 +415,7 @@ pop ax

再继续取出数据

<img src="http://www.cxuan.vip/image-20230121091617669.png" alt="image-20230121091617669" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121091617669.png" style="zoom:50%;" />

涉及的指令有

Expand All @@ -426,15 +426,15 @@ pop bx

完整的 push 和 pop 过程如下

![image-20230121091627168](http://www.cxuan.vip/image-20230121091627168.png)
![](http://www.cxuan.vip/image-20230121091627168.png)

现在 cxuan 问你一个问题,我们上面描述的是 10000H ~ 1000FH 这段空间来作为 push 和 pop 指令的存取单元。但是,你怎么知道这个栈单元就是 10000H ~ 1000FH 呢?也就是说,你如何选择指定的栈单元进行存取?

事实上,8086 CPU 有一组关于栈的寄存器 `SS``SP`。SS 是段寄存器,它存储的是栈的基础地址,而 SP 是栈指针,它存储的是偏移地址。在任意时刻,`SS:SP` 都指向栈顶。push 和 pop 指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。

现在,我们可以完整的描述一下 push 和 pop 过程了,如下图所示:

![image-20230121113435794](http://www.cxuan.vip/image-20230121113435794.png)
![](http://www.cxuan.vip/image-20230121113435794.png)

上面这个过程主要涉及到的关键变化如下。

Expand All @@ -452,20 +452,22 @@ pop bx

比如如下是一个栈顶越界的示意图:

![image-20230121113548387](http://www.cxuan.vip/image-20230121113548387.png)
![](http://www.cxuan.vip/image-20230121113548387.png)

第一开始,SS:SP 寄存器指向了栈顶,然后向栈空间 push 一定数量的元素后,SS:SP 位于栈空间顶部,此时再向栈空间内部 push 元素,就会出现栈顶越界问题。

栈顶越界是危险的,因为我们既然将一块区域空间安排为栈,那么在栈空间外部也可能存放了其他指令和数据,这些指令和数据有可能是其他程序的,所以如此操作会让计算机`懵逼`

我们希望 8086 CPU 能自己解决问题,毕竟 8086 CPU 已经是个成熟的 CPU 了,要学会自己解决问题了。

<img src="http://www.cxuan.vip/image-20230121113558718.png" alt="image-20230121113558718" style="zoom:50%;" />
<img src="http://www.cxuan.vip/image-20230121113558718.png" style="zoom:50%;" />

然而,这对于 8086 CPU 来说,这可能是它一辈子的 `夙愿` 了,真实情况是,8086 CPU 不会保证栈顶越界问题,也就是说 8086 CPU 只会告诉你栈顶在哪,并不会知道栈空间有多大,所以需要程序员自己手动去保证。。。

## 总结

这篇文章我大概带你了解了一下 8086 CPU 的基本结构,了解了一下常用的寄存器分类,8086 CPU 中物理地址的构造过程,以及什么是段和栈。

如果文章对你有帮助,欢迎各位三连走起!
如果你在阅读文章的过程中发现错误和问题,请及时与我联系!

如果文章对你有帮助,希望小伙伴们三连走起!
Loading

0 comments on commit 8f48068

Please sign in to comment.