汇编语言学习笔记(二)寄存器
简介
上文所说的总线,相对于CPU自身而言,属于外部总线。这些外部总线将CPU与外部芯片串联起来。
其内部也有类似结构(运算器/控制器/寄存器/内部总线),组成一个完整的CPU。
- 运算器进行计算处理
- 寄存器进行数据存储
- 控制器控制内部芯片
- 内部总线串联内部芯片
不同CPU,寄存器的数量与结构不尽相同。
8086,80386,x86,x86-64 CPU寄存器位宽分别为16,32,32,64。
8086共有14个寄存器,分别是
- 通用寄存器
AX: Accumulator Register
BX: Base Register
CX: Count Register
DX: Data Register - 指针和索引寄存器
SP: Stack Pointer
BP: Base Pointer
SI: Source Index
DI: Destination Index - 段寄存器
CS: Code Segment
DS: Data Segment
SS: Stack Segment
ES: Extra Segment - 指令指针和标志寄存器
IP: Instruction Pointer
FLAGS: Flags Register
通用寄存器
通用寄存器通常用来存放常规数据
8086通用寄存器:AX,BX,CX,DX
以8086CPU为例。16的位宽,代表可以存储16bit/2b的数据。
同时出于兼容考虑,可以一次性处理两种尺寸的数据
- 字节,byte
- 字,word 分别存储在高8位与低8位中
CPU的结构
8086是16位结构的CPU,同时也说明了它的结构特性
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度也是16位
- 寄存器与运算器之间的内部总线为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+偏移量=物理地址
- CPU内部提供两个16位的地址,一个被称为段地址,一个被称为偏移地址
- 两个地址通过内部总线送入地址加法器
- 合成为一个20位的物理地址
- 通过内部总线送入输入输出控制电路
- 输入输出控制电路将20位物理地址送上外部地址总线
- 20位物理地址被地址总线传输到内存
举个例子
我们要将123C8 这个17位的内存地址传出去
可以分解为段地址1230,偏移量00C8. 段地址向左偏移1位(因为是16进制,所以要x16,同理可得,10进制x10,8进制x8,2进制x2)
段地址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都只会被当作指令的段地址和段地址偏移
眼见为实
在x86-64架构中,CS寄存器已经被简化,不再显示。RIP寄存器代替了IP寄存器
每次代码执行,RIP寄存器都更新偏移量
内存的角度看待寄存器
汉字占2个字节,而内存最小单位是1字节。因此存储一个字需要2个连续的内存单元来存放。
因此提出字单元概念:即一个存放16bit的内存单元,由两个地址连续的内存单元组成,任何两个连续地址的内存单元,N和N+1都可以组成一个字单元
比如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都有栈的设计,其主要原因在于实现代码的复用
栈从高到低增长的历史因素
计算机设计早期,为了提高内存利用率。"栈"与"堆"是相连的,它们之间有一个间隔,防止访问违例。
内存地址的增长的自然规律是从低到高,这样就会出现两个选择,低堆高栈,或者高栈低堆
那么应该如何选择呢?
由于栈空间是固定的,而堆空间是不固定的。那么从理性角度出发,我们是否应该先创建大小固定的栈空间,再创建大小不固定的堆空间?
这样的话,堆空间跟随自然规律,由低向高增长。栈空间因为一开始就分配好了固定的大小,它无法再向高地址增加了(再向高地址增加就访问到堆空间了),只能反规律,由高向低增长。
因此,早期的计算机科学家最终选择了栈在顶部,堆的底部的设计
栈寄存器
8086CPU中,有两个寄存器,栈顶的段地址放在SS寄存器中,偏移地址放在SP中。
任意时刻,SS:SP指向栈顶元素
入栈时,栈定从高地址向低地址方向增长。当栈空时,SS:SP指向栈空间最高地址单元的下一个单元,不为空时SS:SP指向栈中的第一个元素
push/pop 命令执行步骤
- 先移动SP,栈指针
- 再向SP指向的字单元写入数据
简单来说,就是任意时刻,SS:SP都代表栈顶元素,如果没有元素。那么就只能指向栈底的下一个单元。
一个细节,当pop出栈后,只是SP地址增加。本身内存单元并不变化,直到新的数据入栈。覆盖旧内存单元