叫我安不理

汇编语言学习笔记(二)寄存器

简介

上文所说的总线,相对于CPU自身而言,属于外部总线。这些外部总线将CPU与外部芯片串联起来。
其内部也有类似结构(运算器/控制器/寄存器/内部总线),组成一个完整的CPU。

  1. 运算器进行计算处理
  2. 寄存器进行数据存储
  3. 控制器控制内部芯片
  4. 内部总线串联内部芯片

不同CPU,寄存器的数量与结构不尽相同。
8086,80386,x86,x86-64 CPU寄存器位宽分别为16,32,32,64。

8086共有14个寄存器,分别是

  1. 通用寄存器
    AX: Accumulator Register
    BX: Base Register
    CX: Count Register
    DX: Data Register
  2. 指针和索引寄存器
    SP: Stack Pointer
    BP: Base Pointer
    SI: Source Index
    DI: Destination Index
  3. 段寄存器
    CS: Code Segment
    DS: Data Segment
    SS: Stack Segment
    ES: Extra Segment
  4. 指令指针和标志寄存器
    IP: Instruction Pointer
    FLAGS: Flags Register

通用寄存器

通用寄存器通常用来存放常规数据
8086通用寄存器:AX,BX,CX,DX

以8086CPU为例。16的位宽,代表可以存储16bit/2b的数据。
同时出于兼容考虑,可以一次性处理两种尺寸的数据

  1. 字节,byte
  2. 字,word 分别存储在高8位与低8位中
    image

CPU的结构

8086是16位结构的CPU,同时也说明了它的结构特性

  1. 运算器一次最多可以处理16位的数据
  2. 寄存器的最大宽度也是16位
  3. 寄存器与运算器之间的内部总线为16位
    简单来说,在CPU内部,能够一次性处理,传输,存储的最大长度是16位。

x86,x86-64 内部宽度分别为32bit与64bit

CPU如何物理寻址

思考一个问题,如果一个16位结构的CPU,它的内部/外部总线为20根(20位).
CPU内部本身的寻址能力是2^16 =64kb,但总线寻址能力为2^20 =1024kb。
两者差异如此大,如何做到兼容呢?

地址加法器

CPU内部采用16位两个地址,合成成一个32位的地址。最终来兼容20位的总线地址
段地址x16+偏移量=物理地址

  1. CPU内部提供两个16位的地址,一个被称为段地址,一个被称为偏移地址
  2. 两个地址通过内部总线送入地址加法器
  3. 合成为一个20位的物理地址
  4. 通过内部总线送入输入输出控制电路
  5. 输入输出控制电路将20位物理地址送上外部地址总线
  6. 20位物理地址被地址总线传输到内存

image

举个例子

我们要将123C8 这个17位的内存地址传出去
可以分解为段地址1230,偏移量00C8. 段地址向左偏移1位(因为是16进制,所以要x16,同理可得,10进制x10,8进制x8,2进制x2)
image

段地址X16有个更常用的说法,是左移4位。指的是二进制位。

段寄存器

8086CPU有四个段寄存器CS, DS, SS, ES,X86还多了FS,GS
当CPU要访问内存时,由段寄存器提供内存的段地址

CS与IP寄存器

物理寻址的两个最关键寄存器,CS(Code Segment)为代码段寄存器,IP为指令指针寄存器.
当内存访问移动时,IP=IP+所读取指令的长度。CSx16+IP = 下一条指令。

指令和数据在内存中都是二进制信息,没有区别。 那么CPU根据什么判断是指令呢?答:是否被CS/IP指向过。
任何时候CS/IP都只会被当作指令的段地址和段地址偏移

眼见为实

image
image
image

在x86-64架构中,CS寄存器已经被简化,不再显示。RIP寄存器代替了IP寄存器
每次代码执行,RIP寄存器都更新偏移量

内存的角度看待寄存器

汉字占2个字节,而内存最小单位是1字节。因此存储一个字需要2个连续的内存单元来存放。
因此提出字单元概念:即一个存放16bit的内存单元,由两个地址连续的内存单元组成,任何两个连续地址的内存单元,N和N+1都可以组成一个字单元
image

比如index=0,1,2,3可以分别组合成4E20,124E,0012。只要是连续地址。

DS寄存器

8086CPU中的DS(Data Segment)寄存器,通常用来存放要访问数据的段地址,(CS寄存器是要访问指令的段地址)
比如要读取10000H单元的内容,可以如下表示
mov ax,1000H
mov ds,bx
mov al,[0]

[...]表示一个内存单元,里面的数据代表着单元的偏移地址(类似IP寄存器),但只有偏移地址是不能组成一个完整的物理地址的,这是CPU帮我们做的简化,自动取DS中的数据为段地址,可以翻译为mov al,bx+[0]

当代CPU都有栈的设计,其主要原因在于实现代码的复用

栈从高到低增长的历史因素

计算机设计早期,为了提高内存利用率。"栈"与"堆"是相连的,它们之间有一个间隔,防止访问违例。
内存地址的增长的自然规律是从低到高,这样就会出现两个选择,低堆高栈,或者高栈低堆
image

那么应该如何选择呢?
由于栈空间是固定的,而堆空间是不固定的。那么从理性角度出发,我们是否应该先创建大小固定的栈空间,再创建大小不固定的堆空间?
这样的话,堆空间跟随自然规律,由低向高增长。栈空间因为一开始就分配好了固定的大小,它无法再向高地址增加了(再向高地址增加就访问到堆空间了),只能反规律,由高向低增长。
因此,早期的计算机科学家最终选择了栈在顶部,堆的底部的设计

栈寄存器

8086CPU中,有两个寄存器,栈顶的段地址放在SS寄存器中,偏移地址放在SP中。
任意时刻,SS:SP指向栈顶元素
入栈时,栈定从高地址向低地址方向增长。当栈空时,SS:SP指向栈空间最高地址单元的下一个单元,不为空时SS:SP指向栈中的第一个元素
push/pop 命令执行步骤

  1. 先移动SP,栈指针
  2. 再向SP指向的字单元写入数据
    简单来说,就是任意时刻,SS:SP都代表栈顶元素,如果没有元素。那么就只能指向栈底的下一个单元。

一个细节,当pop出栈后,只是SP地址增加。本身内存单元并不变化,直到新的数据入栈。覆盖旧内存单元

posted on 2024-10-22 10:43  叫我安不理  阅读(85)  评论(0编辑  收藏  举报

导航