Airband 125

汇编复习记录 under prj31

基于 三个一工程 第一阶段的复习笔记

代码已上传至 Github,欢迎勘误(?(你看看有人理你吗


Unit 1 基础知识

1.1 机器语言

高低电平驱动各元件工作

来源:机器指令(复杂,不便修改)

1.2 汇编语言的产生

冯诺依曼五大结构:运算器 + 控制器 = CPU,存储,输入系统,输出系统

汇编语言 -编译器→ 机器语言

编译器的工作过程:编译器识别数据,转换成机器码放到另一个内存中,此时并不运行汇编的内容

汇编对计算机的操作是 原子性 的(汇编指令和机器码一一对应)体现高级语言和汇编的编译方式不同

语言在编译时不一定经由汇编,由编译器的编译特性决定

1.3 汇编语言的组成

汇编指令:有对应机器码 (核心)

伪指令、其他符号:无对应机器码,由编译器识别、执行

1.4 存储器

存放指令和数据

= 内存

1.5 指令和数据

在计算机中无区别,均为二进制信息,可以实时转化

1.6 存储单元

存储器 -划分→ 存储单元

一个存储器可以存储 128 个 byte(0-127) 从零开始编号

信息最小单位:bit/位 没有独立地址 8bit共享一个地址

存储最小单位:byte/字节

1.7 CPU对存储器的读写

1.8 地址总线

1.9 数据总线

1.10 控制总线

CPU 想要进行数据的读写,必须和外部器件(芯片)进行三类信息的交互:

传递途径:总线(导线集合)

外部总线:地址总线(表示 2^n 个不同的数据,寻址能力),控制总线,数据总线(单次传输 n 位数据

CPU 工作过程:地址 → 控制信息 → 传送数据

8086CPU 的数据总线宽度为 16,地址总线为 20 根,寻址能力为 1mb

1.11 内存地址空间

内存:

内存地址空间:一个 CPU 可寻址的内存单元的集合,受地址总线宽度的限制

1.12 主板

主板: CPU,存储器,外围芯片组,扩展插槽(RAM 内存条,各类接口卡)

通过总线相连

1.13 接口卡

CPU ← 总线 - 接口卡 → 外设

1.14 各类存储器芯片

存储器:

1.15 内存地址空间

CPU 操作存储器时将它们都当做内存对待

Unit 2 寄存器

2.1 通用寄存器

典型 CPU 的组成

靠内部总线相连

2.5 16 位结构的 CPU

16 位结构 / 16 位机 / 字长为 16 位 =

8086 是 16 位机,能够一次性处理、传输、暂时存储的信息的最大长度为 16 位

2.6 8086CPU 给出物理地址的方法

物理地址(20 位) = 段地址(16 位) * 16(二进制左移 4 位) + 偏移地址(16 位)

8086CPU 读写内存的过程:

  1. CPU 中的相关部件提供段地址、偏移地址

  2. 两个 16 位地址 - 内部总线 → 地址加法器

  3. 地址加法器 → 一个 20 位地址

  4. 20 位地址 - 内部总线 → 输入输出控制电路

  5. 输入输出控制电路 → 地址总线

  6. 地址总线 → 存储器

2.8 段的概念

内存并没有分段,段的划分来自于 CPU

一个段的最大长度为 64kb;仅用偏移地址来寻址最多可寻 64kb 个内存单元

可以用不同的段地址和偏移地址形成同一个物理地址

2.10 CS 和 IP

8086CPU 的工作过程:

  1. 把 CS、IP 放入地址加法器得到物理地址

  2. 地址加法器将地址送入输入输出控制电路

  3. 输入输出电路将地址送上地址总线

  4. CS:IP 处的机器指令通过数据总线被送入输入输出控制电路

  5. 输入输出控制电路将机器指令送入指令缓冲器

  6. IP自加

  7. 数据送入执行控制器,执行指令

  8. 回到步骤 1

8086 的段

我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。定义一段内存的用途,关键在于 CPU 中寄存器的设置,即 CS、IP、SS、SP、DS 的指向

8086 的寄存器:14 个

8086 常用指令:(更多信息参见书 p.286)

8086 debug.exe 总结

R

D

E

U

T

A

Q

q 退出 debug

G

P

Unit 3 寄存器(内存访问)

3.1 内存中字的存储

CPU 中,用 16 位 reg 存储一个字。高 8 位存放高位字节,低 8 位存放低位字节。

字单元:存放一个字型数据(16 位)的内存单元,由两个地址连续的内存单元组成。起始地址为 N 的字单元简称 N 地址字单元

3.6 栈

基本的栈操作:入栈、出栈

栈的操作规则:LIFO(后进先出)

3.7 CPU 提供的栈机制

入栈 - push,出栈 - pop,以字为单位 进行

栈顶指针:SS:SP 指向栈顶元素

栈空时,SS:SP 指向 栈空间最高地址单元的下一个单元

push reg 的执行过程:

  1. SP = SP - 2

  2. 将 reg 中的内容送入 SS:SP 指向的内存单元

入栈时,栈顶从高地址向低地址方向增长

pop reg 的执行过程:

  1. 将 SS:SP 指向的内存单元处的内容送入 reg 中

  2. SP = SP + 2

3.8 栈顶超界的问题

发生栈顶超界的情况:

  1. 栈满时使用 push 入栈

  2. 栈空时使用 pop 出栈

然而 8086CPU 并不保证我们对栈的操作不会超界,所以要自己注意

恢复数据时,出栈的顺序要和入栈的顺序相反

Unit 4 第一个程序

4.1 一个源程序从写出到执行的过程

  1. 编写汇编源程序

    结果:产生了一个存储源程序的文本文件(.asm)

  2. 使用汇编语言编译程序对源程序进行编译链接

    • 编译:产生目标文件(.obj)

    • 连接:生成可在操作系统中直接运行的可执行文件(.exe)

      • 可执行文件包含两部分内容:

        1. 程序(汇编指令翻译过来的机器码)和数据(源程序中定义的)

        2. 相关描述信息

  3. 在操作系统中,执行可执行文件中的程序

    操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关初始化(eg. CS:IP 指向第一条要执行的指令)

    操作系统管理内存

    然后由 CPU 执行程序

4.2 源程序

伪指令:由编译器执行

源程序 & 程序:

标号:

程序返回:

程序错误

4.3 编辑源程序

用 edit,编辑程序后存为 xxx.asm

4.4 编译 && 4.5 连接

过程翻书吧 - -

连接的作用:

  1. 当源程序很大时,可以将它分为多个源程序文件来编译 → 模块化

  2. 将程序调用的库文件和该程序生成的目标文件连接到一起

  3. 目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息

4.6 以简化的方式进行编译和链接

masm projname;
link projname;

4.8 谁将可执行文件中的程序装载进入内存并使它运行?

关于 shell:

汇编程序从写出到执行的过程:

  1. Edit 下编程 → .asm

  2. masm 编译 → .obj

  3. link 连接 → .exe

  4. command 根据文件名找到可执行文件

  5. command 将可执行文件中的程序加载入内存,并设置 CS:IP 指向程序入口

  6. CPU 运行程序

  7. 程序返回到 command

4.9 程序执行过程的跟踪

debug projname.exe

DOS 中 .exe 文件中的程序的加载过程:

  1. 找到一段 起始地址为 SA:0000 的 容量足够空闲 内存区

  2. SA:0 - SA+10H:0 创建 程序段前缀(PSP)

    DOS 利用 PSP 和被加载程序进行通信

  3. 在 SA+10H 后面将程序装入

    PSP 和程序虽然物理地址连续,却有不同的段地址

  4. DS = SA;

    初始化其他 reg;

    CS:IP = SA+10H:0

程序加载后,cx 中存放程序长度,ds 中存放程序所在内存区的段地址(偏移地址为 0)

Unit 5 [BX] 和 loop 指令

内存单元的描述:地址、长度(类型)

(m)

idata: 表示常量

5.3 在 debug 中跟踪用 loop 指令实现的循环程序

默认数制:debug - 16进制;源程序 - 10进制

在汇编程序中,数据不能以字母开头(因为可能和 reg 中的字母搞混),故要在以字母开头的数据前加 0

5.4 Debug 和 masm 对指令的不同处理

对于 [idata]:

5.6 段前缀

用于显式地指明内存单元的段地址,eg. ds:cs:ss:es:

5.7 一段安全的空间

0:200 ~ 0:2ff (是中断向量表中的空闲部分)

Unit 6 包含多个段的程序

程序获得所需空间的方法:

6.1 在代码段中使用数据

数据的定义 / 内存空间的开辟:

在源程序中指明程序入口:用标号

assume cs:code
code segment
    dw ...
start:
    ...
code ends
end start

end 可以通知编译器程序的入口在什么地方

机制:在编译连接后,由 end … 指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中,当程序被加载入内存之后,加载者读到该地址,设置 CS:IP

6.2 在代码段中使用栈

定义方法同数据,但要在程序中将 SS:SP 指向栈底

6.3 将数据、代码、栈放入不同的段

动机:

不同的段要有不同的段名

实验 5 编写、调试具有多个段的程序

如果一个段中定义的数据不是 16byte 的整数倍,会用 0 凑整

把代码段放在最前面就可以不用标号指明程序入口啦

Unit 7 更灵活地定位内存地址的方法

7.3 以字符形式给出的数据

'...' 的方式指明数据是以字符形式给出的,编译器将把它们转化为相对应的 ASCII 码

7.4 大小写转换的问题

将一个字母的第五位置 0,它必将变为大写字母

将一个字母的第五位置 1,它必将变为小写字母

7.10 不同的寻址方式的灵活应用

方式 介绍
[idata] 直接定位
[bx] 间接定位
[bx + idata] 在一个起始地址的基础上间接定位(类似数组)
[bx + si] 用于二重循环
[bx + si + idata] 数组 + 二重循环

用 loop 进行多重循环时 cx 的设置:每次开始内层循环时保存外层循环中 cx 的值,用栈实现

一般来说,在需要暂存数据的时候,我们都应该使用栈

Unit 8 数据处理的两个基本问题

8.1 bx si di bp

[...] 中进行内存单元的寻址时,可接受的形式:

默认 sreg = ds   ←   bx       si
                          +        +   idata
默认 sreg = ss   ←   bp       di

8.2 机器指令处理的数据在什么地方

指令执行前一刻,机器指令将要处理的数据所在的位置:

因为 CPU 可以 直接读写 以上三个地方的数据

8.4 寻址方式

寻址方式小结

8.5 指令要处理的数据有多长

8086CPU 可以处理两种尺寸的数据:byte 和 word

指明数据尺寸的方法:

  1. 通过 reg

  2. 用操作符 X ptr 显式声明,X = word / byte

  3. 其他方法(有些指令默认了访问的是 word 还是 byte,比如 push

Unit 9 转移指令的原理

可以修改 IP,或同时修改 CS 和 IP 的指令称为转移指令

8086 的转移距离

转移时要给出两种信息:

8086 的转移指令

9.1 操作符 offset

offset

seg

9.3 依据位移进行转移的 jmp 指令

机器码中不包括转移的目的地址,而是包含位移

jmp x 标号 的功能:(IP) = (IP) + 位移

9.4 && 9.5 && 9.6 转移的目的地址在指令 && reg && 内存中的 jmp 指令

机器码中包含转移的目的地址,是字型就只改 IP,是双字型就同时改 IP 和 CS

9.7 jcxz 指令

jcxz 标号 的功能:

用了很久才发现 jcxz 的直译是 jump CX zero 呀哈哈哈哈好暴力!

9.8 loop 指令

操作:

  1. (CX)–;

  2. 当 (CX) != 0 时,(IP) = (IP) + 8 位位移,位移细节同 jcxz

    当 (CX) == 0 时,什么也不做(程序向下执行)

9.9 根据位移进行转移的意义

方便了程序段在内存中的 浮动装配

9.10 编译器对转移位移超界的检测

如果超界,编译器会报错

实验 9 根据材料编程

80 x 25 彩色字符模式显示缓冲区的结构:

Unit 10 CALL 和 RET 指令

这章就没什么好写啦,实现子程序嘛……

记得保护寄存器呀……

Unit 11 标志寄存器

标志寄存器(flag)的作用:

8086CPU 的标志寄存器有 16 位,其中存储的信息被称为程序状态字(PSW)

11.6 adc 指令

加法可以分两步运算:

  1. 低位相加

  2. 高位相加再加上低位相加产生的进位值

所以 adc 的目的是突破 16 位机运算位数的限制,实现任意大数据的加法运算,sbb 的思想是类似的

11.8 cmp 指令

cmp m, n

是否相等:取决于 zf

无符号数大小比较:取决于 cf

有符号数大小比较:取决于 sf,of

实际结果:

溢出:

Unit 12 内中断

中断:CPU 不再接着(刚执行完的指令)向下执行,而是转去处理特殊信息

12.1 内中断的产生

中断源:产生中断信息的事件,即中断信息的来源

中断类型码:字节型数据,标识中断源

中断源 中断类型码
除法错误 0
单步执行 1
执行 into 指令 4
执行 int n n

12.2 中断处理程序

中断程序:用来处理中断信息的程序

12.3 中断向量表

中断向量:中断处理程序的入口地址

中断向量表:中断处理程序入口地址的列表,在 8086CPU 中,放在 0000:0000 - 0000:03ff 中,高地址字存放段地址,低地址字存放偏移地址

12.4 中断过程

  1. 取得中断类型码 N

  2. pushf

  3. TF = 0, IF = 0

  4. push CS

  5. push IP

  6. (IP) = (N * 4), (CS) = (N * 4 + 2)

12.5 中断处理程序和 iret 指令

中断处理程序的常规步骤:

  1. 保存 reg

  2. 处理中断

  3. 恢复 reg

  4. iret 返回

iret 指令的功能:

  1. pop IP

  2. pop CS

  3. popf

12.7 编程处理 0 号中断

安装程序应该做的两件事:

  1. 把程序送到安全内存空间中(用串传送指令)

  2. 设置中断向量表

数据和栈不能像以往那样单独定义成段,应该放在中断程序开头处,避免被覆盖

12.11 单步中断

CPU 提供单步中断的原因:为单步跟踪程序的执行过程提供了实现机制

Unit 13 int 指令

13.4 BIOS 和 DOS 所提供的中断例程

BIOS 的组成:

BIOS 的中断例程:和 硬件设备 相关

DOS 的中断例程:操作系统 向程序员提供的变成资源

13.5 BIOS 和 DOS 中断例程的安装过程

  1. CPU 加电,初始化 (CS) = 0FFFH, (IP) = 0

  2. CPU 执行 FFFF:0 处的转跳指令,转去 BIOS 中的硬件系统检测和初始化

  3. 初始化程序建立 BIOS 所支持的中断向量(即将 BIOS 提供的中断例程的入口地址登记在中断向量表中)

    它们是固化到 ROM 中的程序,一直在内存中存在,所以只要登记入口地址就好啦

  4. 硬件系统检测和初始化完成后,CPU 调用 int 19h 进行操作系统引导,从此将计算机交由操作系统控制

  5. DOS 将 DOS 的中断例程装入内存并建立相应的中断向量,并完成其它工作

Unit 14 端口

和 CPU 通过总线相连的芯片:

14.1 端口的读写

端口地址通过内存地址一样通过地址总线来传送

CPU 最多可定位 64 个不同端口 → 端口地址范围为 0 ~ 65535

对端口的读写只能用 in / out 指令

in / out 指令执行时与总线相关的操作:

  1. CPU 通过 地址线 发出地址信息(端口号)

  2. CPU 通过 控制线 发出端口读 / 写命令,选中端口所在芯片,并通知它要从中读数据 / 向其写数据

  3. 端口所在芯片 与 CPU 通过 数据线 传送要读写的数据

14.2 CMOS RAM 芯片

CMOS RAM 芯片的特征:

CPU 对 CMOS RAM 的读写分两步进行:向地址端口中送入地址,与数据端口进行数据交互

14.4 CMOS RAM 中存储的时间信息

信息 存放单元
0
2
4
7
8
9

长度均为 1 byte,以 BCD 码存放

BCD码:以 4 位二进制数表示十进制数码

一个字节可表示两个 BCD 码,所以读取时间信息的时候要用位运算把它拆开

Unit 15 外中断

CPU 除了有运算能力外,还要有 I/O 能力

15.1 接口芯片和端口

CPU 通过端口和外设进行联系

15.2 外中断信息

外中断:由相关芯片发送至 CPU,CPU 在执行完当前指令后,检测终端信息,并引发中断过程,处理外设输入

外中断的中断类型码通过数据总线送入 CPU,内中断的中断类型码则是在 CPU 内部产生的,其余流程和内中断相同

外中断源

15.3 PC 机键盘的处理过程

  1. 键盘输入

按下一个键 → 键盘中芯片产生扫描码(通码) → 送入主板上相关接口芯片的寄存器中(端口地址 = 60h)

松开一个键 → 产生断码 = 通码 + 80h → 同上

键盘上部分键的扫描码

  1. 引发 9 号中断

  2. 执行 int 9 中断例程

  3. 读出 60h 端口中的扫描码

  4. 字符键 - 字符码 - 内存中的 BIOS 键盘缓冲区↓

    键盘缓冲区:存储 15 个键盘输入,1 word / 输入,高位存放扫描码,低位存放字符码

    控制键 & 切换键 - 状态字节 - 内存中 存储状态字节的单元

    存储键盘状态字节的单元:0040:17

      0: 右 shift = 1 → 按下
      1: 左 shift = 1 → 按下
      2: Ctrl = 1 → 按下
      3: Alt = 1 → 按下
      4: ScrollLock = 1 → 指示灯亮
      5: NumLock = 1 → 小键盘输入的是数字
      6: CapsLock = 1 → 大写字母
      7: Insert = 1 → 处于删除态
    
  5. 进行相关控制

15.4 编写 int 9 中断例程

模拟 int:

  1. pushf
  2. IF = 0, TF = 0
  3. call dword ptr ← 为避免在设置段地址和偏移地址的指令之间发生键盘中断,请活用 sticli ~

Unit 16 直接定址表

16.1 描述了单元长度的标号

标号

16.2 在其他段中使用数据标号

地址标号只能在代码段中使用

如果想在代码段中直接用数据标号访问数据,需要用伪指令 assume 将标号所在的段和一个段寄存器联系起来,还要在代码段中用相应段寄存器指向相应段吼

16.3 直接定址表

利用表,在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一集合中的对应数据

目的:

  1. 为了算法的清晰和简洁

  2. 为了加快运算速度

  3. 为了使程序易于扩充

16.4 程序入口地址的直接定址表

子程序的实现机制:我们可以在直接定址表中存储子程序的入口地址,它们在表中的位置和功能号相对应,从而方便地实现不同子程序的调用

Unit 17 使用 BIOS 进行键盘输入和磁盘读写

17.1 int 9 中断例程对键盘输入的处理

键盘缓冲区

int 9 对键盘输入的处理:

17.2 使用 int 16h 中断例程读取键盘缓冲区

int 16h 的 0 号功能进行如下工作:

  1. 检测键盘缓冲区中是否有数据

  2. 没有则继续做第一步(循环等待)

  3. 读取缓冲区中 第一个 字单元中的键盘输入

  4. 将读取的扫描码送入 ah,ASCII 码送入 al

  5. 将已读取的键盘输入从缓冲区中删除

17.4 应用 int 13h 中断例程对磁盘进行读写

3.5 英寸软盘:

实验 17 编写包含多个功能子程序的中断例程

写逻辑扇区编号体现统一编址的好处呀 ~~


感想这种东西就悄悄附在文末……

虽然严格意义上来讲,写下这个文档的时候我还没完成第一阶段(毕竟还没做传说中的课设二……),但如果要我为这段日子做个总结的话,我会说,我没想到,在大一的热血青春之后,在这个方圆不超过两公里的校园内,竟然还能遇到如此美好的事。

最初是因为看到可以学汇编所以稀里糊涂地报了名,还差一点因为大冬天刮大风而没去启动会(?),现在想想,真是万幸我去了。宣讲的学长站在台上,把“独立”“强大”和“纯粹”说得那么坚定和恳切,我在台下听着,看着,几乎像个傻子一样热泪盈眶,我的人生至今为止,一直就是在踏进各式各样的校门,受各式各样的教育,以至于对受教育这事都快要变得麻木了,但那一夜我心里的某些东西松动了,因为从没有人对我说过那些话,做过那般热忱的讲演,宣讲的学长说“也许你们有的人在想,终于有人大声地把这些说出来了”,其实我在想的是,终于有人在我眼前鲜活地证明了这些话是可以被说出来的,不仅可以说出来,还可以无畏地付诸实践。

之后……就是学习活动啦,没啥好说的,都在笔记里了,就有一点,我意外地发现,我对随堂考试和上台讲课竟然也没有抵触或者恐惧,虽然没有提前完成进度,但也没有被丢在后面,一切都自然而然地完成了,每每想到这里,我总是十分惶恐,在这“顺利”背后,得有多少人的付出呀。

希望我还能在这里变得更强大一些,能在知识当中汲取更多,然后,不要当程序员(诶)。最后还有一句,是第一阶段的学长走之前说的:“你们明明还什么都没做”。以此自勉吧。






Powered by Jekyll | Z ♥ | somnusberg 2020