基础知识

1.每一种CPU(微处理器)都有自己的汇编指令集

2.汇编指令是机器指令的助记符, 与机器指令一一对应

3.地址总线宽度 => 寻址能力 数据总线宽度 => 数据传送时的数据传输量 控制总线宽度 => 系统中其他器件的控制能力

4.逻辑存储器 => 内存地址空间 => 受CPU寻址能力的限制(地址总线) => 分配

寄存器

8086CPU所有寄存器都是16位的, 可以放两个字节, 有14个寄存器

1.通用寄存器: AX BX CX DX 为了兼容上一代, 都可以分为两个独立的8位寄存器H(igh) L(ow) AH AL BH BL CH CL DH DL

2.字节 byte, 一个字节由8bit组成 字 word, 一个字由两个字节组成, 分别称为高位字节和低位字节

3.数据传输或运算时, 两个操作对象的位数要一致

4.物理地址 => 8086CPU20位地址总线 => 16位结构 => 内部两个16位地址合成为一个20位的物理地址 => 段地址+偏移地址 => 地址加法器 => 物理地址=段地址*16+偏移地址 => *16即左移4位


5.基础地址+偏移地址 = 物理地址

6.段的概念 => 内存并没有分段 => 段的划分来自CPU => 可以分段管理内存

7.段地址*16必然是16的倍数 => 所以一个段的起始地址也一定是16的倍数

8.偏移地址位16位, 寻址能力为64KB => 一个段的长度最大为64KB
9.
10.段寄存器: CS DS SS ES

11.CS:代码段寄存器, IP:指令指针寄存器
12.

13.

14,8086CPU的工作过程:

15.
16.CS,IP的值不能通过mov指令修改 => jmp 段地址:偏移地址

jmp 3:0B16 ;执行后CS=0003H, IP=0B16H, CPU将从0B46H处读取指令 jmp ax ;仅修改IP的内容,
jmp 某一合法寄存器,把寄存器的赋给IP

18.代码段 => 代码存放一组连续, 起始地址为16的倍数的内存单元中 => CS:IP指向代码段第一条指令的首地址

19.DosBox => debug.exe => debug

寄存器(内存访问)

1.内存中字的存储 => 两个内存单元 => 低地址单元存放低位字节, 高地址单元存放高位字节 => 如0,1两个单元存放4E20H, 0存放20H, 1存放4EH => 这两个单元称为0地址字单元 => 起始地址为N, 称为N地址字单元
2.

mov bx, 1000h
mov ds, bx
mov al, [0] ;将内存单元10000的数据送入al
mov ax,[0] ;起始地址为10000的内存字单元数据送入ax

3.DS寄存器 => 存放要访问的数据的段地址 => mov bx 1000H => mov ds, bx => 需要寄存器中转

8086不支持直接向段寄存器送入数据

4.mov al, [0] => [0]表示一个内存单元 => 0表示偏移地址 => 段地址来自DS寄存器的数据

5.mov ax, [0] => 则送入ax一个字型数据
6.

7.mov 段寄存器, 寄存器 => mov 寄存器, 段寄存器

8.mov 内存单元, 寄存器 => mov 内存单元, 段寄存器 => mov 段寄存器, 内存单元

9.
10.数据段 => 将一段内存当作数据段 => ds存放数据段的段地址

11.栈 => LIFO => Last In First Out => PUSH 入栈 => POP 出栈 => 都以字为单位进行

12.SS存放栈顶的段地址, SP存放偏移地址 => SS:SP指向栈顶元素 类似 CS:IP指向指令

13.push ax => SP=SP-2 => ax送入SS:SP指向的内存单元处 => 入栈时, 栈顶从高地址向低地址方向增长 => pop ax相反 => SP=SP+2, SS:SP指向当前栈顶下面的字单元, 以此单元为新的栈顶

14.栈空时, 不存在栈顶元素 => SS:SP指向最底部单元下面的单元

15.栈顶超过栈空间 => 8086没有解决方案, 需要自己注意栈空间大小

16.push 寄存器 => 将一个寄存器中的数据入栈 => push 段寄存器 => push 内存字单元

17.pop 寄存器 => 用一个寄存器接收出栈的数据 => pop 段寄存器 => pop 内存字单元

18.push, pop是一种内存传送指令

;将10000H~1000FH当作栈, 将AX,BX,DS中的数据入栈

;初始化栈顶
mov ax, 1000h
mov ss, ax	;8086不支持直接向段寄存器送入数据
mov sp, 0010h

push ax
push bx
push ds

;将ax,bx设值, 清零后恢复
mov ax, 001ah
mov bx, 001bh
push ax
push bx
sub ax, ax	;2bytes
mov bx, 0	;3bytes
pop bx
pop ax

20.栈段 => 10000H1FFFFH当作栈段, 初始状态为空, 则SS=1000H, SP=? => SP= FFFE+2=0000 => SS=1000, SP=0000 => 所以栈最大为0FFFF, 64KB => 当栈满时, 再次压入栈, 将环绕覆盖原来栈中的内容

21.
22.
23.
24.

mov ax, 1000h
mov ds, ax
mov ax, 2000h
mov ss, ax
mov sp, 10h
push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]

第一个程序

1.伪指令由编译器执行, 汇编指令由CPU执行

assume cs:codesg	;将代码段codesg与段寄存器cs联系起来

;伪指令*** segment ~ *** ends 成对使用
codesg segment

	mov ax, 0123h
	mov bx, 0456h
	add ax, bx
	add ax, ax

	mov ax, 4c00h
	int 21h		;程序返回, 程序运行完毕后将CPU的控制权交出
	
codesg ends

end		;伪指令, 汇编程序的结束标记

2.用masm.exe进行编译, 会产生3个输出文件, 目标文件.obj (列表文件.lst, 交叉引用文件.crf)

3.用link.exe进行连接得到可执行文件

4.连接的作用有: 描述信息
5.masm 路径\文件名; link 路径\文件名; 忽略中间文件的生成
6.

7.debug *.exe

8.exe文件的加载过程

9. ds=075A => PSP地址为075A:0 => 程序地址为076A:0 cs:ip=076A:0 => 指向程序的第一条指令 cx => 存放程序的长度

10.要用p命令执行int 21, 程序会返回到debug中, q命令退出debug返回到command中

[BX]和loop指令

1.
2.
3.[0]表示内存单元 => 0是偏移地址 => 段地址在ds中 => 单元的长度由具体指令中的其他对象如寄存器指出

4.[bx]也表示一个内存单元 => 偏移地址在bx中

5.inc bx => (bx)+1

6.loop 标号 => CPU执行时 => (cx)=(cx)-1 => 判断cx中的值,不为0则转到标号处执行程序,为0向下执行(即cx存放循环次数)

mov cx, 循环次数
s: 循环执行的程序段
loop s

8.在汇编源程序中数据不能以字母开头, 要在前面加上0

9.g 代码地址跳过单步执行过程, 在遇到loop指令时, p命令自动重复执行loop

10.在汇编源程序中不可以使用类似mov ax, [0]的指令, masm编译器会当作mov ax, 0来处理 => 可以用bx间接给出内存单元的偏移地址 => mov ax, ds:[0]

;累加ffff:0~ffff:b的数据
assume cs:code

code segment
	     mov  ax, 0ffffh
	     mov  ds, ax
	     mov  bx, 0     	;ds:bx

	     mov  dx, 0     	;初始化累加寄存器

	     mov  cs, 12    	;初始化循环计数寄存器
	s:   mov  al, [bx]
	     mov  ah, 0
	     add  dx, ax
	     inc  bx
	     loop s

	     mov  ax, 4c00h
	     int  21h
code ends

end

12.找到一段空的安全的内存使用 => 一般情况使用0:200~0:2ff这段空间

;0:200~0:23f = 0020:0~0020:3f
;在这个段中分别写入0~63
assume cs: code

code segment
	     mov  ax, 0020h
	     mov  ds, ax
	     mov  bx, 0h

	     mov  cx, 40h	;64
	s:   mov  ds:[bx], bl	;8位对8位
	     inc  bx
	     loop s

	     mov  ax, 4c00h
	     int  21h
code ends

end

包含多个段的程序

1.内存空间的获取 => 加载程序时为程序分配 => 在源程序中定义段
2.

; 将定义的数据逆序排放
assume cs:code

code segment
	;dw => define word 定义了8个字型数据
	;在cs得到段地址, 因为定义在代码段的开始所以偏移地址为0,2,4...
	;代码段前16个字节存储dw定义的数据, 所以cs:ip, ip应该设置为10h, 或者在end后表明程序的入口
	;定义16个字型数据, 在后面的代码中当作栈来使用
	      dw   0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
	      dw   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
	
	start:
	;ss:sp => cs:30
	      mov  ax, cs
	      mov  ss, ax
	      mov  sp, 30h
	
	      mov  bx, 0
	      mov  cx, 8
	s:    push cs:[bx]
	      add  bx,2
	      loop s

	      mov  bx, 0
	      mov  cx, 8
	s0:   pop  cs:[bx]
	      add  bx, 2
	      loop s0

	      mov  ax, 4c00h
	      int  21h
code ends

end start	;end 标号表明程序的入口

3.可执行文件=描述信息+程序 => 描述信息是编译, 连接对伪指令进行处理得到的信息
4.

;重构2, 将代码 数据 栈分开
assume cs:code, ds:data, ss:stack

data segment
	     dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends

stack segment
	      dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
stack ends

code segment
	start:
	      mov  ax, stack
	      mov  ss, ax
	      mov  sp, 20h  ;stack

	      mov  ax, data
	      mov  ds, ax

	      mov  bx, 0
	      mov  cx, 8
	s:    push [bx]
	      add  bx,2
	      loop s

	      mov  bx, 0
	      mov  cx, 8
	s0:   pop  [bx]
	      add  bx, 2
	      loop s0

	      mov  ax, 4c00h
	      int  21h
code ends

end start

更灵活的定位内存的方法

1.and => 逻辑与指令

2.or => 逻辑或指令

3.db => define byte 以字符形式给出数据db ‘Unix’ mov al, ‘a’

4.ASCII => A=41h , a= 61h => 65,97 => 相差20h, 就是二进制第六位32 => 二进制倒数第六位是0是大写, 是1是小写

5.[bx+idata] => mov ax, [200+bx] => mov ax, 200[bx] => mov ax, [bx].200 => 都是指偏移地址为(bx)+200

assume cs:code, ds:data

data segment
	     db 'BasiC'
	     db 'MiniX'
data ends

code segment
	start:mov  ax, data
	      mov  ds, ax
	
	;[bx+idata]的写法, 类似数组
	      mov  bx, 0
	      mov  cx, 5
	s:    mov  al, 0[bx]
	      and  al, 11011111b
	      mov  0[bx], al
	      mov  al, 5[bx]
	      or   al, 00100000b
	      mov  5[bx], al
	      inc  bx
	      loop s
	      
	;原本写法
	; s:      mov al, [bx]
	;         and al, 11011111b   ;将第六位置0,即小写变大写
	;         mov [bx], al
	;         inc bx
	;         loop s

	;         mov bx, 5
	;         mov cx, 5
	; s0:     mov al, [bx]
	;         or al, 00100000b    ;第六位置1,大写变小写
	;         inc bx
	;         loop s0

	      mov  ax, 4c00h
	      int  21h
code ends

7.si, di是8086中和bx功能相近的寄存器 => 不能分成2个8位寄存器来使用

8.[bx+si] [bx+di] => mov ax, [bx+si] => mov ax, [bx] [si]

9.mov ax, [bx+si+200] => mov ax, 200[bx] [si] => mov ax, [bx].200[si] => mov ax, [bx] [si].200

10.嵌套循环时 => 将外层循环的cx值保存起来 => 保存在闲置的寄存器中 => 开辟一段内存空间保存dw 0 => 定义一个栈段来存储

assume cs:code, ss:stack, ds:data

;每个单词的前四个字母改成大写字母
data segment
    db '1. display '
    db '2. brows '
    db '3. replace '
    db '4. modify '
data ends

stack segment
    dw 0, 0, 0, 0, 0, 0, 0, 0
stack ends

code segment
    start:  mov ax, stack
            mov ss, ax
            mov sp, 16h

            mov ax, data
            mov ds, ax

            mov bx, 0
            mov cx, 4
    s:      push cx
            mov cx, 4
            mov si, 0
    s0:     mov al, [bx+si]
            and al, 11011111b
            mov [bx+si], al
            inc si
            loop s0

            add bx,16
            pop cx
            loop s

            mov ax, 4c00h
            int 21h
code ends

end start

数据处理的两个基本问题

1.寄存器 => reg => ax, bx, cx, dx, ah, al, bh, bl, ch, cl, dh, dl, sp, bp, si, di

2.段寄存器 => sreg => ds, cs, ss, es

3.bx, si, di, bp => 寻址 => 单独出现 => bx, si => bx, di => bp, si => bp, di

4.使用bp时没有显性给出段地址 => 段地址默认在ss中

5.

数据位置的表达

1.立即数, 包含在机器指令中的数据, 执行前在CPU的指令缓冲器中 => 1, 20h, 0011b, ‘a’
2.寄存器
3.段地址SA+偏移地址EA

寻址方式

1.直接寻址 => [idata]
2.寄存器间接寻址 => [bx]
3.寄存器相对寻址 => 结构体 [bx].idata, 二维数组 idata[bx], [bx] [idata]
4.基址变址寻址 => [bx] [si]
5.相对基址变址寻址 => 二维数组 idata[bx] [si], 结构体 [bx].idata[si]

指明要处理的数据的长度

1.寄存器名直接指明 => ax, al
2.X ptr => word ptr, byte ptr => mov word ptr ds:[0], 1
3.其他方法 => 有些指令默认了访问数据的长度 => push, pop只进行字操作

struct company {
   
    char cn[3];     //名称
    char hn[9];     //总裁姓名
    int pm;         //排名
    int sr;         //收入
    char cp[3];     //产品
};

// 存储在seg:60
struct company dec = {
   "DEC", "Ken Olsen", 137, 40, "PDP"};

int main(void) {
   
    /*
        mov ax, seg
        mov ds, ax
        mov bx, 60h     ;首地址
    */
    int i;      
    dec.pm = 38;            // mov word ptr [bx].0ch, 38
    dec.sr = dec.sr+70;     // add word ptr [bx].0eh, 70
    i = 0;                  // mov si, 0
    dec.cp[i] = 'V';        // mov byte ptr [bx].10h[si], 'V'
    i++;                    // inc si
    dec.cp[i] = 'A';        // mov byte ptr [bx].10h[si], 'A'
    i++;                    // inc si
    dec.cp[i] = 'X';        // mov byte ptr [bx].10h[si], 'X'

    return 0;
}

10.div => 除法指令 => div reg => div 内存单元

11.

; 除法100001/100 
; 100001>65535 dx+ax存放, 186A1h
mov dx, 1
mov ax, 86A1h
mov bx, 100
div bx
;(ax)=03E8h
; 余数(dx)=1

; 1001/100
mov ax, 1001
mov bl, 100
div bl
;(ah)=0Ah
; 余数(al)=1

12.dd => 定义dword(double word)双字型数据

13.dup => 与db,dw,dd配合使用 => 进行数据的重复 => db 3 dup(0, 1, 2) 定义了9个字节相当于db 0,1,2,0,1,2,0,1,2 => db/dw/dd 重复次数 dup (数据)

assume cs:code, ds:data, es:table

data segment
	;年份21*4 0~83
	     db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
	     db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
	     db '1993','1994','1995'
	;收入21*4 84~167
	     dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
	     dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
	;雇员人数21*2 168~209
	     dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
	     dw 11542,14430,45257,17800
data ends

;将数据放到table里
table segment
	      db 21 dup ('year summ ne ?? ')
table ends

code segment
	start:mov  ax, data
	      mov  ds, ax
	      mov  bx, 0

	      mov  ax, table
	      mov  es, ax
	      mov  si, 0
	      mov  di, 0

	      mov  cx, 21
	s:    mov  ax, [bx]
	      mov  es:[si], ax
	      mov  ax, [bx+2]
	      mov  es:[si+2], ax    	;放入年份

	      mov  ax, [bx+84]      	;低位收入
	      mov  dx, [bx+86]      	;高位收入
	      mov  es:[si+5], ax    	;放入低位收入
	      mov  es:[si+7], dx    	;放入高位收入

	      div  word ptr [di+168]	;计算平均收入
	      mov  es:[si+13], ax   	;放入平均收入

	      mov  ax, [di+168]
	      mov  es:[si+10], ax   	;放入雇员
            
	      add  bx, 4            	;年份
	      add  si, 16           	;table
	      add  di, 2            	;雇员
	      loop s

	      mov  ax,4c00h
	      int  21h
code ends

end start

转移指令的原理

1.可以修改ip或同时修改cs:ip的指令叫做转移指令 => 控制CPU执行内存中某处代码的指令

2.段内转移 => 只修改ip => jmp ax => 短转移ip范围-128127 => 长转移范围-3276832767

3.段间转移 => 修改cs:ip => jmp 1000:0 形如此类指令只能在debug中使用

4.offset => 取得标号的偏移地址 => mov ax, offset start => mov ax, 0

5.jmp short 标号 => 转到标号处执行指令 => 段内短转移修改范围-128~127

6.在一般的汇编指令中的立即数, 无论是数据还是偏移地址都会在机器指令中出现

7.CPU在执行jmp指令时不需要转移目的地址, 就可以实现对ip的修改 => jmp指令包含到目的地址的位移

8.jmp下一个指令的地址 - 标号处地址 = 偏移位移地址

9.
10.jmp far ptr 标号 => 段间转移 => (cs)=标号所在段地址, (ip)标号所在段的偏移地址

11.jmp word ptr 内存单元地址(段内转移) => 内存单元地址存放专一的目的偏移地址 => (ip)=(字单元地址)

12.jmp dword ptr 内存单元地址(段间转移) => (cs)=(内存单元地址+2), (ip)=(内存单元地址)

13.有条件转移指令, 所有有条件转移指令都是短转移=> jcxz 标号 => 如果(cx)=0,转到标号处执行

14.所有循环指令都是短转移 => loop 标号

15.短转移位移超界编译时会报错

16.80*25彩色字符模式缓冲区 B8000H~BFFFFH
每行可以显示80个字符, 1个字符2个字节
低位字节存放ASCII码, 高位字节存放字符属性属性字节格式

17.

;在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 'welcome to masm!';00000010 02h  00100100 24h  01110001 71h

assume cs:code, ds:data, ss:stack

data segment
	     db 'welcome to masm!'	;16 byte
	     db 02h, 24h, 71h
data ends

stack segment
	      dw 8 dup (0)
stack ends

code segment

	start:mov  ax, data
	      mov  ds, ax

	      mov  ax, stack
	      mov  ss, ax
	      mov  sp, 10h

	      mov  ax, 0b800h      	;显示区段
	      mov  es, ax
        
	      mov  bx, 780h        	;屏幕中间第12行
	      mov  si, 10h         	;指向颜色

	      mov  cx, 3
	s:    mov  ah, ds:[si]     	;颜色
	      push cx
	      push si

	      mov  cx, 16
	      mov  si, 64          	;160列中间就是(160-32)/2=64
	      mov  di, 0

	s0:   mov  al, ds:[di]     	;data段的字符
	      mov  es:[bx+si], al  	;低位存放字符
	      mov  es:[bx+si+1], ah	;高位存放属性
          
	      add  si, 2
	      add  di, 1
	      loop s0

	      pop  si
	      pop  cx
	      add  si, 1           	;指向下一个颜色
	      add  bx, 0a0h        	;指向下一行 160=0a0h
	      loop s

	      mov  ax, 4c00h
	      int  21h
code ends

end start

CALL和RET指令

1.ret => 使用栈中的数据修改ip => 近转移 => 类似于pop ip

2.retf => 使用栈中的数据修改cs:ip => 远转移 => 类似于pop ip, pop cs

3.call 标号 => 将当前ip压栈后, 转到标号处执行命令 => 类似于push ip, jmp near ptr 标号
call指令与jmp类似, 对应的机器指令中没有转移目的地址, 而是相对于当前ip的转移位移

4.call far ptr 标号 => push cs, push ip, jmp far ptr 标号

5.call 16位reg => push ip, jmp reg

6.call word ptr 内存单元地址 => push ip, jmp word ptr 内存单元地址

7.call dword ptr 内存单元地址 => push cs, push ip, jmp dword ptr 内存单元地址

8.mul => 乘法指令 => mul reg => mul 内存单元 => 与除法指令类似

9.

;计算100 * 10
mov al, 100
mov bl, 10
mul bl
;计算100 * 10000
mov ax, 100
movbx, 10000
mul bx

10.call和ret指令可以实现模块化编程, 比如:


为了避免寄存器冲突, 把子程序要用的寄存器全都入栈结束后出栈

标志寄存器


1.flag寄存器

2.ZF标志 => zero => 相关指令执行后结果为0, zf=1

3.PF标志 => 奇偶标志位parity => 结果中所有bit位1的个数为偶数, pf =1

4.SF标志 => 符号标志位signal => 结果为负sf=1 => 对有符号计算结果的记录

5.CF标志 => 进位标志位carry => 进行无符号运算时,记录了最高有效位更高位的进位值或者借位值

6.OF标志 => 移除标志位overflow => 有符号运算超过了机器所能表示的范围


8.

9.cmp ax,bx => 做减法运算(ax)-(bx) => 不保存结果,影响标志寄存器

10.
11.

;一些条件转移指令
;根据无符号数的比较结果进行转移
je 			zf=1		;equal = 
jne 		zf=0		;not equal !=
jb 			cf=1		;below <
jnb 		cf=0		;not below >
ja			cf=0且zf=0	;above	>
jna			cf=1或zf=1	;not above <=

12.DF标志 => 方向标志位direction => 在串处理指令中, df=0, 每次操作后si, di递增 => df=1, 递减 串处理指令 => movsb => 传送一个字节 => 将ds:si指向的内存单元的字节送入es:si中

movsw => 传送一个字 => 传送完递增或递减2 一般都和rep连在一起使用, 根据cx的值来重复执行传送指令 => rep movsb => rep movsw

cld 将df置0 std 将df置1
13.

;将F000h段中最后16个字节复制到data段中
; ds:si  =>  es:di
data segment
	db 16 dup (0)
data ends

; ds:si => es:si
mov ax, 0f000h
mov ds, ax
mov si, 0ffffh
mov ax, data
mov es, ax

mov di, 15
mov cx, 16
std
rep movsb

14.pushf popf => 将flag标志寄存器的内容压栈, 出栈数据送入

15.debug中的表示

内中断

1.CPU内部中断源及中断类型码
除法错误: 0
单步执行: 1
执行into指令: 4
执行int指令: int n , n为字节型立即数是提供给CPU的中断类型码
2.产生中断 => 中断信息包含表示中断源的类型码 => 查找中断向量表 => 得到中断处理程序的入口地址 => 设置cs:ip

8086中断向量表指定放在内存0000:0000~0000:03FF的1024个单元中, 一个表项占两个字, 高地址字存放段地址,
低地址字存放偏移地址

3.8086收到中断信息后引发的中断过程
得到中断类型码 N
flag寄存器的值入栈 pushf
设置标志寄存器第8位TF和第九位IF的值为0 tf=0, if=0
cs内容入栈, ip内容入栈 push cs, push ip
从内存地址为中断类型码 *4和 中断类型码 *4+2的两个字单元中读取中断处理程序的入口地址设置cs:ip (ip)=(N * 4) (cs) = (N * 4 +2)
硬件自动执行的中断过程, 程序员无法改变这个过程中做的工作

4.中断处理程序的编写方法

5.iret => pop ip, pop cs, popf

6.中断向量表一般占不满 => 可以使用0000:0200~0000:02FF这段内存

7.单步中断 => 为了单步跟踪程序的执行过程 => debug

8.ss:sp设置应该连续完成 => 设置ss后即使发生中断CPU也不会响应

;0号中断 除法溢出时屏幕中间显示字符串"divide error!"
assume cs:code

code segment
	start:  mov  ax, cs
	        mov  ds, ax
	        mov  si, offset d0             	;源地址ds:si

	        mov  ax, 0
	        mov  es, ax
	        mov  di, 200h                  	;目的地址es:di 0:200

	        mov  cx, offset d0end-offset d0	;传输长度

	        cld                            	;正向
	        rep  movsb

	        mov  ax, 0
	        mov  es, ax
	        mov  word ptr es:[0*4], 200h
	        mov  word ptr es:[0*4+2], 0    	;设置中断向量表,0中断

	        mov  ax, 4c00h
	        int  21h
	d0:     jmp  short d0start
	        db   'divide error!'

	d0start:mov  ax, cs
	        mov  ds, ax
	        mov  si, 202h                  	;ds:si指向字符串

	        mov  ax, 0b800h
	        mov  es, ax
	        mov  di, 12*160+36*2           	;es:di指向显存, 屏幕中间

	        mov  ah, 24h                   	;颜色属性
	        mov  cx, 13                    	;字符串长度
	s:      mov  al, [si]
	        mov  es:[di], al
	        mov  es:[di+1], ah
	        inc  si
	        add  di, 2
	        loop s

	        mov  ax, 4c00h                 	;返回dos
	        int  21h
	d0end:  nop								;计算程序长度
code ends

end start

int指令

1.
2.
3.和硬件设备相关的dos中断例程中, 一般都掉用了BIOS的中断例程

4.BIOS和DOS提供的中断例程安装到内存中

5.

;BIOS提供的int 10h中断例程
;在屏幕的5行12列显示3个红底高亮闪烁绿色的'a'
assume cs:code

code segment
    mov ah, 2       ;内部子程序的编号2,置光标
    mov bh, 0       ;第0页
    mov dh, 5       ;行号
    mov dl, 12      ;列号
    int 10h

    mov ah, 9           ;光标处显示字符
    mov al, 'a'         ;字符
    mov bl, 11001010b   ;颜色属性
    mov bh, 0
    mov cx, 3           ;字符重复个数
    int 10h

    mov ax, 4c00h
    int 21h         
code ends

end 
;DOS提供的int 21h中断例程
;子程序4ch 程序返回
;子程序9 光标位置显示字符串
;要显示的字符串以$作为结束符, ds:dx指向字符串
assume cs:code

data segment
	db 'Welcome to masm', '$'
data ends

code segment
	start:	mov ah, 2
			mov bh, 0
			mov dh, 5
			mov dl, 12
			int 10h
			
			mov ax, data
			mov ds, ax
			mov dx, 0
			mov ah, 9
			int 21h
			
			mov ax, 4c00h
			int 21h
code ends

端口

1.各种存储器都和CPU的地址,数据,控制线相连, 在操控这些存储器时当作内存来对待 => 总地看作一个由若干存储单元组成的逻辑存储器 => 将这个逻辑存储器称作内存地址空间
2.
3.这些芯片中都有一组可以由CPU读写的寄存器 => CPU在对他们进行读写的时候都是通过控制线向他们所在的芯片发出端口读写命令 => CPU将这些寄存器当作端口进行统一编址, 建立了一个统一的端口地址空间, 每一个端口在地址空间中都有一个地址

4.
5.端口地址和内存地址一样通过地址总线来传送 => 64KB => 端口号: 0~65535

6.端口读写指令in从端口读入 out向端口写入

7.CPU访问内存 mov ax, ds:[8]

CPU通过地址线将地址信息8发出
CPU通过控制线发出读内存命令, 选中存储器芯片, 通知它要读数据
存储器将8号单元的数据通过数据线送到CPU

8.CPU访问端口 in al, 60h

CPU通过地址线将地址信息60h发出
CPU通过控制线发出端口读命令, 选中端口所在芯片, 通知它要读数据
端口所在的芯片将60h端口的数据通过数据线送到CPU
9.

;CMOS RAM不断电保存时间和系统配置信息
;70h地址端口 71h数据端口读写CMOS RAM
assume cs:code

code segment
    mov al, 2
    out 70h, al
    in al, 71h      ;读取CMOS RAM的2号单元的内容

    mov al, 2
    out 70h, al
    out 71h, 0      ;向CMOS RAM的2号单元写入0
code ends

11.逻辑移位指令 => shl shr
最后移除的1位写入cf中
最低位/最高位用0补充
移动多位时, 移动位数放在cl中

12.CMOS RAM中用BCD码存放当前时间: 年 月 日 时 分 秒 6个信息各1个字节, 高4位BCD码存放十位, 低4位存放个位, 存放单元分别为:

外中断

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

2.外中断源
(1)可屏蔽中断 => 检测到中断时 => if=1响应中断, if=0不响应可屏蔽中断 => 所以在处理内中断中断过程中将if置0 => sti 置if为1 cli 置if为0

与内中断类似, 只在取中断类型码时有区别, 可屏蔽中断通过地址线送入CPU, 中断类型码是在CPU内部产生的

(2)不可屏蔽中断 => 中断类型码固定为2, 不需要取 =>但几乎所有外设引起的中断都是可屏蔽中断

3.键盘处理
按下一个按键 => 产生扫描码 => 送入相关芯片寄存器端口地址为60h => 通码
松开 => 扫描码 => 60h => 断码
扫描码长度一个字节 => 通码最高位0, 断码最高位1
断码 = 通码+80h => 最高位变为1
键盘的输入到达60h端口时 => 相关芯片向CPU发出中断类型码为9的可屏蔽中断 => CPU执行int 9中断例程处理键盘输入

4.BIOS提供了int 9中断例程
(1)读出60h端口的扫描码
(2)字符区的会将该扫描码和ASCII码送入内存中的BIOS键盘缓冲区

(3)控制键或切换键会将其转变为状态字节写入内存中存储状态字节的单元

(4)对键盘系统进行相关的控制
(5)

;安装一个新的int 9中断例程
;功能:在DOS下,按下“A”键后,除非不再松开,如果松开,就显示满屏幕的“A”,其他的键照常处理

assume cs:code

stack segment
	      db 128 dup (0)
stack ends

code segment
	start:  
	        mov   ax, stack
	        mov   ss, ax
	        mov   sp, 128

	;di:si => es:di
	        push  cs
	        pop   ds
	        mov   ax, 0
	        mov   es, ax
	        mov   si, offset int9
	        mov   di, 204h

	        mov   cx, offset int9end-offset int9
	        cld
	        rep   movsb

	;原int 9地址入口放到0:200
	        push  es:[9*4]
	        pop   es:[200h]
	        push  es:[9*4+2]
	        pop   es:[202h]

	;新的int 9放到中断向量表中
	        cli
	        mov   word ptr es:[9*4], 204h
	        mov   word ptr es:[9*4+2], 0
	        sti

	        mov   ax, 4c00h
	        int   21h
	int9:   
	        push  ax
	        push  bx
	        push  cx
	        push  es

	        in    al, 60h
	        pushf
	        call  dword ptr cs:[200h]
        
	        cmp   al, 1eh+80h                   	;a的扫描码
	        jne   int9ret

	        mov   ax, 0b800h
	        mov   es, ax
	        mov   bx, 0
	        mov   al, 'A'
	        mov   cx, 2000
	s:      
	        mov   byte  ptr es:[bx], al
	        add   bx, 2
	        loop  s
	int9ret:
	        pop   es
	        pop   cx
	        pop   bx
	        pop   ax
	        iret
	int9end:nop
code ends

end start

直接定址表

1.地址标号: => 仅标记地址 => 只能在代码段中使用

2.数据标号 => 不加冒号 => 标记了存储单元的地址和长度 => 想在代码段中直接用数据标号访问数据,用assume将标号所在段与一个段寄存器连接起来满足编译器的需要

;累加a里的数据, 结果存到b中
assume cs:code, ds:data

data segment
	a db 1, 2, 3, 4, 5, 6, 7, 8
    b dw 0
data ends

code segment
	start:
		mov ax, data	;指明段ds
		mov ds, ax
		
		mov si, 0
		mov cx, 8
	s:
		mov al, a[si]
		mov ah, 0
		add b, ax
		inc si
		loop s
		
		mov ax, 4c00h
		int 21h
code ends

4.seg 标号 => 取得段地址

5.
相当于c dw offset a, offset b
6.
7.可以通过依据数据, 直接计算出所要找的元素的位置的表 => 称为直接定址表

; 安装一个新的int 7ch中断例程,为显示输出提供如下功能子程序。
; (1)清屏 (2)设置前景色;(3)设置背景色;(4)向上滚动一行。
; 入口参数说明:
; (1)用ah寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行
; (2)对于1、2号功能,用al传送颜色值,(al){
   0,1,2,3,4,5,6,7}。
assume cs:code

code segment
	start:     
	;int 7ch安装
	           push cs
	           pop  ds
	           mov  si, offset int7ch                  	;ds:si指向源地址

	           mov  ax, 0
	           mov  es, ax
	           mov  di, 204h

	           mov  cx, offset int7ch_end-offset int7ch
	           cld
	           rep  movsb

	           mov  ax, 0
	           mov  es, ax
	;原int7ch入口地址放到0:200处
	           push es:[7ch*4]
	           pop  es:[200h]
	           push es:[7ch*4+2]
	           pop  es:[202h]
	;设置新的int7ch到中断向量表
	           cli
	           mov  word ptr es:[7ch*4], 204h
	           mov  word ptr es:[7ch*4+2], 0h
	           sti

	           mov  ax, 4c00h
	           int  21h

	;让编译器从204h重新计算标号地址
	           org  204h
	int7ch:    
	           jmp  short set
	table      dw   sub1, sub2, sub3, sub4
	set:       
	           push bx
	           cmp  ah, 3
	           ja   sret
	           mov  bl, ah
	           mov  bh, 0
	           add  bx, bx
	           call word ptr table[bx]
	sret:      
	           pop  bx
	           iret
	sub1:      
	           push bx
	           push cx
	           push es
	           mov  bx, 0b800h
	           mov  es, bx
	           mov  bx, 0
	           mov  cx, 2000
	sub1s:     
	           mov  byte ptr es:[bx], ' '              	;清屏, 每一个字符都用' '替换
	           add  bx, 2
	           loop sub1s
	           pop  es
	           pop  cx
	           pop  bx
	           ret

	sub2:      
	           push bx
	           push cx
	           push es
	           mov  bx, 0b800h
	           mov  es, bx
	           mov  bx, 1                              	;颜色属性在高位
	           mov  cx, 2000
	sub2s:     
	           and  byte ptr es:[bx], 11111000b        	;前景色置0
	           or   es:[bx], al                        	;放入前景色
	           add  bx, 2
	           loop sub2s
	           pop  es
	           pop  cx
	           pop  bx
	           ret
    
	sub3:      
	           push bx
	           push cx
	           push es
	           mov  bx, 0b800h
	           mov  es, bx
	           mov  bx, 1                              	;颜色属性在高位
	           mov  cl, 4
	           shl  al, cl                             	;al左移4位
	           mov  cx, 2000
	sub3s:     
	           and  byte ptr es:[bx], 10001111b
	           or   es:[bx], al
	           add  bx, 2
	           loop sub3s
	           pop  es
	           pop  cx
	           pop  bx
	           ret

	sub4:      
	           push cx
	           push ds
	           push es
	           push si
	           push di
	;ds:si => es:si
	           mov  si, 0b800h
	           mov  es, si
	           mov  ds, si
	           mov  si, 160                            	;第一行后
	           mov  di, 0
	           cld

	           mov  cx, 24                             	;行数
	sub4s:     
	           push cx
	           mov  cx, 160                            	;将第一行后面的数据复制到从第一行开始, 向上滚动一行
	           rep  movsb

	           pop  cx
	           loop sub4s
        
	           mov  cx, 80
	           mov  si, 0
	sub4s1:    
	           mov  byte ptr [160*24+si], ' '          	;最后一行清空
	           add  si, 2
	           loop sub4s1

	           pop  di
	           pop  si
	           pop  es
	           pop  ds
	           pop  cx
	           ret
	int7ch_end:nop
code ends

end start

使用BIOS进行键盘读写和磁盘读写

1.int 9中断例程对键盘输入的处理 => 具体看外中断4

2.int 16h中断例程读取键盘缓冲区 => 从键盘缓冲区读取一个键盘输入, 然后将其从缓冲区删除, 该功能编号为0 mov ah, 0 int 16h => 结果扫描码放到ah中, ASCII码放到al中 => 检测缓冲区为空后, 会循环等待直到缓冲区中有数据

; 子程序:字符栈的入栈、出栈和显示
; 参数说明:(ah)=功能号,0表示入栈,1表示出栈,2表示显示; ds:si 指向字符栈空间
; 对于0号功能:(al)=入栈字符;对于1号功能:(al)=返回的字符
; 对于2号功能:(dh)(dl)=字符串在屏幕上显示的行、列位置

assume cs:code

code segment
	charstack:
	          jmp  short charstart
	table     dw   charpush, charpop, charshow
	top       dw   0                          	;字符栈 栈顶

	charstart:
	          push bx
	          push dx
	          push di
	          push es
            
	          cmp  ah, 2                      	;0表示入栈,1表示出栈,2表示显示
	          ja   sret                       	;>2
	          mov  bl, ah
	          mov  bh, 0
	          add  bx, bx                     	;找到功能号*2的子程序
	          jmp  word ptr table[bx]

	charpush: 
	          mov  bx, top
	          mov  [si][bx], al               	;将读取的ASCII码放入栈
	          inc  top
	          jmp  sret

	charpop:  
	          cmp  top, 0
	          je   sret
	          dec  top
	          mov  bx, top
	          mov  al, [si][bx]               	;栈顶元素读出
	          jmp  sret

	charshow: 
	          mov  bx, 0b800h
	          mov  es, bx
	          mov  al, 160
	          mov  ah, 0
	          mul  dh                         	;ROW
	          mov  di, ax
	          add  dl, dl                     	;COLUMN
	          mov  dh, 0
	          add  di, dx                     	;偏移值 es:di
            
	          mov  bx, 0
	s:        
	          cmp  bx, top
	          jne  noempty
	          mov  byte ptr es:[di], ' '
	          jmp  sret
	noempty:  
	          mov  al, [si][bx]
	          mov  es:[di], al
	          mov  byte ptr es:[di+2], ' '
	          inc  bx
	          add  di, 2
	          jmp  s
	sret:     
	          pop  es
	          pop  di
	          pop  dx
	          pop  bx
	          ret

	getstr:   
	          push ax
	getstrs:  
	          mov  ah, 0
	          int  16h
	          cmp  al, 20h
	          jb   nochar
	          mov  ah, 0
	          call charstack
	          mov  ah, 2
	          call charstack
	          jmp  getstrs
	nochar:   
	          cmp  ah, 0eh                    	;backspace的扫描码
	          je   backspace
	          cmp  ah, 1ch                    	;enter的扫描码
	          je   enter
	          jmp  getstrs
	backspace:
	          mov  ah, 1
	          call charstack
	          mov  ah, 2
	          call charstack
	          jmp  getstrs
	enter:    
	          mov  ah, 0
	          mov  al, 0
	          call charstack
	          mov  ah, 2
	          call charstack
	          pop  ax
	          ret
code ends

end getstr

4.int 13h对磁盘进行读写 => 通过磁盘控制器进行读写 => 读写时给出面号,磁道号, 扇区号 => 只有扇区号从1开始

5.功能号3是写扇区

6.