计算机组成原理之机器
各进制以及各进制之间的互相转换
二进制和机器指令和汇编语言的关系
汇编指令:通过编译器将汇编指令转化为01串。
伪指令:由编译器执行,指示编译器这边怎么运行,那边怎么运行。没有对应的机器指令。
符号体系:有编译器执行,没有对应的机器指令。
机器指令存放的地方
存放于内存之中。
command–debug–u–d
内存在存储器中的编号从0开始,内存大小 以字节(一个存储单元)为单位。
地址线、数据线、控制线
地址线的多少决定寻址能力的大小。
表示一个字节需要八根控制线。
控制线决定了cpu能够对哪些部件进行控制。
测验:
一个cpu的寻址能力为8kB,那么他的地址总线宽度应该为13.
解答:寻址能力为8kB,即可以寻找8k个存储单元,其中一个存储单元为一个字节。即需要13个地址线去表示8k个地址。
进一步理解内存
几乎所有部件都有内存,包括键盘鼠标。
cpu将其他部件都视为内存,进行数据的交换。
rom断电情况下也是存有数据的,ram断电数据就都消失了。
AX=AH+AL h:high l:low ah和al是相互独立的,为了满足16位兼容8位
BX=BH+BL
CX=CH+CL
DX=DH+DL
通用寄存器,一般存放数据。
-a
-mov ax,4e20
-mov bh,ah
-r
-t
-r
-t
进一步理解ax,bx,cx,dx寄存器
mov ax,18h
mov ah,78h
add ax,8h
mov bx,ax
add ax,bx
结构:
ax = 0018h
ax = 7818h
ax = 7820h
bx = 7820h
ax = f040h
实验:
-a
-mov ax,18h
-mov ah,78h
-add ax,8h
-mov bx,ax
-add ax,bx
-r
-t
...
地址寄存器和地址的组合
cpu存放段地址信息的寄存器为:ds,es,ss,cs
cpu存放偏移地址信息的寄存器为:bx,sp,bp,si,di,ip
古老的cpu8086有20个地址线。寄存器只能表示16位。所以计算机引用了段地址和偏移地址。
基础地址=段地址*10H
物理地址=基础地址+偏移地址
CPU通过地址寄存器区分指令和数据
cpu将cs:ip所组合出来的地址里面的内容全部当作指令
指令执行的过程:
- cpu从cs:ip所组成的地址中读取指令,将这个指令存放到指令缓存器中
- ip=ip+所读指令的字节数
- 执行指令缓存器中的内容,再回到步骤1
检测题:
在下面的代码中,寄存器ip的值修改了几次,最终ip的值是多少?
mov ax,bx
sub ax,ax
jmp ax 读取指令->ip=ip+所读指令的字节数->执行指令内容(再次修改ip)
答:4次,最终值为0
调试指令
r read 显示寄存器和寄存器中的值的键值对
r 寄存器名 修改寄存器中的值
d 显示后面128组字节的值
d 段地址:偏移地址 显示该地址后的128组的值
d 段地址:偏移地址 F 显示该地址后的16组的值 即一行
u 将后续的字节翻译成汇编指令,后面可以跟上参数段地址:偏移地址
a 向内存中写入汇编指令,后面可以跟上参数段地址:偏移地址
e 修改内存中的值,后面可以跟上参数段地址:偏移地址,可以连续内存的值一起修改,例如:e 1000:0 “abcd”
t 执行当前指令
高位地址存放高位字节。
数据的读取
ds存放了段地址信息,ds又叫做段寄存器。
mov al,ds:[0] //这句话的意思是将ds寄存器中的段地址和偏移地址0组合成的物理地址中的值赋给al,该值的长度取决于al的长度。 注:ds:[0],省略ds:在虚拟机中可以通过,但是在生产环境下是不可以省略的。
/*****将al中的数据送入内存单元10000h当中
mov bx,1000
mov ds,bx
mov ds:[0],al
******/
栈
出栈 pop 字型数据
入栈 push 字型数据
栈指针永远指向栈顶元素。
和栈有关的寄存器:段寄存器ss 偏移地址寄存器sp
栈是先进后出的。
可以一直push或者pop,超过了你自己规定的栈大小,称之为越界。
一个栈最多能够存放多少个字型数据
sp偏移地址决定了栈的寻址能力,所以最大寻址能力为FFFF
所以最多能够存放 256*256/2个字型数据。
设置了最大的栈内存,push满了后再去push还是会越界,越界的部分会替换掉重叠的部分。
比如10000h会替换掉内存0000h的数据。
push ax ==> sp-2 ==> ax 存放到 ss:sp-2
pop ax ==> ss:sp 存放到 ax ==> sp+2
注:如果ss为2000,sp为0,push ax,其中ax=1111
则1fff:000e和1fff:000f不会变成1111.暂时猜想是越界问题。
猜想正确,因为越界问题所以造成循环,最后 2fff:fffe和2fff:ffff变成1111.
loop和cx寄存器
计算123*236
assume cs:code
code segment
mov ax,0
mov cx,123
addNumber: add ax,236
mov ax,1000H
mov dx,2000H
loop addNumber
mov ax,4C00H
int 21H
code ends
end
内存安全
mov dx:[26H],ax
这句话会报错。在运行汇编程序的时候,一般都是在保护模式下,所有很多数据受保护的。无法写入。
更不能在实模式下随便写入内存,因为这样可能会篡改了受保护的重要的数据。
可用内存范围 0000:0200H ~~ 0000:02FFH 256个字节
0000:7E00H ~~ 暂不确定,一般在这里不会超出范围。
定义数据空间
dw 是定义字型数据,db是定义字节型数据
START就是一个标号,标志程序的入口而已,程序加载到内存之后CS:IP会指向这个标号,从START指向的指令开始运行,这个标号不一定是START,你也可以用MAIN,但在程序的最后要用END MAIN来提示程序结束。
使用寄存器间接寻址时,只可以使用 BX, BP, SI, DI 这四个寄存器中的一个,不可以使用其它寄存器。
可以ds:[bx+si]或者ds:[bx+di],但是不可以ds:[bx:di]
ds和bx 相当于 ss和bp
assume cs:code
data segment
dw 0123H,4567H,890AH
data ends
stack segment stack
dw 0,0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,6
mov ax,data
mov ds,ax
mov bx,0
mov cx,3
movSegment: push ds:[bx]
add bx,2
loop movSegment
int 21H
code ends
end start
如果数据字段存放与内存中的大小小于16倍数,则利用00H补全到16的倍数。
上面程序加载后,如果code段的段地址为x,则data段的段地址为x-2,stack段的段地址为x-1.
mov word ptr ds:[0],1
inc word ptr ds:[0] //字型数据加1
div word ptr ds:[4] //必须加上word ptr,不然计算机判断不出来到底要以多大长度为单位处理数据。
div 除法指令
1.除数
8bit,16bit 存在内存单元中或者寄存器中
2.被除数
如果除数是8bit,那么被除数为16bit,存放在ax中
如果除数是16bit,那么被除数为32bit,ax存放低16bit,dx存放高16bit
3.结果
如果除数是8bit,那么al存商,ah存放余数
如果除数是16bit,那么ax存商,dx存放余数。
dup 重复
stack segment stack
db 128 dup(0)
stack ends
定义一个栈,长度为128个字节。
db 256 dup(‘0’) 定义一个字符串,长度为256,‘0000000000000···’
段地址和偏移地址尽量按照这样搭配:ds:[bx] ds:[si] es:[di]
小程序
;题目:将year替换成年份,将summ替换成
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984','1985','1986','1987'
db '1988','1989','1990','1991','1992','1993','1994','1995' ;年份 长度为 84 byte
dd 16,634,545622,2333,34523,5647,76864 ;奖金 长度为 84 byte
dd 24234,3563,6356,657567,84845,8484,6467
dd 3563,2626,57547,3563,3232,232,566
dw 2,4,543,12,4345,3454,5311 ;人数 长度为 42 byte
dw 2,4,543,12,34534,222,33
dw 2,4,543,12,123,3453,454
data ends
table segment ;012345678901234
db 21 dup('year summ ne ?? ')
table ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack ;定义栈的大小为128字节
mov ss,ax
mov sp,128
mov ax,data ;设置数据地址的起始位置,ds段寄存器
mov ds,ax ;si=0,bx=168
mov si,0
mov bx,168
mov ax,table ;设置ax 存放修改字段的其实位置。
mov es,ax
mov di,0
mov cx,21 ;循环21次
inputTable: push ds:[si+0] ;将19放入栈中(push默认字型数据)
pop es:[di] ;将ye替换成19
push ds:[si+2] ;将75放入栈中
pop es:[di+2] ;将ar替换成75
mov ax,ds:[si+84]
mov dx,ds:[si+86]
mov es:[di+5],ax
mov es:[di+7],dx ;将su替换成0010H,mm替换成0000H(16=10H=0010H),并且将dx ax设置为被除数 即16=0000 0010H
push ds:[bx]
pop es:[di+10] ;将2替换掉ne
div word ptr es:[di+10] ;设置除数为2,其中2为字型数据,即16bit,他的被除数有dx,ax组成,即16=0000 0010H,得到商存在ax中
mov es:[di+13],ax ;将商ax存入ne的后面??。
add si,4 ;将si指向第二个四字节数据
add bx,2 ;将bx指向第二个两字节数据
add di,16 ;将di指向第二个year summ字符串
loop inputTable
int 21H
code ends
end start
offset 取出标号处的第一个偏移地址
jmp 标号处的地址 - jmp指令后的第一个字节的地址
jmp far ptr -32768~32767
jmp short ptr -128~127
jmp word ptr ds:[0]
jmp dword ptr ds:[0]
call 标号(将当前的IP压栈后,转到标号处执行指令),”call far ptr 标号”实现的是段间转移.
CPU执行”call far ptr 标号”时,相当于进行:
push CS
push IP
jmp far ptr 标号
call word ptr 内存单元地址,call dword ptr 内存单元地址
CPU执行”calldword ptr 内存单元地址”时,相当于:
push CS
push IP
jmp dword ptr 内存单元地址
jcxz
当cx为0的时候,跳转到标号处的第一个偏移地址。
loop是短转移,循环里面内容字节不能太多。
assume cs:code
code segment
mov ax,4C00H
int 21H
start: mov ax,0
s: nop
nop
mov di,OFFSET s
mov si,OFFSET s2
mov ax,cs:[si]
mov cs:[di],ax
s0: jmp short s
s1: mov ax,0
int 21H
mov ax,0
s2: jmp short s1
nop
code ends
end start
;程序可以正常运行吗?深刻理解运行过程。
0,2,4,6,8 偶数地址存放的是ascii码
1,3,5,7,9 奇数地址存放的是颜色
一行能写80个字符,共160个字节
一个dos窗口共25行,共4000个字节。
7号位置表示闪烁
6 red 背景色
5 green
4 blue
3 高亮
2 red 前景色
1 green
0 blue
;运行一遍然后查看结果,理解过程
;显示三行welcome to masm!
;第一行绿色,第二行绿底红字,第三行白底篮字
;利用显示内存显示,高位字节显示颜色,低位字节显示字符
assume cs:code,ds:data,ss:stack
data segment
;0123456789ABCDEF
db 'welcome to masm!'
db 00000010B
db 00100100B
db 01110001B
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
jmp show_masm
next: mov ax,4C00H
int 21H
show_masm: mov bx,data
mov ds,bx
mov bx,0B800H ;第一位不能是字母,所以加了个0,B800是屏幕的第一个字节
mov es,bx
mov di,160*12+30*2
mov cx,3
mov bx,16
showMasm: push cx
push di
mov cx,16
mov si,0
mov dh,ds:[bx]
showstring: mov dl,ds:[si]
mov es:[di],dx
add di,2
inc si
loop showstring
pop di
pop cx
add di,160
inc bx
loop showMasm
jmp next
code ends
end start
ret,retf
ret 近转移 相当于pop ip
retf 段间转移 相当于 pop ip,pop cs
call
在第二部ip=ip+指令字节数后执行push ip
call far ptr s 多做了一件事,push cs.
mul
8bit al bl mul byte ptr ds:[0] ax
16bit ax bx mul word ptr ds:[0] dx,ax
div
ax / 8bit = al ~ ah
dx,ax / 16bit = ax ~ dx
;在指定的位置,用指定的颜色,显示用0结束的字符串。
;dh=行号;cl=颜色;ds:si字符串首地址
;‘Welcome to masm!’,0
assume cs:code,ds:data,ss:stack
data segment
db 'Welcome to masm!',0
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack ;设置栈空间
mov ss,ax
mov sp,128
mov dh,2 ;行号,列号,颜色
mov dl,1
mov cl,2
call init_reg ;调用初始化函数,设置数据区域为ds,设置屏幕区域为es
mov si,0
call show_str ;调用显示结果函数,完成本题目的
mov ax,4C00H
int 21H
;----------------------------------------------------------------------------
init_reg: ;初始化函数,设置数据区域为ds,设置屏幕区域为es
mov bx,data
mov ds,bx
mov bx,0B800H
mov es,bx
ret
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
get_row:
mov al,160 ;获取显存的行偏移地址并存入di中
mov bl,dh
mul bl
mov di,ax
ret
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
get_col:
mov al,2 ;获取显存的列偏移地址并存入di中
mov bl,dl
mul bl
add di,ax
ret
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
show_str: ;显示结果函数
call get_row
call get_col
mov dh,cl
call show_string
ret
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
show_string:
; push cx
; push dx
; push ds
; push es
; push si
; push di
mov cx,0
showString:
mov cl,ds:[si]
jcxz showStringRet
mov dl,cl
mov es:[di],dx
add di,2
inc si
jmp showString
showStringRet:
; pop di
; pop si
; pop es
; pop ds
; pop dx
; pop cx
ret
code ends
end start
;程序:1234567/10
;因为会溢出产生不了正常结果,所以本程序自定义了怎样计算除法,ax存商,cx存余数
assume cs:code,ds:data,ss:stack
data segment
dd 1234567
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
mov bx,data
mov ds,bx
call div_number
mov ax,4C00H
int 21H
long_div: mov ax,dx
mov dx,0
div cx
push ax
mov ax,ss:[bp]
div cx
mov cx,dx
pop dx
ret
div_number:
mov ax,ds:[0]
mov dx,ds:[2]
mov cx,10
push ax
mov bp,sp
call long_div
; add sp,2
ret
code ends
end start
;输出数据段对应的字符
assume cs:code,ds:data,ss:stack
data segment
dw 1234,5678,65535,0
db 10 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call init_reg
call show_number
mov ax,4C00H
int 21H
init_reg: mov bx,data
mov ds,bx
mov bx,0B800H
mov es,bx
ret
div_number:
push ax
push bx
push cx
push dx
push ds
push es
push si
push di
mov ax,ds:[si]
mov dx,0
mov bx,10
divNumber:
div bx
add dx,30H
mov es:[di],dl
sub di,2
mov cx,ax
jcxz divNumberRet
mov dx,0
jmp divNumber
divNumberRet:
pop ax
pop bx
pop cx
pop dx
pop ds
pop es
pop si
pop di
ret
show_number:
mov si,0
mov di,160*10 + 40*2
mov cx,4
showNum: call div_number
add si,2
add di,160
loop showNum
ret
code ends
end start
标志位寄存器
cf :carry flag carry 进位 cy nc 移动指令不影响该寄存器。
zf : zero flag zr nz mul和div不影响该寄存器。
pf:parity 奇偶性 po 奇数 pe 偶数 1的个数是偶数就是pe
sf:sign 符号 ng 负数 pl 正数 ,mul不影响该寄存器
of overflow ov nv
of 在运算过程中决定,两个操作数会被当作有符号的处理,决定是否溢出 ,
cf 进位,把两个操作数当作无符号处理。
mov al,0FCH
add al,05H
上面结果是nv,未溢出,0FCH=1111 1100=-4 -4+5 显然没有溢出
adc,sbb
add ax,bx ax=ax+bx
adc ax,bx ax=ax+bx+carry
sbb ax,bx ax=ax-bx-carry
可以利用adc计算多位的加法,比如32 bit,128 bit等,这说明了内存中的值是没有任何含义的,可以根据
自己的情况决定他的用处。
je,jne,jb,jnb,ja,jna
je = jmp equal 其中jmp有范围 -128~127
b = below
a = above
DF
方向标志DF(Direction Flag)
用于串操作指令中,控制地址的变化方向:设置DF=0,存储器地址自动增加;
设置DF=1,存储器地址自动减少。
CLD指令复位方向标志:DF=0
STD指令置位方向标志:DF=1
DF标志和串传送指令
DF 方向标志位,在串处理指令中,控制每次操作后si、di的增减
df=0 每次操作后si、di递增
df=1 每次操作后si、di递减
movsb:
相当于
mov es:[di],byte ptr ds:[si]
如果 df=0
inc di
inc si
如果 df=1
dec di
dec si
movsw:
相当于
mov es:[di],word ptr ds:[si]
如果 df=0
add si,2
add di,2
如果 df=1
sub si,2
sub di,2
一般来说,movsb和movsw都和rep配合使用,格式如下:
rep movsb
或者
rep movsw
相当于
s:movsb
loop s
rep的作用是根据cx的值,重复执行rep后面的串传送指令
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call init_reg ;ds=0B800H,es=0B800H
call cpy_screen
mov ax,4C00H
int 21H
cpy_screen: mov cx,24
mov si,160
mov di,0
cpyScreenR:
push si
push di
mov cx,80
cld ;s: mov es:[di],word ptr ds:[si]
rep movsw ; add si,2
; add di,2
;loop s
pop di
pop si
add si,160
add di,160
loop cpyScreenR
ret
init_reg:
mov bx,0B800H
mov ds,bx
mov es,bx
ret
code ends
end start
PSW
标志寄存器PSW(程序状态字寄存器PSW)
标志寄存器PSW是一个16为的寄存器。它反映了CPU运算的状态特征并且存放某些控制标志。8086使用了16位中的9位,包括6个状态标志位和3个控制标志位。
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
OF DF IF TF SF ZF AF PF CF
popf: pop PSW
pushf: push PSW
终端向量表
系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储256个中断向量。
8086的cpu 0x00200后面是没有中断向量的,可以用来写程序。
系统引导时,处在实模式下,只可寻址1MB,为什么要用4个字节来寻址中断呢处理程序?
刚看到的时候,我也很纳闷。我们都知道编程的时候指针都是4个字节的,可以寻址4GB,在实模式下完全可以少用字节啊?其实实模式的内存选址方式是:段值 * 16 + 偏移值,就是内存地址。而这4个字节中,2个字节存储段值,两外两个存储偏移值。所以共有4个字节。
内存地址(十六进制) | 对应向量号(十六进制) | 中断用途 |
---|---|---|
BASIC中断向量 | ||
0x3C4 - 0x3FF | F1-FF | 未使用 |
0x218 - 0x3C3 | 86-F0 | BASIC程序运行时提供给BASIC解释程序作用 |
0x200 - 0x217 | 80-85 | 为BASIC保留 |
0x1E0 - 0x1FF | 78-7F | 未使用 |
0x1DC - 0x1DF | 77 | 硬件中断15 |
0x1D8 - 0x1DB | 76 | 硬件中断14 |
0x1D4 - 0x1D7 | 75 | 硬件中断13 |
0x1D0 - 0x1D3 | 74 | 硬件中断12 |
0x1CC - 0x1CF | 73 | 硬件中断11 |
0x1C8 - 0x1CB | 72 | 硬件中断10 |
0x1C4 - 0x1C7 | 71 | 硬件中断9 |
0x1C0 - 0x1C3 | 70 | 硬件中断 |
0x1A0 - 0x1BF | 68-6F | 未使用 |
0x180 - 0x19F | 60-67 | 为用户程序保留的单元 |
0x128 - 0x17F | 4A-5F | 保留 |
0x124 - 0x127 | 49 | 指向键盘增强服务变换表 |
0x120 - 0x123 | 48 | PC机使用,用于把PC机的键盘代码变换为标准的键盘代码 |
0x11C - 0x11F | 47 | 保留 |
DOS中断向量 | ||
0x118 - 0x11B | 46 | 第二硬盘参数块 |
0x114 - 0x117 | 45 | 保留 |
0x110 - 0x113 | 44 | PC机使用,用于指向低分辩率图形字符参数表 |
0x108 - 0x10F | 42-43 | 未使用 |
0x104 - 0x107 | 41 | 硬盘参数块 |
0x0C0 - 0x0CB | 34-40 | 未使用 |
0x0CC - 0x0CF | 33 | 鼠标中断 |
0x0C0 - 0x0CB | 30-32 | 未使用 |
0x0BC - 0x0BF | 2F | 多路服务中断 |
0x0B8 - 0x0BB | 2E | 基本SHELL程序装入 |
0x0AC - 0x0B7 | 2B-2D | 未使用 |
0x0A8 - 0x0AB | 2A | Microsoft 网络接口 |
0x0A4 - 0x0A7 | 29 | 快速写字符 |
0x0A0 - 0x0A3 | 28 | DOS安全使用 |
0x09C - 0x09F | 27 | 终止并驻留程序 |
0x098 - 0x09B | 26 | 绝对磁盘写功能 |
0x094 - 0x097 | 25 | 绝对磁盘读功能 |
0x090 - 0x093 | 24 | 严重错误处理(用户不能直接调用) |
0x08C - 0x08F | 23 | Ctrl+Break 处理地址(用户不能直接调用) |
0x088 - 0x08B | 22 | 程序中止时DOS返回地址(用户不能直接调用) |
0x084 - 0x087 | 21 | DOS系统功能调用 |
0x080 - 0x083 | 20 | DOS中断返回 |
数据表指针 | ||
0x07C - 0x07F | 1F | 图形字符扩展码 |
0x078 - 0x07B | 1E | 软盘参数块 |
0x074 - 0x077 | 1D | 视频参数块 |
提供给用户的中断 | ||
0x070 - 0x073 | 1C | 定时器控制的软中断 |
0x06C - 0x06F | 1B | Ctrl + Break控制的软中断 |
BIOS中断 | ||
0x068 - 0x06B | 1A | 时钟管理 |
0x064 - 0x067 | 19 | 引导装入程序–系统自举 |
0x060 - 0x063 | 18 | BASIC入口代码–ROM BASIC入口代码 |
0x05C - 0x05F | 17 | 打印机输出 |
0x058 - 0x05B | 16 | 键盘I/O |
0x054 - 0x057 | 15 | 盒式磁带I/O |
0x050 - 0x053 | 14 | RS-232串行通讯口I/O |
0x04C - 0x04F | 13 | 磁盘I/O |
0x048 - 0x04B | 12 | 测定存储器容量 |
0x044 - 0x047 | 11 | 设备检验 |
0x040 - 0x043 | 10 | 屏幕显示I/O |
8259中断向量 | ||
0x03C - 0x03F | F | LPT2控制器中断–并行打印机(IRQ7) |
0x038 - 0x03B | E | 磁盘控制器中断–软磁盘(IRQ6) |
0x034 - 0x037 | D | LPT2控制器中断–硬磁盘(并行口)(IRQ5) |
0x030 - 0x033 | C | 异步通信(primary)–串行通信接口1(IRQ4) |
0x02C - 0x02F | B | 异步通信(secondary)–串行通信接口2(IRQ3) |
0x028 - 0x02B | A | 彩色/图形(IRQ2) |
0x024 - 0x027 | 9 | 键盘(IRQ1) |
0x020 - 0x023 | 8 | 定时器(IRQ0) |
8088中断向量 | ||
0x01C - 0x01F | 7 | 保留 |
0x018 - 0x01B | 6 | 保留 |
0x014 - 0x017 | 5 | 打印屏幕 |
0x010 - 0x013 | 4 | 溢出 |
0x00C - 0x00F | 3 | 断点指令 |
0x008 - 0x00B | 2 | 非屏蔽中断 |
0x004 - 0x007 | 1 | 单步(用于DEBUG) |
0x000 - 0x003 | 0 | 除以零 |
中断过程
1. 取得终端类型码
2. 保存标志位寄存器 pushf
3. 将标志位寄存器的第八位TF和第九位IF设置为0
4. push cs
5. push ip
6. cs = n*4+2 ip = n*4
iret
等同于:
pop ip
pop cs
popf
db ‘ad’ 和 db ‘a’,’d’ 在内存中显示的都是 61 64
TF,IF
TF(Trap Flag)陷阱标志:当TF被设置位1时,CPU进入单步模式,所谓单步模式就是CPU在每执行一步指令后都产生一个单步中断。主要用于程序的调试。8086/8088中没有专门用来置位和清零TF的命令,需要用其他办法。
IF(Interrupt Flag)中断标志:决定CPU是否响应外部可屏蔽中断请求。IF为1时,CPU允许响应外部的可屏蔽中断请求。
中断过程
- 取中断类型码N
- 标志位寄存器入栈 IF=0 TF=0
- push cs, push ip
cs=N*4+2 ip=N*4
0号终端较为特殊,是除数为0 ,程序直接退出了,其他的还会返回程序继续执行。
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call cpy_new_int0
call set_new_int0
int 0
mov ax,4C00H
int 21H
set_new_int0:
mov bx,0
mov es,bx
cli
mov word ptr es:[0*4],7E00H
mov word ptr es:[0*4+2],0
sti
ret
new_int0:
push bx
push cx
push dx
push es
mov bx,0B800H
mov es,bx
mov bx,0
mov cx,2000
mov dl,'!'
show_asc: mov es:[bx],dl
add bx,2
loop show_asc
pop es
pop dx
pop cx
pop bx
iret
new_int0_end:
nop
cpy_new_int0:
mov bx,cs
mov ds,bx
mov si,OFFSET new_int0
mov bx,0
mov es,bx
mov di,7E00H
mov cx,OFFSET new_int0_end - new_int0
cld
rep movsb
ret
code ends
end start
;安装int 7CH中断程序,功能为显示一个用0结束的字符串,终端程序安装在0:7E00H
;dh 行号
;dl 列号
;cl 颜色
;ds:si 指向字符串首地址
assume cs:code,ds:data,ss:stack
data segment
db 'welcome to masm',0
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call cpy_new_int7CH
call set_new_int7CH
call init_reg
call show_masm
mov ax,4C00H
int 21H
init_reg: mov bx,data
mov ds,bx
mov bx,0B800H
mov es,bx
ret
show_masm: mov si,0
mov dh,15
mov dl,20
mov cl,2
int 7CH
ret
new_int7CH: call get_row
call get_col
call show_string
iret
show_string:push dx
push ds
push es
push si
push di
mov dh,cl
showString: mov dl,ds:[si]
cmp dl,0
je showStringRet
mov es:[di],dx
add di,2
inc si
jmp showString
showStringRet:
pop di
pop si
pop es
pop ds
pop dx
ret
get_col: mov al,2
mul dl
add di,ax
ret
get_row: mov al,160
mul dh
mov di,ax
ret
new_int7CH_end:
nop
cpy_new_int7CH:
mov bx,cs
mov ds,bx
mov si,OFFSET new_int7CH
mov bx,0
mov es,bx
mov di,7E00H
mov cx,OFFSET new_int7CH_end - OFFSET new_int7CH
cld
rep movsb
ret
set_new_int7CH:
mov bx,0
mov es,bx
cli
mov word ptr es:[7CH*4],7E00H
mov word ptr es:[7CH*4+2],0
sti
ret
code ends
end start
端口
in al,20H ;从20H端口读取一个字节
out 20H,al ;往20H端口中写入一个数据
in和out指令,只能使用ax或al存放从端口中的数据或发送到数据到端口中。
cpu能够读到的端口地址为 0–65535
端口直接和cpu通信。
cmos ram芯片包含了时钟和128个存储单元的ram存储器和电池。所以开机可以显示正确的北京时间。
cmos ram的地址端口为70H,数据端口为71H.
;读取cmos ram的2号单元内容
mov al,2
out 70H,al
in al,71H
;向cmos ram的2号单元写入0
mov al,2
out 70H,al
mov al,0
out 71H,al
shl,shr
shl 向左移动一位,
shr 向右移动以为。
当al=1000 0000时候,shl al,1 会得到 al=0000 0000 并且CF=1
;在屏幕中显示当前的秒数,0~DH 内存地址保存的是时间信息
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call init_reg
testA: call show_second
jmp testA ;不停的更新秒数
mov ax,4C00H
int 21H
init_reg: mov bx,0B800H
mov es,bx
ret
show_second: mov al,0
out 70H,al ;cpu发出命令,我需要端口70H中的0号单元的数据。
in al,71H ;存的是bcd码 二进制to十进制,比如12 那就是 0001 0010
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
and al,00001111B ;到这里 ah为0001 al为0010
add ah,30H ;加上30H可以直接显示ascii码
add al,30H
mov di,160*10 +40*2
mov es:[di],ah
mov es:[di+2],al
ret
code ends
end start
外中断
外中断指的是外设产生的中断。
外中断分为可屏蔽中断,不可屏蔽中断。
““
不可屏蔽终端:终端类型码为2
1 pushf IF=0 TF=0
2 push cs
3 push ip
4 IP=8H CS=AH
可屏蔽中断:
1取得终端类型码,保存标志位
2 pushf
3 IF=0 TF=0
4 push cs ,push ip
5 IP=n*4 cs=n*4+2
““
键盘中断
键盘提供了三种基本类型的键
1)字符键:传送一个ASCII码字符给计算机。如A~Z,0~9,%,$ 等。
2)扩展功能键:产生一个动作。如Home,End,Enter等,不能用标准ASCⅡ码表示的特殊键或组合键。
F1~F10:3B~44H
↑ ↓ ← → :48 50 4B 4DH
PgDn PgUp Ins Del: 50 48 4B 4D 52 53H
3)组合功能键:改变其它键所产生的字符码。如Alt,Ctrl,Shift等
字符码与扫描码
扫描码:键盘中断处理程序从8255的60H端口读取一个字节,该字节的低7位就是键的扫描码。键盘上的每一个键对应一个扫描码,从01H~51H。
字符码:BIOS键盘处理程序将所取得的扫描码转换成相应的字符码。大部分键的字符码为ASCII码,没有ASCII码的键其字符码为0或一个指定的操作(如屏幕打印等)。系统有自动转化的。键盘中断为int 9,如果自己写代码替换了int 9的中断向量指向,那么就需要你自己将扫描码等转化为字符码。
;键盘状态字节KB_FLAG:内存的0040:0017H单元,由类型 9的硬件键盘中断置入键盘的对应状态到该单元。低4位是Alt、Ctrl、Shift左、Shift右的标志位,这4位在相应键按下时置位,该键一抬起即复位。KB–FLAG的高4位是Ins、Caps Lock、Num Lock、Scroll Lock键的标志位,这些位在相应键奇次按下时置位,偶次按下时复位。
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call init_reg
call showKWstatus
mov ax,4C00H
int 21H
init_reg: mov bx,0B800H
mov es,bx
mov bx,40H
mov ds,bx
ret
show_status:push cx
push dx
push ds
push es
push si
push di
mov cx,8
showStatus: mov dx,0
shl al,1
adc dx,30H
mov es:[di],dl
add di,2
loop showStatus
pop di
pop si
pop es
pop ds
pop dx
pop cx
ret
showKWstatus: mov si,17H
testA: mov al,ds:[si]
mov di,160*10+40*2
call show_status
jmp testA
ret
code ends
end start
描述内存长度的标号
NUMBER db 1
mov NUMBER,80H
等价于:
NUMBER db 1
mov bx,OFFSET NUMBER
mov word ptr cs:[bx],80H
还可以写成这种格式: cs:NUMBER[si] 等同于 cs:[OFFSET NUMBER + si]
data segment
a db 1,23
b dw OFFSET a, seg a, OFFSET a, seg b ;offset获取偏移 地址,seg获取段地址
data ends
BIOS有一个键盘缓冲区,INT 9触发时,从60H端口读入键盘扫描码,保存到键盘缓冲区。INT 16H从键盘缓冲区取扫描,并转换ASCII码返回。等待输入就是当缓冲区为空时循环一直检查,直到有数据为止。
- 检测键盘缓冲区是否有数据
- 没有的话就继续检测
- 读取缓冲区第一个字型数据的键盘输入
- ah=扫描码 al=ascii
- 读取的字型数据删除