精华内容
下载资源
问答
  • 本指南描述了32位x86汇编语言编程的几个基础知识,主要介绍寄存器和内存寻址。有几种不同的汇编语言可用于生成x86机器代码,本文使用标准的Intel语法来编写x86汇编代码。寄存器如图1所示,现代(即386及更高版本)x86...

    本指南描述了32位x86汇编语言编程的几个基础知识,主要介绍寄存器和内存寻址。
    有几种不同的汇编语言可用于生成x86机器代码,本文使用标准的Intel语法来编写x86汇编代码。

    寄存器

    如图1所示,现代(即386及更高版本)x86处理器有8个32位的通用寄存器。

    寄存器的名称大多是基于历史的命名而来。
    例如,EAX过去被称为累加器,因为它被大量用于算术运算;而ECX被称为计数器,因为它被用于保存循环索引。

    尽管大多数寄存器在现代指令集中已失去了其特殊用途,但是按照使用惯例,有两个寄存器仍然被保留用于特殊用途 — 堆栈指针(ESP)和基址指针(EBP)。

    对于EAX、EBX、ECX和EDX四个寄存器,支持以子寄存器的方式使用。
    例如,EAX的2个最低有效字节,可作为一个16位的寄存器AX来使用。AX的最低有效字节,可以用作一个8位的寄存器AL;而AX的最高有效字节,可以用作一个8位的寄存器AH。这些名称指的都是相同的物理寄存器。

    当将两个字节的数据存入DX时,这个更新操作会同时影响DH、DL和EDX的值。

    这些子寄存器主要是从过去的16位版本指令集保留下来的。
    但是,当处理小于32位的数据时(例如处理1个字节的ASCII字符),它们有时却很方便。

    使用汇编语言引用寄存器时,名称不区分大小写。例如,名称EAX和eax指的是同一个寄存器。

    5af8411441333fbf43c4f13ece7c04f3.png

    内存和寻址模式

    声明静态数据区

    在x86汇编中,使用特殊的汇编指令来声明静态数据区(类似于全局变量)。在声明数据的前面,需要加上 .DATA 指令。
    同时,可以使用DB、DW和DD来分别声明大小为1个字节、2个字节和4个字节的数据位置。

    声明的位置可以用名称标记,以便后续引用该位置 — 这类似于使用名称来定义一个变量,但是遵循的是一些较低级别的规则。
    例如,按照顺序声明的数据位置将位于内存中彼此相邻的位置。

    示例:

    .DATA       
    var DB 64 ; 声明一个字节,标记为位置var(用名称var来标记、引用该位置),该字节的值为64
    var2 DB ? ; 声明一个未初始化的字节,标记为位置var2。
    DB 10 ; 声明一个没有标记名称的字节,该字节的值为10。其位置为 var2 + 1
    X DW ? ; 声明一个2个字节的未初始化的值,标记为位置X。
    Y DD 30000 ; 声明一个4个字节的值,标记为位置Y,初始化值为30000

    在高级语言中,一个数组可以是多维的,并且可以通过索引进行访问。
    与高级语言不同,在x86汇编语言中的数组,只是在内存中连续分布的多个内存单元。
    要声明一个数组,只需要顺序的列出多个值,如下面的第一个示例所示。

    另外两个用于声明数组的常用方法,分别是使用DUP指令和字符串字面量。

    通过DUP指令,来通知汇编程序,按照给定的次数来复制指定的表达式。
    例如:

    4 DUP(2)

    等价于

    2, 2, 2, 2

    一些示例:

    Z           DD 1, 2, 3           ; 声明三个4字节的值,分别初始化为123。则位置Z的值为1,位置Z+4的值为2,位置Z+8的值为3
    bytes DB 10 DUP(?) ; 从位置bytes标记的地址开始,声明10个未初始化的字节
    arr DD 100 DUP(0) ; 从位置arr标记的地址开始,声明100个四字节的字,值都初始化为0
    str DB 'hello',0 ; 从位置str标记的地址开始,声明6个字节,初始化为字符串hello的ASCII字符值,和一个null(0)字节

    内存寻址

    现代的x86兼容处理器能够寻址多达2的32次方个字节的内存:内存地址为32位宽。
    在上面的示例中,我们使用标记来引用内存区域,这些标记实际上会被汇编器替换为32位的数值,用来指定内存中的地址。

    除了支持通过标记(即常量值)引用内存区域外,x86还提供了一种灵活的方案来计算和引用内存地址:最多可以将两个32位的寄存器和一个32位的带符号常量加在一起来计算内存地址。
    其中的一个寄存器,可以选择预乘以2、4或8。

    寻址模式可以和许多x86指令一起使用。
    下面我们举一些例子,使用mov指令在寄存器和内存之间移动数据。
    该指令有两个操作数:第一个是目标操作数,第二个是源操作数。

    mov指令使用地址计算的一些示例:

    mov eax, [ebx]              ; 将寄存器EBX的值对应的内存地址的4个字节,传入寄存器EAX
    mov [var], ebx ; 将寄存器EBX的值,传入内存地址 var 对应的4个字节(注意,var是一个32位常量值)
    mov eax, [esi-4] ; 将内存地址 ESI-44个字节,传入寄存器EAX
    mov [esi+eax], cl ; 将寄存器CL的值,传入内存地址为 ESI+EAX 对应的1个字节
    mov edx, [esi+4*ebx] ; 将内存地址 ESI+4*EBX 的4个字节的数据,传入寄存器EDX

    一些无效的地址计算示例:

    mov eax, [ebx-ecx]           ; 寄存器之间只能相加不能相减
    mov [eax+esi+edi], ebx ; 地址计算最多只能使用2个寄存器

    尺寸操作符

    一般来说,给定内存地址的数据项的预期大小,可以从汇编代码指令中推断出来。
    例如,在上面的所有指令中,内存区域的大小都可以从寄存器操作数的大小推断出来。

    当我们加载一个32位寄存器时,汇编程序可以推断出我们所引用的内存区域是4个字节宽。
    当我们将单字节寄存器的值存储到内存中时,汇编程序可以推断出我们希望引用的地址是指向内存中的一个字节。

    然而,在某些情况下,被引用的内存区域的大小是不明确的。

    例如有这样一条指令:

    mov [ebx], 2

    这条指令是否表示应该将2传入内存地址 EBX 对应的一个字节中?
    也许应该表示将32位的整数2,传入内存地址 EBX 开始的4个字节中。

    由于这两种情况都是有可能的,因此必须在指令中显式的告诉汇编程序哪一个是正确的。

    使用BYTE PTR、WORD PTR 和 DWORD PTR操作符,可以显式的指明内存单元的大小,这三个操作符分别表示1个字节、2个字节和4个字节的内存单元。

    例如:

    mov BYTE PTR [ebx], 2           ;2传入内存地址 EBX 的一个字节中
    mov WORD PTR [ebx], 2 ;16位的整数2,传入内存地址 EBX 开始的2个字节中
    mov DWORD PTR [ebx], 2 ;32位的整数2,传入内存地址 EBX 开始的4个字节中

    参考

    英文原文:http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

    Intel’s Pentium Manuals:https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html

    Intel x86 Instruction Set Reference:https://www.felixcloutier.com/x86/

    Guide to Using Assembly in Visual Studio .NET:http://www.cs.virginia.edu/~evans/cs216/guides/vsasm.html


    题图:www.cs.virginia.edu极客教程:996geek.com个人博客:binarylife.icu
    展开全文
  • CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。对...

    be505f524f59649d388c2cbf4ac911fe.png

    CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。

    • 对开发者说CPU最重要的器件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制

    • 不同CPU寄存器个数、结构不相同

    高速缓存

    iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.

    CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.

    问题:

    既然高速缓存这么牛x,为什么不把内存搞大点?其中有一个原因商业成本 等等

    数据地址寄存器


    数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。

    浮点和向量寄存器


    因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数

    • 浮点寄存器 64位: D0 – D31 32位: S0 – S31

    现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.

    • 向量寄存器 128位:V0-V31

    ARM64中


    • 64位: X0-X30, XZR(零寄存器)

    • 32位: W0-W30, WZR(零寄存器)

    注意:8086汇编一些特殊寄存器段寄存器 CS,DS,SS,ES这些段的基地址属于Intel CPU架构中(Intel也不在用这些段寄存器),ARM中不存在

    1.通用寄存器


    ARM64拥有有31个64位的通用寄存器 x0 到 x30,这些寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)x开头是64位寄存器,w开头是32位寄存器

    • 那么w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位.

    • 比如 w0 就是 x0的低32位!

    • f30e1bf3e72b8296ab5f2a0d8885f0f6.png

    • *通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算

    2.PC寄存器(program counter)


    • 为指令指针寄存器,它指示了CPU当前要读取指令的地址

    • 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息

    • CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义

    • CPU根据什么将内存中的信息看做指令?

      • CPU将pc指向的内存单元的内容看做指令

      • 如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过

    其中pc是指即将执行的指令(pc就是指令 读/写等)

    pc指向那里就是读那里,它读的是内存地址 (指令保存到高速缓存中)首先去高速缓存映射关系表中找,如果有就直接在高速读,如果没有把内存一堆copy到高速缓存中。

    如果把pc寄存器改了下一个即将执行 可以使用 register write pc 0x21233b2

    a2503f318c992a3b47b5c2c216d1ad38.png

    *CPU和内存交互

    2f57e923777e259a65c4b8bc7a953b57.png

    • CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间

    • 然后让X0寄存器与1相加:sub X0,1

    • 最后将值赋值给内存空间:mov 蓝色内存空间,X0

    移走后是读,读内存空间本数不改变。内存空间也可以是一个。

    3.bl指令(转移跳转指令)


    • CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令

    • ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值(#10:就是数字),比如

      • mov x0,#10、mov x1,#20

    • 但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能

    • ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令

    .text.global _A,_B _A:    mov x0,#0xa0    mov x1,#0x00    add x1, x0, #0x14    mov x0,x1    bl _B    mov x0,#0x0    ret _B:    add x0, x0, #0x10    ret

    在main中执行

    int A(); int main(){   A();}
    展开全文
  • 外部总线连接 CPU 各种部件,进行数据传输;内部总线负责 CPU 内部各种组件的数据处理。如何通俗易懂的介绍寄存器呢,小浩看到一个很通俗易懂的回答:寄存器就是你的口袋,身上只有那么几个,只装最常用或者马上要...

    今天小浩想给大家分享一下寄存器这个东西。我们知道,寄存器是 CPU 内部的构造,它主要用于信息的存储。除此之外,CPU 内部还有运算器,负责处理数据;控制器控制其他组件;外部总线连接 CPU 和各种部件,进行数据传输;内部总线负责 CPU 内部各种组件的数据处理。

    如何通俗易懂的介绍寄存器呢,小浩看到一个很通俗易懂的回答:

    • 寄存器就是你的口袋,身上只有那么几个,只装最常用或者马上要用的东西。
    • 内存就是你的背包。有时候拿点什么放到口袋里,有时候从口袋里拿出点东西放在背包里。
    • 辅存就是你家里的抽屉。可以放很多东西,但存取不方便。
    • 断电了就相当于你人没了,在家里复活,家里抽屉里东西还在,但包里口袋里的装备都爆没了。

    那为什么会出现寄存器?因为我们知道,程序在内存中装载,由 CPU 来运行,CPU 的主要职责就是用来处理数据。那么这个过程势必涉及到从存储器中读取和写入数据,因为它涉及通过控制总线发送数据请求并进入存储器存储单元,通过同一通道获取数据,这个过程非常的繁琐并且会涉及到大量的内存占用,而且有一些常用的内存页存在,其实是没有必要的,因此出现了寄存器,存储在 CPU 内部。

    认识寄存器

    寄存器的官方叫法有很多,Wiki 上面的叫法是 Processing Register, 也可以称为 CPU Register,计算机中经常有一个东西多种叫法的情况,反正你知道都说的是寄存器就可以了。

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

    ebb023940cbcb644880cc2324fe25f19.png

    CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元和存储单元,这三部分由 CPU 内部总线连接起来。

    几乎所有的冯·诺伊曼型计算机的 CPU,其工作都可以分为5个阶段:「取指令、指令译码、执行指令、访存取数、结果写回」

    • 取指令阶段是将内存中的指令读取到 CPU 中寄存器的过程,程序寄存器用于存储下一条指令所在的地址
    • 指令译码阶段,在取指令完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。
    • 执行指令阶段,译码完成后,就需要执行这一条指令了,此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。
    • 访问取数阶段,根据指令的需要,有可能需要从内存中提取数据,此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。
    • 结果写回阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据写回到 CPU 的内部寄存器中,以便被后续的指令快速地存取;

    计算机架构中的寄存器

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

    d491e1cc2cee6f3b4ecca3f22fd7ca42.png

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

    在 8086 CPU 中,地址总线达到 20 根,因此最大寻址能力是 2^20 次幂也就是 1MB 的寻址能力,8088 也是如此。

    在 8086 架构中,所有的内部寄存器、内部以及外部总线都是 16 位宽,可以存储两个字节,因为是完全的 16 位微处理器。8086 处理器有 14 个寄存器,每个寄存器都有一个特有的名称,即

    「AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES」

    这 14 个寄存器有可能进行具体的划分,按照功能可以分为三种

    • 通用寄存器
    • 控制寄存器
    • 段寄存器

    下面我们分别介绍一下这几种寄存器

    通用寄存器

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

    bd7b26c268d638179547fa38d4b5b53c.png

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

    在细节方面,AX、BX、CX、DX 可以再向下进行划分

    • AX(Accumulator Register) :累加寄存器,它主要用于输入/输出和大规模的指令运算。
    • BX(Base Register):基址寄存器,用来存储基础访问地址
    • CX(Count Register):计数寄存器,CX 寄存器在迭代的操作中会循环计数
    • DX(data Register):数据寄存器,它也用于输入/输出操作。它还与 AX 寄存器以及 DX 一起使用,用于涉及大数值的乘法和除法运算。

    这四种寄存器可以分为上半部分和下半部分,用作八个 8 位数据寄存器

    • 「AX 寄存器可以分为两个独立的 8 位的 AH 和 AL 寄存器;」
    • 「BX 寄存器可以分为两个独立的 8 位的 BH 和 BL 寄存器;」
    • 「CX 寄存器可以分为两个独立的 8 位的 CH 和 CL 寄存器;」
    • 「DX 寄存器可以分为两个独立的 8 位的 DH 和 DL 寄存器;」

    除了上面 AX、BX、CX、DX 寄存器以外,其他寄存器均不可以分为两个独立的 8 位寄存器

    如下图所示。

    7feb707bd64a1feb3732598a06602243.png

    合起来就是

    4cf6b579321c48e82cc77f364c7305d4.png

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

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

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

    1cbd75e376ab793092dbd706c6c99a60.png

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

    8086 CPU 可以一次存储两种类型的数据

    • 字节(byte):一个字节由 8 bit 组成,这是一种恒定不变的存储方式
    • 字(word):字是由指令集或处理器硬件作为单元处理的固定大小的数据,对于 intel 来说,一个字长就是两个字节,字是计算机一个非常重要的特征,针对不同的指令集架构来说,计算机一次处理的数据也是不同的。也就是说,针对不同指令集的机器,一次能处理不用的字长,有字、双字(32位)、四字(64位)等。

    AX 寄存器

    我们上面探讨过,AX 的另外一个名字叫做累加寄存器或者简称为累加器,其可以分为 2 个独立的 8 位寄存器 AH 和 AL;在编写汇编程序中,AX 寄存器可以说是使用频率最高的寄存器。

    下面是几段汇编代码

    mov ax,20   /* 将 20 送入寄存器 AX*/
    mov ah,80 /* 将 80 送入寄存器 AH*/
    add ax,10 /* 将寄存器 AX 中的数值加上 8 */

    这里注意下:上面代码中出现的是 ax、ah ,而注释中确是 AX、AH ,其实含义是一样的,不区分大小写。

    AX 相比于其他通用寄存器来说,有一点比较特殊,AX 具有一种特殊功能的使用,那就是使用 DIV 和 MUL 指令式使用。

    DIV 是 8086 CPU 中的除法指令。

    MUL 是 8086 CPU 中的乘法指令。

    BX 寄存器

    BX 被称为数据寄存器,即表明其能够暂存一般数据。同样为了适应以前的 8 位 CPU ,而可以将 BX 当做两个独立的 8 位寄存器使用,即有 BH 和 BL。BX 除了具有暂存数据的功能外,还用于 寻址,即寻找物理内存地址。BX 寄存器中存放的数据一般是用来作为偏移地址 使用的,因为偏移地址当然是在基址地址上的偏移了。偏移地址是在段寄存器中存储的,关于段寄存器的介绍,我们后面再说。

    CX 寄存器

    CX 也是数据寄存器,能够暂存一般性数据。同样为了适应以前的 8 位 CPU ,而可以将 CX 当做两个独立的 8 位寄存器使用,即有 CH 和 CL。除此之外,CX 也是有其专门的用途的,CX 中的 C 被翻译为 Counting 也就是计数器的功能。当在汇编指令中使用循环 LOOP 指令时,可以通过 CX 来指定需要循环的次数,每次执行循环 LOOP 时候,CPU 会做两件事

    • 一件事是计数器自动减 1

    • 还有一件就是判断 CX 中的值,如果 CX 中的值为 0 则会跳出循环,而继续执行循环下面的指令,

      当然如果 CX 中的值不为 0 ,则会继续执行循环中所指定的指令 。

    DX 寄存器

    DX 也是数据寄存器,能够暂存一般性数据。同样为了适应以前的 8 位 CPU ,DX 的用途其实在前面介绍 AX 寄存器时便已经有所介绍了,那就是支持 MUL 和 DIV 指令。同时也支持数值溢出等。

    段寄存器

    CPU 包含四个段寄存器,用作程序指令,数据或栈的基础位置。实际上,对 IBM PC 上所有内存的引用都包含一个段寄存器作为基本位置。

    段寄存器主要包含

    • CS(Code Segment) :代码寄存器,程序代码的基础位置
    • DS(Data Segment):数据寄存器,变量的基本位置
    • SS(Stack Segment):栈寄存器,栈的基础位置
    • ES(Extra Segment):其他寄存器,内存中变量的其他基本位置。

    索引寄存器

    索引寄存器主要包含段地址的偏移量,索引寄存器主要分为

    • BP(Base Pointer):基础指针,它是栈寄存器上的偏移量,用来定位栈上变量
    • SP(Stack Pointer): 栈指针,它是栈寄存器上的偏移量,用来定位栈顶
    • SI(Source Index): 变址寄存器,用来拷贝源字符串
    • DI(Destination Index): 目标变址寄存器,用来复制到目标字符串

    状态和控制寄存器

    就剩下两种寄存器还没聊了,这两种寄存器是指令指针寄存器和标志寄存器:

    • IP(Instruction Pointer):指令指针寄存器,它是从 Code Segment 代码寄存器处的偏移来存储执行的下一条指令
    • FLAG : Flag 寄存器用于存储当前进程的状态,这些状态有
      • 位置 (Direction):用于数据块的传输方向,是向上传输还是向下传输
      • 中断标志位 (Interrupt) :1 - 允许;0 - 禁止
      • 陷入位 (Trap) :确定每条指令执行完成后,CPU 是否应该停止。1 - 开启,0 - 关闭
      • 进位 (Carry) : 设置最后一个无符号算术运算是否带有进位
      • 溢出 (Overflow) : 设置最后一个有符号运算是否溢出
      • 符号 (Sign) : 如果最后一次算术运算为负,则设置  1 =负,0 =正
      • 零位 (Zero) : 如果最后一次算术运算结果为零,1 = 零
      • 辅助进位 (Aux Carry) :用于第三位到第四位的进位
      • 奇偶校验 (Parity) : 用于奇偶校验

    物理地址

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

    6d6578b000e6606b4cdc31f622204dd8.png

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

    现在,我们先来讨论一下和 8086 CPU 有关的结构问题。

    cxuan 和你聊了这么久,你应该知道 8086 CPU 是 16 位的 CPU 了,那么,什么是 16 位的 CPU 呢?

    你可能大致听过这个回答,16 位 CPU 指的是 CPU 一次能处理的数据是 16 位的,能回答这个问题代表你的底层还不错,但是不够全面,其实,16 位的 CPU 指的是

    • CPU 内部的运算器一次最多能处理 16 位的数据

    运算器其实就是 ALU,运算控制单元,它是 CPU 内部的三大核心器件之一,主要负责数据的运算。

    • 寄存器的最大宽度为 16 位

    这个寄存器的最大宽度值就是通用寄存器能处理的二进制数的最大位数

    • 寄存器和运算器之间的通路为 16 位

    这个指的是寄存器和运算器之间的总线,一次能传输 16 位的数据

    好了,现在你应该知道为什么叫做 16 位 CPU 了吧。

    在你知道上面这个问题的答案之后,我们下面就来聊一聊如何计算物理地址。

    8086 CPU 有 20 位地址总线,每一条总线都可以传输一位的地址,所以 8086 CPU 可以传送 20 位地址,也就是说,8086 CPU 可以达到 2^20 次幂的寻址能力,也就是 1MB。8086 CPU 又是 16 位的结构,从 8086 CPU 的结构看,它只能传输 16 位的地址,也就是 2^16 次幂也就是 64 KB,那么它如何达到 1MB 的寻址能力呢?

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

    124b9a81cf595906407c6a9dd4f4521f.png

    叙述一下上图描述的过程

    CPU 相关组件提供两个地址:段地址和偏移地址,这两个地址都是 16 位的,他们经由地址加法器变为 20 位的物理地址,这个地址即是输入输出控制电路传递给内存的物理地址,由此完成物理地址的转换。

    地址加法器采用 「物理地址 = 段地址 * 16 + 偏移地址」 的方法用段地址和偏移地址合成物理地址。

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

    ab5089ba7c9f0ca1d83490390ec6e9fa.png

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

    你可能不太清楚 的概念,下面我们就来探讨一下。

    什么是段

    段这个概念经常出现在操作系统中,比如在内存管理中,操作系统会把不同的数据分成 来存储,比如 「代码段、数据段、bss 段、rodata 段」 等。

    但是这些的划分并不是内存干的,cxuan 告诉你是谁干的,这其实是幕后 Boss CPU 搞的,内存当作了声讨的对象。

    其实,内存没有进行分段,分段完全是由 CPU 搞的,上面聊过的通过基础地址 + 偏移地址 = 物理地址的方式给出内存单元的物理地址,使得我们可以分段管理 CPU。

    如图所示

    355de54e129f08d5a76497488d139ec2.png

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

    这里需要注意一点, 8086 CPU 段地址的计算方式是段地址 * 16,所以,16 位的寻址能力是 2^16 次方,所以一个段的长度是 64 KB。

    段寄存器

    cxuan 在上面只是简单为你介绍了一下段寄存器的概念,介绍的有些浅,而且介绍段寄存器不介绍段也有「不知庐山真面目」的感觉,现在为你详细的介绍一下,相信看完上面的段的概念之后,段寄存器也是手到擒来。

    我们在合成物理地址的那张图提到了 相关部件 的概念,这个相关部件其实就是段寄存器,即 「CS、DS、SS、ES」 。8086 的 CPU 在访问内存时,由这四个寄存器提供内存单元的段地址。

    CS 寄存器

    要聊 CS 寄存器,那么 IP 寄存器是你绕不过去的曾经。CS 和 IP 都是 8086 CPU 非常重要的寄存器,它们指出了 CPU 当前需要读取指令的地址。

    CS 的全称是 Code Segment,即代码寄存器;而 IP 的全称是 Instruction Pointer ,即指令指针。现在知道这两个为什么一起出现了吧!

    在 8086 CPU 中,由 CS:IP 指向的内容当作指令执行。如下图所示

    02e1871a1cfffdc6c495ef6714f6e2e0.png

    说明一下上图

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

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

    ac0fabd148f113ffff9a54e986438c3c.png

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

    352b239373b06d03e20a7f72c40930b1.png

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

    4f8267dfe74cfaf8562c113551cf6343.png

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

    1cb2af406c22140b1127efdb0a5306ed.png

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

    b697b27a63d0a345706b88531660fe20.png

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

    51b13e0ec7ca039d7ad275487f314682.png

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

    然后下面再把 2000 和 0003 送到地址加法器中再进行后续指令的读取。后面的指令读取过程和我们上面探讨的如出一辙,这里 cxuan 就不再赘述啦。

    通过对上面的描述,我们能总结一下 8086 CPU 的工作过程

    • 段寄存器提供段地址和偏移地址给地址加法器
    • 由地址加法器计算出物理地址通过输入输出控制电路将物理地址送到内存中
    • 提取物理地址对应的指令,经由控制电路取回并送到指令缓存器中
    • IP 继续指向下一条指令的地址,同时指令执行器执行指令缓冲器中的指令

    什么是 Code Segment

    Code Segment 即代码段,它就是我们上面聊到就是 CS 寄存器中存储的基础地址,也就是段地址,段地址其本质上就是一组内存单元的地址,例如上面的 「mov ax,0123H 、mov bx, 0003H」。我们可以将长度为 N 的一组代码,存放在一组连续地址、其实地址为 16 的倍数的内存单元中,我们可以认为,这段内存就是用来存放代码的。

    DS 寄存器

    CPU 在读写一个内存单元的时候,需要知道这个内存单元的地址。在 8086 CPU 中,有一个 DS 寄存器,通常用来存放访问数据的段地址。如果你想要读取一个 10000H 的数据,你可能会需要下面这段代码

    mov bx,10000H
    mov ds,bx
    mov a1,[0]

    上面这三条指令就把 10000H 读取到了 a1 中。

    在上面汇编代码中,mov 指令有两种传送方式

    • 一种是把数据直接送入寄存器
    • 一种是将一个寄存器的内容送入另一个寄存器

    但是不仅仅如此,mov 指令还具有下面这几种表达方式

    描述举例
    mov 寄存器,数据比如:mov ax,8
    mov 寄存器,寄存器比如:mov ax,bx
    mov 寄存器,内存单元比如:mov ax,[0]
    mov 内存单元,寄存器比如:mov[0], ax
    mov 段寄存器,寄存器比如:mov ds,ax

    栈我相信大部分小伙伴已经非常熟悉了,是一种具有特殊的访问方式的存储空间。它的特殊性就在于,先进入栈的元素,最后才出去,也就是我们常说的 先入后出

    它就像一个大的收纳箱,你可以往里面放相同类型的东西,比如书,最先放进收纳箱的书在最下面,最后放进收纳箱的书在最上面,如果你想拿书的话, 必须从最上面开始取,否则是无法取出最下面的书籍的。

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

    1cc609b441c7748a0c2d0de1104c5aa2.png

    入栈相当于是增加操作,出栈相当于是删除操作,只不过叫法不一样。栈和内存不同,它不需要指定元素的地址。它的大概使用如下

    // 压入数据
    Push(123);
    Push(456);
    Push(789);

    // 弹出数据
    j = Pop();
    k = Pop();
    l = Pop();

    在栈中,LIFO 方式表示栈的数组中所保存的最后面的数据(Last In)会被最先读取出来(First Out)。

    9fb8f2e6b3193f6871248b5920b1d8e8.png

    栈和 SS 寄存器

    下面我们就通过一段汇编代码来描述一下栈的压入弹出的过程

    8086 CPU 提供入栈和出栈指令,最基本的两个是 PUSH(入栈)POP(出栈)。比如 push ax 会把 ax 寄存器中的数据压入栈中,pop ax 表示从栈顶取出数据送入 ax 寄存器中。

    这里注意一点:8086 CPU 中的入栈和出栈都是以字为单位进行的。

    我这里首先有一个初始的栈,没有任何指令和数据。

    06442d68013dcacfc3113a4016d0418f.png

    然后我们向栈中 push 数据后,栈中数据如下

    b71541f11fddc6001edc73d7522428cf.png

    涉及的指令有

    mov ax,2345H
    push ax

    注意,数据会用两个单元存放,高地址单元存放高 8 位地址,低地址单元存放低 8 位。

    再向栈中 push 数据

    c6b6857dafbf87981f22d1f11203a3e4.png

    其中涉及的指令有

    mov bx,0132H
    push bx

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

    9db39887636cafa913071590d8bf1790.png

    其中涉及的指令有

    pop ax
    /* ax = 0132H */

    再继续取出数据

    934b4eedf0a5165b1884fb8d30173ecf.png

    涉及的指令有

    pop bx
    /* bx = */

    完整的 push 和 pop 过程如下

    24a778255f9023f20afd662a365a4fe1.png

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

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

    现在,我们可以完整的描述一下 push 和 pop 过程了,下面 cxuan 就给你推导一下这个过程。

    349ba75d37814ea859775d23322277ba.png

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

    当使用 「PUSH」 指令向栈中压入 1 个字节单元时,SP = SP - 1;即栈顶元素会发生变化;

    而当使用 「PUSH」 指令向栈中压入 2 个字节的字单元时,SP = SP – 2 ;即栈顶元素也要发生变化;

    当使用 「POP」 指令从栈中弹出 1 个字节单元时, SP = SP + 1;即栈顶元素会发生变化;

    当使用 「POP」 指令从栈中弹出 2 个字节单元的字单元时, SP = SP + 2 ;即栈顶元素会发生变化;

    栈顶越界问题

    现在我们知道,8086 CPU 可以使用 SS 和 SP 指示栈顶的地址,并且提供 PUSH 和 POP 指令实现入栈和出栈,所以,你现在知道了如何能够找到栈顶位置,但是你如何能保证栈顶的位置不会越界呢?栈顶越界会产生什么影响呢?

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

    fa6be7b5f3010f46a78d8348e7a957ba.png

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

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

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

    fb01ada460669905e0e124bd2b9f0c2d.png

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

    a26e43b755b02717fe40a84921753141.png

    我把一千本开源电子书,以及我写的 140 多篇算法图解,进行了汇总。下方扫码回复【资源】就可以全部下载 !!!

    56f01e9139a32c910e46325790d9d496.png

    内容展示:

    fb6b3bda6b3adee11b24732b60463e77.png

    展开全文
  • 我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 starhttps://github.com/crisxuan/bestJavaer下面我们就来介绍一下关于寄存器...外部总线连接 CPU 各种部件,进行数据传输;内部总线负责 CPU 内部各种组件的...

    我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star
    https://github.com/crisxuan/bestJavaer

    下面我们就来介绍一下关于寄存器的相关内容。我们知道,寄存器是 CPU 内部的构造,它主要用于信息的存储。除此之外,CPU 内部还有运算器,负责处理数据;控制器控制其他组件;外部总线连接 CPU 和各种部件,进行数据传输;内部总线负责 CPU 内部各种组件的数据处理。

    那么对于我们所了解的汇编语言来说,我们的主要关注点就是 寄存器

    为什么会出现寄存器?因为我们知道,程序在内存中装载,由 CPU 来运行,CPU 的主要职责就是用来处理数据。那么这个过程势必涉及到从存储器中读取和写入数据,因为它涉及通过控制总线发送数据请求并进入存储器存储单元,通过同一通道获取数据,这个过程非常的繁琐并且会涉及到大量的内存占用,而且有一些常用的内存页存在,其实是没有必要的,因此出现了寄存器,存储在 CPU 内部。

    认识寄存器

    寄存器的官方叫法有很多,Wiki 上面的叫法是 Processing Register, 也可以称为 CPU Register,计算机中经常有一个东西多种叫法的情况,反正你知道都说的是寄存器就可以了。

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

    4b92845c0a43c84670deb8dbf8e4ba7a.png

    CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元和存储单元,这三部分由 CPU 内部总线连接起来。

    几乎所有的冯·诺伊曼型计算机的 CPU,其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数、结果写回

    • 取指令阶段是将内存中的指令读取到 CPU 中寄存器的过程,程序寄存器用于存储下一条指令所在的地址
    • 指令译码阶段,在取指令完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。
    • 执行指令阶段,译码完成后,就需要执行这一条指令了,此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。
    • 访问取数阶段,根据指令的需要,有可能需要从内存中提取数据,此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。
    • 结果写回阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据写回到 CPU 的内部寄存器中,以便被后续的指令快速地存取;

    计算机架构中的寄存器

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

    011bccef99157ba274255fb8be050725.png

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

    在 8086 CPU 中,地址总线达到 20 根,因此最大寻址能力是 2^20 次幂也就是 1MB 的寻址能力,8088 也是如此。

    在 8086 架构中,所有的内部寄存器、内部以及外部总线都是 16 位宽,可以存储两个字节,因为是完全的 16 位微处理器。8086 处理器有 14 个寄存器,每个寄存器都有一个特有的名称,即

    AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES

    这 14 个寄存器有可能进行具体的划分,按照功能可以分为三种

    • 通用寄存器
    • 控制寄存器
    • 段寄存器

    下面我们分别介绍一下这几种寄存器

    通用寄存器

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

    22859ac0e577a63eb5b367e9e0a37ab2.png

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

    在细节方面,AX、BX、CX、DX 可以再向下进行划分

    • AX(Accumulator Register) : 累加寄存器,它主要用于输入/输出和大规模的指令运算。
    • BX(Base Register):基址寄存器,用来存储基础访问地址
    • CX(Count Register):计数寄存器,CX 寄存器在迭代的操作中会循环计数
    • DX(data Register):数据寄存器,它也用于输入/输出操作。它还与 AX 寄存器以及 DX 一起使用,用于涉及大数值的乘法和除法运算。

    这四种寄存器可以分为上半部分和下半部分,用作八个 8 位数据寄存器

    • AX 寄存器可以分为两个独立的 8 位的 AH 和 AL 寄存器;
    • BX 寄存器可以分为两个独立的 8 位的 BH 和 BL 寄存器;
    • CX 寄存器可以分为两个独立的 8 位的 CH 和 CL 寄存器;
    • DX 寄存器可以分为两个独立的 8 位的 DH 和 DL 寄存器;

    除了上面 AX、BX、CX、DX 寄存器以外,其他寄存器均不可以分为两个独立的 8 位寄存器

    如下图所示。

    0fd8f133f026bc2fb632c04800cbd646.png

    合起来就是

    7b1de40a7872ad71b30368155b5412e8.png

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

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

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

    0dd42604f47844dae0ec013ab037683b.png

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

    8086 CPU 可以一次存储两种类型的数据

    • 字节(byte): 一个字节由 8 bit 组成,这是一种恒定不变的存储方式
    • 字(word):字是由指令集或处理器硬件作为单元处理的固定大小的数据,对于 intel 来说,一个字长就是两个字节,字是计算机一个非常重要的特征,针对不同的指令集架构来说,计算机一次处理的数据也是不同的。也就是说,针对不同指令集的机器,一次能处理不用的字长,有字、双字(32位)、四字(64位)等。

    AX 寄存器

    我们上面探讨过,AX 的另外一个名字叫做累加寄存器或者简称为累加器,其可以分为 2 个独立的 8 位寄存器 AH 和 AL;在编写汇编程序中,AX 寄存器可以说是使用频率最高的寄存器。

    下面是几段汇编代码

    mov ax,20/* 将 20 送入寄存器 AX*/mov ah,80   /* 将 80 送入寄存器 AH*/add ax,10  /* 将寄存器 AX 中的数值加上 8 */

    这里注意下:上面代码中出现的是 ax、ah ,而注释中确是 AX、AH ,其实含义是一样的,不区分大小写。

    AX 相比于其他通用寄存器来说,有一点比较特殊,AX 具有一种特殊功能的使用,那就是使用 DIV 和 MUL 指令式使用。

    DIV 是 8086 CPU 中的除法指令。

    MUL 是 8086 CPU 中的乘法指令。

    BX 寄存器

    BX 被称为数据寄存器,即表明其能够暂存一般数据。同样为了适应以前的 8 位 CPU ,而可以将 BX 当做两个独立的 8 位寄存器使用,即有 BH 和 BL。BX 除了具有暂存数据的功能外,还用于 寻址,即寻找物理内存地址。BX 寄存器中存放的数据一般是用来作为偏移地址 使用的,因为偏移地址当然是在基址地址上的偏移了。偏移地址是在段寄存器中存储的,关于段寄存器的介绍,我们后面再说。

    CX 寄存器

    CX 也是数据寄存器,能够暂存一般性数据。同样为了适应以前的 8 位 CPU ,而可以将 CX 当做两个独立的 8 位寄存器使用,即有 CH 和 CL。除此之外,CX 也是有其专门的用途的,CX 中的 C 被翻译为 Counting 也就是计数器的功能。当在汇编指令中使用循环 LOOP 指令时,可以通过 CX 来指定需要循环的次数,每次执行循环 LOOP 时候,CPU 会做两件事

    • 一件事是计数器自动减 1
    • 还有一件就是判断 CX 中的值,如果 CX 中的值为 0 则会跳出循环,而继续执行循环下面的指令,当然如果 CX 中的值不为 0 ,则会继续执行循环中所指定的指令 。

    DX 寄存器

    DX 也是数据寄存器,能够暂存一般性数据。同样为了适应以前的 8 位 CPU ,DX 的用途其实在前面介绍 AX 寄存器时便已经有所介绍了,那就是支持 MUL 和 DIV 指令。同时也支持数值溢出等。

    段寄存器

    CPU 包含四个段寄存器,用作程序指令,数据或栈的基础位置。实际上,对 IBM PC 上所有内存的引用都包含一个段寄存器作为基本位置。

    段寄存器主要包含

    • CS(Code Segment) : 代码寄存器,程序代码的基础位置
    • DS(Data Segment): 数据寄存器,变量的基本位置
    • SS(Stack Segment): 栈寄存器,栈的基础位置
    • ES(Extra Segment): 其他寄存器,内存中变量的其他基本位置。

    索引寄存器

    索引寄存器主要包含段地址的偏移量,索引寄存器主要分为

    • BP(Base Pointer):基础指针,它是栈寄存器上的偏移量,用来定位栈上变量
    • SP(Stack Pointer): 栈指针,它是栈寄存器上的偏移量,用来定位栈顶
    • SI(Source Index): 变址寄存器,用来拷贝源字符串
    • DI(Destination Index): 目标变址寄存器,用来复制到目标字符串

    状态和控制寄存器

    就剩下两种寄存器还没聊了,这两种寄存器是指令指针寄存器和标志寄存器:

    • IP(Instruction Pointer): 指令指针寄存器,它是从 Code Segment 代码寄存器处的偏移来存储执行的下一条指令
    • FLAG : Flag 寄存器用于存储当前进程的状态,这些状态有位置 (Direction):用于数据块的传输方向,是向上传输还是向下传输中断标志位 (Interrupt) :1 - 允许;0 - 禁止陷入位 (Trap) :确定每条指令执行完成后,CPU 是否应该停止。1 - 开启,0 - 关闭进位 (Carry) : 设置最后一个无符号算术运算是否带有进位溢出 (Overflow) : 设置最后一个有符号运算是否溢出符号 (Sign) : 如果最后一次算术运算为负,则设置 1 =负,0 =正零位 (Zero) : 如果最后一次算术运算结果为零,1 = 零辅助进位 (Aux Carry) :用于第三位到第四位的进位奇偶校验 (Parity) : 用于奇偶校验

    物理地址

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

    001182e937c2a3ccd722b1e354cd8d86.png

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

    现在,我们先来讨论一下和 8086 CPU 有关的结构问题。

    cxuan 和你聊了这么久,你应该知道 8086 CPU 是 16 位的 CPU 了,那么,什么是 16 位的 CPU 呢?

    你可能大致听过这个回答,16 位 CPU 指的是 CPU 一次能处理的数据是 16 位的,能回答这个问题代表你的底层还不错,但是不够全面,其实,16 位的 CPU 指的是

    • CPU 内部的运算器一次最多能处理 16 位的数据

    运算器其实就是 ALU,运算控制单元,它是 CPU 内部的三大核心器件之一,主要负责数据的运算。

    • 寄存器的最大宽度为 16 位

    这个寄存器的最大宽度值得就是通用寄存器能处理的二进制数的最大位数

    • 寄存器和运算器之间的通路为 16 位

    这个指的是寄存器和运算器之间的总线,一次能传输 16 位的数据

    好了,现在你应该知道为什么叫做 16 位 CPU 了吧。

    在你知道上面这个问题的答案之后,我们下面就来聊一聊如何计算物理地址。

    8086 CPU 有 20 位地址总线,每一条总线都可以传输一位的地址,所以 8086 CPU 可以传送 20 位地址,也就是说,8086 CPU 可以达到 2^20 次幂的寻址能力,也就是 1MB。8086 CPU 又是 16 位的结构,从 8086 CPU 的结构看,它只能传输 16 位的地址,也就是 2^16 次幂也就是 64 KB,那么它如何达到 1MB 的寻址能力呢?

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

    da9f377f4624a0cdab325eafdb39e559.png

    叙述一下上图描述的过程

    CPU 中相关组件提供两个地址:段地址和偏移地址,这两个地址都是 16 位的,他们经由地址加法器变为 20 位的物理地址,这个地址即是输入输出控制电路传递给内存的物理地址,由此完成物理地址的转换。

    地址加法器采用 物理地址 = 段地址 * 16 + 偏移地址 的方法用段地址和偏移地址合成物理地址。

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

    df32f62c3ba97daded699b99bc26fd94.png

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

    你可能不太清楚 的概念,下面我们就来探讨一下。

    什么是段

    段这个概念经常出现在操作系统中,比如在内存管理中,操作系统会把不同的数据分成 来存储,比如 代码段、数据段、bss 段、rodata 段 等。

    但是这些的划分并不是内存干的,cxuan 告诉你是谁干的,这其实是幕后 Boss CPU 搞的,内存当作了声讨的对象。

    其实,内存没有进行分段,分段完全是由 CPU 搞的,上面聊过的通过基础地址 + 偏移地址 = 物理地址的方式给出内存单元的物理地址,使得我们可以分段管理 CPU。

    如图所示

    fc7c62e3bd1c8c3de22c47088f4cc320.png

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

    这里需要注意一点, 8086 CPU 段地址的计算方式是段地址 * 16,所以,16 位的寻址能力是 2^16 次方,所以一个段的长度是 64 KB。

    段寄存器

    cxuan 在上面只是简单为你介绍了一下段寄存器的概念,介绍的有些浅,而且介绍段寄存器不介绍段也有不知庐山真面目的感觉,现在为你详细的介绍一下,相信看完上面的段的概念之后,段寄存器也是手到擒来。

    我们在合成物理地址的那张图提到了 相关部件 的概念,这个相关部件其实就是段寄存器,即 CS、DS、SS、ES 。8086 的 CPU 在访问内存时,由这四个寄存器提供内存单元的段地址。

    CS 寄存器

    要聊 CS 寄存器,那么 IP 寄存器是你绕不过去的曾经。CS 和 IP 都是 8086 CPU 非常重要的寄存器,它们指出了 CPU 当前需要读取指令的地址。

    CS 的全称是 Code Segment,即代码寄存器;而 IP 的全称是 Instruction Pointer ,即指令指针。现在知道这两个为什么一起出现了吧!

    在 8086 CPU 中,由 CS:IP 指向的内容当作指令执行。如下图所示

    3cb7a868147d82c829656a8d46d9acc9.png

    说明一下上图

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

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

    c546f7d954ca5fc0b7b018065f992631.png

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

    0a7b3242f8226d012f73b990458dd907.png

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

    d2e4189378b5a06b5e0ae31d055cef5e.png

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

    9d49f96c2ae3e7b7881b6c03e8746ee6.png

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

    c4d6f27201f2f72ec82faf64d60d48b2.png

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

    f3aeaaf3f6f97d9f7e360fe9bf578059.png

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

    然后下面再把 2000 和 0003 送到地址加法器中再进行后续指令的读取。后面的指令读取过程和我们上面探讨的如出一辙,这里 cxuan 就不再赘述啦。

    通过对上面的描述,我们能总结一下 8086 CPU 的工作过程

    • 段寄存器提供段地址和偏移地址给地址加法器
    • 由地址加法器计算出物理地址通过输入输出控制电路将物理地址送到内存中
    • 提取物理地址对应的指令,经由控制电路取回并送到指令缓存器中
    • IP 继续指向下一条指令的地址,同时指令执行器执行指令缓冲器中的指令

    什么是 Code Segment

    Code Segment 即代码段,它就是我们上面聊到就是 CS 寄存器中存储的基础地址,也就是段地址,段地址其本质上就是一组内存单元的地址,例如上面的 mov ax,0123H 、mov bx, 0003H。我们可以将长度为 N 的一组代码,存放在一组连续地址、其实地址为 16 的倍数的内存单元中,我们可以认为,这段内存就是用来存放代码的。

    DS 寄存器

    CPU 在读写一个内存单元的时候,需要知道这个内存单元的地址。在 8086 CPU 中,有一个 DS 寄存器,通常用来存放访问数据的段地址。如果你想要读取一个 10000H 的数据,你可能会需要下面这段代码

    mov bx,10000Hmov ds,bxmov a1,[0]

    上面这三条指令就把 10000H 读取到了 a1 中。

    在上面汇编代码中,mov 指令有两种传送方式

    • 一种是把数据直接送入寄存器
    • 一种是将一个寄存器的内容送入另一个寄存器

    但是不仅仅如此,mov 指令还具有下面这几种表达方式

    描述举例mov 寄存器,数据比如:mov ax,8mov 寄存器,寄存器比如:mov ax,bxmov 寄存器,内存单元比如:mov ax,[0]mov 内存单元,寄存器比如:mov[0], axmov 段寄存器,寄存器比如:mov ds,ax

    栈我相信大部分小伙伴已经非常熟悉了,是一种具有特殊的访问方式的存储空间。它的特殊性就在于,先进入栈的元素,最后才出去,也就是我们常说的 先入后出

    它就像一个大的收纳箱,你可以往里面放相同类型的东西,比如书,最先放进收纳箱的书在最下面,最后放进收纳箱的书在最上面,如果你想拿书的话, 必须从最上面开始取,否则是无法取出最下面的书籍的。

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

    add2314406c1e2816baf25854497ed77.png

    入栈相当于是增加操作,出栈相当于是删除操作,只不过叫法不一样。栈和内存不同,它不需要指定元素的地址。它的大概使用如下

    // 压入数据Push(123);Push(456);Push(789);// 弹出数据j = Pop();k = Pop();l = Pop();

    在栈中,LIFO 方式表示栈的数组中所保存的最后面的数据(Last In)会被最先读取出来(First Out)。

    0fa4321aed4f1998ad040938a59d6212.png

    栈和 SS 寄存器

    下面我们就通过一段汇编代码来描述一下栈的压入弹出的过程

    8086 CPU 提供入栈和出栈指令,最基本的两个是 PUSH(入栈)POP(出栈)。比如 push ax 会把 ax 寄存器中的数据压入栈中,pop ax 表示从栈顶取出数据送入 ax 寄存器中。

    这里注意一点:8086 CPU 中的入栈和出栈都是以字为单位进行的。

    我这里首先有一个初始的栈,没有任何指令和数据。

    814b2b0d224f932494018c32dac85d80.png

    然后我们向栈中 push 数据后,栈中数据如下

    3a42c65324ef01d4e547df7cd1a6d6b0.png

    涉及的指令有

    mov ax,2345Hpush ax

    注意,数据会用两个单元存放,高地址单元存放高 8 位地址,低地址单元存放低 8 位。

    再向栈中 push 数据

    a2ff16ba2f28b3c508d1fffaf46a5511.png

    其中涉及的指令有

    mov bx,0132Hpush bx

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

    c1820f4f00339c9d618ad1f1344aeeca.png

    其中涉及的指令有

    pop ax/* ax = 0132H */

    再继续取出数据

    bb9a67ae03dda34d704569d6e2423a90.png

    涉及的指令有

    pop bx/* bx = */

    完整的 push 和 pop 过程如下

    7f35e10ea7b78702bf192d64de595e98.png

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

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

    现在,我们可以完整的描述一下 push 和 pop 过程了,下面 cxuan 就给你推导一下这个过程。

    ecb6050a635cc7c19ef88a664e8d9da5.png

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

    当使用 PUSH 指令向栈中压入 1 个字节单元时,SP = SP - 1;即栈顶元素会发生变化;

    而当使用 PUSH 指令向栈中压入 2 个字节的字单元时,SP = SP – 2 ;即栈顶元素也要发生变化;

    当使用 POP 指令从栈中弹出 1 个字节单元时, SP = SP + 1;即栈顶元素会发生变化;

    当使用 POP 指令从栈中弹出 2 个字节单元的字单元时, SP = SP + 2 ;即栈顶元素会发生变化;

    栈顶越界问题

    现在我们知道,8086 CPU 可以使用 SS 和 SP 指示栈顶的地址,并且提供 PUSH 和 POP 指令实现入栈和出栈,所以,你现在知道了如何能够找到栈顶位置,但是你如何能保证栈顶的位置不会越界呢?栈顶越界会产生什么影响呢?

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

    421bee4f6c3ef9fca615c153ed08e756.png

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

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

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

    3578c48ea4fde2447ed2d6d699875e17.png

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

    另外,我输出了 六本 PDF,已免费提供下载,如下所示

    7fe22b7f85c853f9c0772e63eb05b9db.png
    展开全文
  • 作 者:程序员cxuan原文链接:https://mp.weixin.qq.com/s/Okpdf90VpbuO7XfI3N3WgQ下面我们就来介绍一下关于寄存器的...外部总线连接 CPU 各种部件,进行数据传输;内部总线负责 CPU 内部各种组件的数据处理。那...
  • 如果我们将两个4G内存插入内存...一部分原因是4G以下有Memory map IO(mmio)空间PCIe的配置空间,另一个原因是Interleaving会打撒内存地址到各个Channel、DIMM甚至是Rankbank上。今天我们就一起来了解一下x86系统...
  • 本文翻译并修改自:http://duartes.org/gustavo/blog/ 微信公众号:技术原理君本文是Intel兼容计算机(x86)的内存与保护系列文章的第一篇,延续了启动引导系列文章的主题,进一步分析操作系统内核的工作流程。...
  • 但只要有存储器,并且存储器中存储着程序数据,计算机就可以工作。本文中将存储器分为两类。以下分类名称非标准名称只是小编自己的叫法,大家领会精神就好。一类存储器是指CPU可以直接访问的存储器,最为我们熟知...
  • IO寄存器和传统内存对比

    千次阅读 2013-10-02 20:48:46
     尽管硬件寄存器和内存之间有很强的相似性, 程序员在存取 I/O 寄存器的时候还是要格外小心,避免被CPU(或者编译器)优化所迷惑, 因为它可能修改你期待的 I/O 行为.  I/O 寄存器和 RAM 一个主要的不同是:I/O 操作...
  • GDB寄存器和内存

    2017-02-25 19:08:44
    1. 查看寄存器 (gdb) i r (gdb) i r a # 查看所有寄存器(包括浮点、多媒体) (gdb) i r esp ...(gdb) x /wx 0x80040000 # 以16进制显示指定地址处的数据 (gdb) x /8x $esp (gdb) x /16x $esp
  • 缓存 内存 寄存器 共享内存 首先,寄存器的速度大于缓存的速度大于内存的速度。...共享内存属于一级缓存,每个SM中一级缓存共享内存一个64KB的内存段,速度高于全局内存低于寄存器。全局内存会受高低端
  • 存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程称为存储器映射,如果再分配一个地址就叫重映射(具体地址分配参考芯片数据手册及中文参考手册)。 二、寄存器及...
  • 第 6 集:寄存器和内存 本集重点是 Memory (存储 / 内存 两种含义) 03:30 存 1 位 (Gated Latch - 锁存器) 04:48 存 8 位 (Register - 寄存器) 05:46 16x16 的矩阵存 256 位 数据选择器/多路复用器 ...
  • 一、DS和[address]CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中内存地址有段地址和偏移地址组成。DS(数据寄存器)中通常存放要访问数据的段地址。比如要读取1000H单元的内容,可以用下面...
  • 寄存器是有限存储容量的高速存储部件,可用来存储指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)程序计数器(PC)。 存储器在CPU外,一般只硬盘、U盘等在切断电源后可以保存资料的...
  • 寄存器是CPU的内部组成单元,是CPU运算时取指令数据的地方,速度很快,寄存器可以用来暂存指令、数据和地址。在CPU中,通常有通用寄存器,如指令寄存器IR;特殊功能寄存器,如程序计数器PC、sp等 Cache Cache :即...
  • 1625-5 王子昂 总结...-rip(直接输入为显示所有寄存器、当前cs:ip指向的地址、命令)(加上寄存器名称则为显示当前内容及修改内容) -d 查看内存中的内容 用法:-d 1000:0 -d 1000:0 0(不加偏移地址结尾则全
  • 寄存器地址

    2019-10-08 22:30:06
    其实没啥大的关联,只不过寄存器地址和内存的地址都是在CPU的寻址空间中。简单的说寄存器的地址指向的就是寄存器,跟其他什么什么的没有关系。访问寄存器的方式和访问内存的方式一样一样的。不过要注意一下,这种...
  • 内存和寄存器

    2019-09-26 21:57:56
    先定义的变量放在高地址,后定义的变量放在低地址,在数组里面低地址存放地下标数据。 转载于:https://www.cnblogs.com/vhwuln/p/3521181.html...
  • 地址和寄存器

    2020-09-16 10:45:28
    即段地址和偏移地址 段地址随意定义只要找到物理地址就行 2、段寄存器寄存器就是提供段地址的 8086CPU有4个段寄存器 CS、DS、SS、ES当8086要访问内存时,由这4个段寄存器提供内存单元的段地址 CS和IP是.
  • 当我输入一个数据的时候,软件就会为我们在内存中开辟一个地址,以往内存表中存放数据,如这里就是往这个地址开始写入数据:0040339C。我们对内存表的高位低位的理解就可以想做这里就是一张白纸。我们把每个空格都...
  • 寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,...
  • 这次,我们将添加读写寄存器和内存的功能,这将使我们能够使用我们的程序计数器、观察状态和改变程序的行为。 系列文章索引 随着后面文章的发布,这些链接会逐渐生效。 准备环境 断点 寄存器和内存 Elves 和 ...
  • 汇编语言入门:寄存器和内存访问

    千次阅读 2018-02-14 12:38:27
    内存中字的存储 高位高地址,低位对应低地址…… 下图,在0地址处开始存放20000(4E20H) 看图,“0地址单元中存放的字节型数据是20H”,“0地址字单元中存放的字型数据是4E20H”,“2地址单元中存放的字节型...
  • 寄存器内存访问)

    2018-01-12 21:37:45
    title:寄存器内存访问) date: tags: categories: IT ...8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。 move al,[0] 这里使用move指令将一个内存单元中的内容送入一个寄存器。[0]表示内存

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,160
精华内容 1,664
关键字:

寄存器地址和内存地址