机器语言: 一串二进制数。转变为电信号就可以控制计算机活动。
汇编语言: 汇编指令 + 伪指令 + 其它符号
- 汇编指令: 机器语言助记符,和机器语言一一对应
注意: 机器型号不一样,所对应的汇编指令集也不一样
- 伪指令/其它符号: 由编译器识别/执行,没有对应的机器语言
存储单元: 一个存储单元,可以存储1B的数据
三类信息: CPU进行数据读写,需要进行三类信息的交互
1. 地址信息: 数据所在地址
2. 数据信息: 数据
3. 控制信息: 读 or 写
读写流程: 得到地址,发出控制,找到数据
总线: 地址线 + 数据线 + 控制线
1. 地址总线: 宽度表示CPU寻址能力(CPU位数)。假设宽度为N,那么最多可以寻找 ( A 2 1 ) N ({A^1_2})^N (A21)N个内存单元(一个内存单元1B )
注意: 一个64位需要满足三条件(不满足的话,就是假64位):64位CPU + 64位操作系统 + 64位软件
2. 数据总线: 宽度表示CPU一次能读写的数据大小。一根线一次传1b,8根线一次传1B。
3. 控制总线: 宽度表示CPU最多能控制的外部器材的数量。
存储器: 随机存储器RAM
(随机存储器,接口卡上的),只读存储器ROM
(刷了BIOS的)
- 随机存储器: 断电丢失数据,如主存(内存)
- BIOS: 标准输入输出系统。用来检测硬件,加载操作系统的。
补充: 往显卡RAM中写入数据,就会显示在显示器中。
注意: 运行程序的主体是CPU,所以,我们必须站在CPU的角度思考问题。
补充: 每个物理存储器,占用一个地址段(区间)
注意: 不同的计算机内存地址分配是不同的,《汇编语言(王爽)》(三版)使用的是Intel8086
。
CPU: 由运算器、控制器、寄存器 等组成,依靠内部总线 相连(连接主板上外部存储器的是外部总线 )
扩展: 可以看看《程序是怎样跑起来的》这本入门书
8086寄存器: 8086的寄存器都是16位(可存放两字节)。有以下14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW
AX、BX、CX、DX 称为通用寄存器
注意: 8086上一代CPU的寄存器都是8位的,为了向下兼容,8086CUP上的AX、BX、CX、DX都可以拆成两个可独立使用的8位寄存器来用。
比如,AX 分为AH 和 AL (其中H 是高, L 是低的意思),如果要向下兼容,就使用AL ,AH 全部填0。
字: 一个字 = 2B,一个字的高位字节存放在寄存器高八位,低位字节存放在低八位
补充: 阅读二进制不方便,所以我们一般写作16进制。4个二进制数就是一个十六进制。为了区分不同进制,二进制后加B,十六进制加H,八进制加O。
注意: 汇编指令不区分大小写
汇编指令 | CPU操作 | 对应高级语言 |
---|---|---|
mov ax, 7 | 把7存入ax | ax = 7 |
add ax, bx | 把ax + bx的结果存入ax | ax += bx |
练习题2.1 :
注意: 进行运算时,位数须一致。比如8位到8位,16位到16位。
物理地址: CPU认为,所有存储器是一个整体,每个存储单元都有其唯一地址,这个唯一地址 被称为物理地址
16位结构CPU: 具有以下特征
一个疑问? 8086有20位地址总线,寻址能力为1M。可是它是16位系统,只能传送16位地址,寻址能力只有64k。那么怎么能达到1M的寻址能力呢?
物 理 地 址 = 段 地 址 ∗ 16 + 偏 移 地 址 物理地址 = 段地址*16 + 偏移地址 物理地址=段地址∗16+偏移地址
偏移地址为16位,16位的寻址能力为64K,所以一个段的最大长度为64K。
CPU可以通过不同的段地址和偏移地址到达同一个物理地址,这就是条条大路通罗马
补充: 数据A13C6H在8086中可以表述为A000:13C6单元中;也可以表述为在A000段中的13C6单元中。
8086CPU有4个段寄存器:
CS | DS | SS | ES | |
---|---|---|---|---|
英文 | code statement | data … | stack… | extra… |
解释 | 代码段(寄存器) | 数据… | 堆栈… | 附加段 |
CS
和IP
是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
注意: 此IP非彼IP
附加: 8086按下电源键的一刻,CS被设置为FFFFH,IP被设置为0000H。所以,FFFF0H单元中的指令是8086开机后执行的第一条指令。
转移指令: 可以改变CS、IP的值
用法: jmp 段地址:偏移地址
只修改IP的值: jmp 某一合法寄存器
例子: jmp ax
相当于: mov IP, ax
功能: 把ax的值赋值给IP
对于8086来说,可以根据需要将一组内存定义为一个段(地址连续、起始地址为16的倍数、长度为N(N <= 64k))。这段内存是用来存放代码的,从而定义了一个代码段。
注意: CPU并不认代码段,只认CS、IP。所以要让CS:IP指向代码段第一行的首地址。
任意时刻,CPU将CS、IP指向的内存地址当做指令执行
补充: sub ax, bx
含义: ax -= bx
四次:
最后IP = ax = 0000H
- r 查看/修改 寄存器
- a 以汇编格式输入命
- d 查看内存中的内容
- e 改写内存中的内容
- u 将机器指令转变为汇编指令
- d 段地址:偏移地址 查看从物理地址开始的内存
- u 查看汇编代码
- t 执行命令CS/IP
注意: 内存区域的值是ASCII值
ASCII码 | 值 | 16进制 |
---|---|---|
48 | 0 | 30 |
50 | 2 | 32 |
47 | / | 2f |
注意: 0号是低地址单元。两个16进制数是一个字(2字节)
8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址。
mov可以将一个内存单元中的数据送入一个寄存器:
mov
寄存器名, [偏移地址]mov ax, [0]
注意: 8086cpu有一个缺陷(硬件问题),不能直接把值赋给DS,需要靠一个通用寄存器转手
mov ax, 1000H
mov DS, ax
CS指向的段就是代码段,DS指向的段就是数据段
注意:
mov ax, 1000[0]
不会报错,但不会进行赋值。这个可能和机器型号有关,我用的是DOSBox。
指令 | ax | bx | CS | IP | DS |
---|---|---|---|---|---|
初始 | 0 | 0 | 2000H | 0 | 1000H |
mov ax, 6622H | 6622H | 3 | |||
jmp 0ff0:0100 | 0ff0 | 0100 | |||
变形 | 1000 | 0 | |||
mov ax, 2000H | 2000H | 3 | |||
mov ds, ax | 2000H | 5 | 2000H | ||
mov ax, [0008] | C389H | 8 | |||
mov ax, [0002] | EA66H | B |
数据和程序没区别,都是一堆二进制数。在CS段中就是指令,在DS段中就是数据。
栈: stack
,一种具有特殊的访问方式的存储空间,LIFO
先进后出。
用处: 用处非常广泛,介绍一种最开始发明栈的原因。一个程序,如果只能从上至下进行,那么功能就太差了。为了复用性,需要函数 ,有一个问题就是,怎么回去?怎么回到调用处?于是发明了栈,把调用处的地址入栈,执行完函数后,再调用return(汇编语言指令都是三个字的,不叫这名,这是《程序是怎样跑起来的》上面的说法),把栈中的地址弹给CS/IP。
注意: 把一段内存,当做什么来使用,是程序员的看法。CPU只认数据。
push ax
把ax
中的数据入栈
pop ax
把栈中的数据弹入ax
注意: 8086CPU入栈/出栈是以字为单位的(ax是16进制,当然是字,而不是字节)
补充:push CS
/pop CS
,push和pop可以对段寄存器 进行直接操作
补充:push [1]
/pop [2]
,push和pop可以对内存进行直接操作
SS: 存放栈顶的栈地址
SP: 存放栈顶的偏移地址
SS:SP: 指向栈顶的元素
内存空间上面是低地址,下面是高地址。数据一个个存放进去,栈顶就向上移动,所以入栈又称为压栈 。那么栈顶向下移动,SS:SP
又时刻指向栈顶,是怎么做到的呢?靠得是SP进行变化。
push ax: SP -= 2。减号表示向上移动;pop指令正好相反。
补充: 当栈是空的时候,不存在栈顶。SP指向
SS:0
的下面一个内存单元(更大的内存单元)
注意: pop后,原指向的内存中的数据不会消失,只是SP向下移动了。当再次push时,会对原存在的数据进行覆盖。(格式化不是真正的删除,只有覆盖重写才是真的删除,这就是数据恢复的原理)
栈顶越界: 当栈满时仍push
,当栈空时仍pop
,那么操作就会脱离我们规划的栈空间。这就叫栈顶越界 (危险)
注意:
java
等语言有溢出检测,而像C
/C++
之类的就没有
解决方法: 弄两个寄存器,一个记录栈下限,一个记录栈上限。然而,这种CPU
目前并不存在。我们只能自己小心不越界,合理预估所需栈的大小(在C中,程序员可以操作Heap堆
,这是由一堆零散空间通过逻辑连接的。要注意使用后,及时释放内存)
问题3.7: 注意,sp
是可以直接赋值的,初始赋值为下一个内存单元,即000fH
的下一个0010H
。
问题3.8: 注意,清零的步骤,我使用的是mov ax, 0
这样的机器码是3个字节。书上的sub ax, ax
的机器码是2个字节(现在资源没有那么匮乏,不需要纠结这么一小点空间)。系统很喜欢用xor ax, ax
异或,如果ax等于ax就将ax清零。
问题3.10: 重要的事情说N遍,CPU只认二进制数据!
补充: mov指令涉及一个操作。push/pop涉及两个操作,push先改变SP的值,再放入值;pop先取出值,再改变SP
注意: 因为入栈是改变SP,所以栈的最大变化范围为0~ffff。当栈满,会由于SP溢出,从而又回到栈底部开始循环。
问题3.11: SP = 0000。当有程序入栈时,SP -= 2,会得到fffe。也就是利用了数据溢出 的原理
编译: 高级语言 -> 机器语言
反编译: 机器语言 -> 高级语言
汇编: 汇编语言 -> 机器语言
反汇编: 机器语言 -> 汇编语言
- d 段地址:偏移地址
可以查看内存单元的数据。
那么必须有一个寄存器来记录这个段地址,这个寄存器是DS(DS的值在操作前后,不会改变)。
D更多用法:
SS
时,下一条指令也会紧跟着执行 (一般情况下,t只能执行一条命令,这是个特例,涉及中断机制 ,以后再学)实验任务1:
实验任务2:: SP = 10H,-2就变成e,再-2就变成c。(王爽老师说理解这里是很有悟性,我倒觉得没有理解就该回去复习了,显而易见到我不觉得这三个问题了)