基础知识
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.