汇编语言是二进制指令的文本形式,与指令是一一对应的关系。
计算机组成
总线
总线分为地址总线、数据总线、控制总线。
- CPU通过地址总线指定存储单元,地址总线的宽度(条数)决定了可寻址的存储单元的大小。宽度为n的地址总线对应的寻址空间为2^n B。如8086地址总线为20,寻址能力1MB。
- CPU与内存或其它器件之间的数据传送是通过数据总线进行的。数据总线的宽度决定了CPU与外界数据的传输速度。如8086数据总线宽度16,一次可传输2B的数据。
- CPU通过控制总线对外部器件进行控制。
CPU要想进行数据读写,需要通过三类信息进行交互:
- 存储单元的地址(地址信息)
- 器件的选择,读或写命令(控制信息)
- 读或写的数据(数据信息)
如汇编指令:
mov al,[3]
,含义为从3号单元读取数据送入寄存器al。地址总线发送地址,控制总线传输读信号,数据线传回3号单元内的数据。存储器
将各类存储器(RAM、ROM)看作一个逻辑存储器进行统一编址,每个物理存储器在这个逻辑存储器上占一段地址空间。CPU在这段地址空间上读写数据,实际上是在对应的物理存储器中读写数据。
以8086为例:
寄存器
8086CPU有14个寄存器,都是16位的:
- 通用寄存器
- AX
- BX
- CX
- DX
- 变址寄存器
- SI
- DI
- 指针寄存器
- SP
- BP
- 指令指针寄存器
- IP
- 段寄存器
- CS
- SS
- DS
- ES
- 标志寄存器
- PSW
其中通用寄存器又可以分为两个独立的8位寄存器使用。如AX可以分为AH和AL,分别表示AX的高8位和低8位。
常见的32位CPU、64位CPU指的是字长(word size),也就是寄存器大小。如32位CPU寄存器大小是4字节;8086是16位CPU,字长是16bit,一个字(word)可以存在一个16位寄存器中,这个字的高位字节存在这个寄存器的高8位寄存器,低位存在低8位寄存器。
物理地址
8086有20位地址总线,可传送20位地址,寻址能力为1M,但因为是16位CPU,内部处理、传输的地址也是16位地址,寻址能力只有64KB。因此,需要用两个16位的地址(段地址*16+偏移地址),合成一个物理地址。
内存模型
堆 Heap
程序运行过程中,对于动态内存占用的请求,如新建对象、使用malloc命令等,系统会从预先分配好的内存中划出一部分给用户(从低位地址向高位地址划分)。这种因为用户主动请求而划分出的内存区域叫堆。
堆的一个重要特点就是不会自动消失,必须手动释放或由垃圾回收机制回收。
栈 Stack
系统执行函数时会在内存里面建立一个帧(frame),函数中所有内部变量保存在帧内。函数执行结束,帧会被回收,释放所有内部变量,不再占用空间。
如果函数中调用了其它函数,系统也会为其新建一个帧。该函数运行结束,系统会回到原函数中断的位置继续执行。通过这种机制就实现了函数的层层调用,且每一层都能使用自己的本地变量。一般来说调用栈有多少层就有多少帧。
栈是由内存区域结束地址开始,从高位向低位分配。
CPU指令
如下c语言程序example.c:
int add_a_and_b(int a, int b) { return a + b; } int main() { return add_a_and_b(2, 3); }
使用gcc -S 转换为汇编后简化的example.s如下:
_add_a_and_b: push %ebx mov %eax, [%esp+8] mov %ebx, [%esp+12] add %eax, %ebx pop %ebx ret _main: push 3 push 2 call _add_a_and_b add %esp, 8 ret
其中每行是CPU执行的一次打操作。如:
push %ebx
push是CPU指令,%ebx是指令用到的运算子。每个CPU指令可以有零个或多个运算子。
程序由_main标签开始执行,系统在栈上为main建立一个帧,并将栈指向的地址写入ESP寄存器。
push指令
push 3 push 2
用于将运算子放入栈,这里是将3和2写入main帧。
push指令包含了一个前置操作,它会先取出ESP寄存器里面的地址,将其减去4个字节,然后将新地址写入ESP寄存器。两次操作累计减去8字节。
call指令
call _add_a_and_b
表示调用add_a_and_b函数,并为该函数建立一个新的帧。
push %ebx
将EBX寄存器中的值,写入_add_a_and_b帧。使用该寄存器,将里面的值暂存,用完后写回EBX。
此时,push指令累计减去12字节。
mov指令
用于将一个值写入寄存器
mov %eax, [%esp+8] mov %ebx, [%esp+12]
先将ESP寄存器里面地址加8个字节,得到新的地址,然后按该地址在栈中取出数据2,再将2写入EAX。加12字节,重复该过程取出3写入EBX。
add指令
把两个运算子相加,并将结果写入第一个运算子。
add %eax, %ebx
EAX的值2加上EBX的值3,得到结果5并写回EAX。
pop指令
用于取出栈顶的值,并写入运算子指定的位置。
pop %ebx
取出EBX原始值,写回EBX。
执行一个后置操作,将ESP寄存器地址加4,即回收4个字节。
ret指令
用于终止当前函数执行,将运行权交还上层函数。当前函数的帧被回收。
add %esp, 8
将ESP寄存器里面的地址手动加上8字节,再写回ESP。
因为ESP寄存器中是栈的写入开始地址,前面pop已经回收4字节,这里回收8字节,等于全部回收。