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中
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
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
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
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.