文章目录
标志寄存器(flag)
关于flag的两个指令:
pushf和popf:提供了直接访问标志寄存器的一种方式
pushf将标志寄存器的值压栈;
popf从栈中弹出数据送入flag。
-
ZF :0标志位。当前运算结果为0 为真,否则为假
-
PF :奇偶标志位。判断所有bit位中1的个数,偶数为真,奇数为假
-
SF :符号标志位。结果为负 则为真;非负为假
-
CF :进位标志位。记录运算数据最高有效位的进位/借位值。
配合adc指令:格式:adc object1,object2; 功能:object1 = object1 + object2 + CF 应用:adc配合add(要先进位)
配合sbb指令:
格式:adc object1,object2; 功能:object1 = object1 - object2 - CF 应用:sbb配合sub
-
OF :溢出标志位。数据超过机器表示范围为真
-
DF :方向标志位。控制每次操作后si、di的增减。
df = 0,每次操作后si、di递增;
df = 1,每次操作后si、di递减。
主要用于串传送。
关于串传送的3个指令:
movsb指令:mov es:[di] , byte ptr ds:[si]
if(df=0) inc si , inc di
if(df=1) si=si-1,di=di-1
应用:将ds:si指向内存单元中的字节送入es:di中,根据df进行增减;
movsw指令mov es:[di] ,word ptr ds:[si]
if(df=0) add si,2 ; add di,2
if(df=1) sub si,2 ; sub di,2
应用:同movsb,只是传送的数据为字型。
(两者配合 rep指令)
以rep movsb功能为例:
s:movsb
loop s
即,根据cx值,重复执行后面的串传送指令,循环实现cx个字符的传送。
注意:
一:数据交换类指令不会引起标志位的变化。
二:数据传送类指令不会引起标志位的变化。
三:算数运算类指令会影响标志位变化。
所以:
有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and 等,它们大都是运算指令(进行逻辑或算术运算);
有的指令的执行对标志寄存器没有影响,比如,mov、push、pop 等,它们大都是传送指令。
PS:关于OF,CF的区分以及cmp指令的配合,请看这里。
PS:关于不同指令对flag的具体影响,请看这里
端口
前言定义
和CPU通过总线相连的芯片除各种存储器,还有:
- 各种接口卡的接口芯片(网卡、显卡),芯片控制接口卡工作
- 主板上的接口芯片,cpu通过芯片对外设访问
- 其他芯片,存储相关系统信息/进行输入输出处理
anyway,CPU操控它们的时候,把这些芯片、存储器统一试做一个巨大无比的逻辑存储区,我们称之为内存地址空间。
这些芯片中,都由一组可由cpu读写的寄存器。
这些寄存器有以下特点:
- 都和cpu的总线相连(通过芯片)
- CPU对寄存器读写时通过控制线向芯片发送端口读写命令
所以从CPU角度,如同逻辑存储器一样,把这些寄存器都当做端口,统一编制,形成一个端口地址空间。
所以CPU可以直接读写以下3个地方的数据:
- cpu 内部寄存器
- 内存单元
- 端口
端口处理
虽然端口本质是芯片连接的存储器,但你不能像内存地址空间那样用mov、push、pop来操作。
端口读写指令有两条:in和out 。只能使用ax或al来存放从端口中读入/发送的数据。访问8位端口用al,16位用ax。
eg:in al,20h #从20h端口读入一个字节
out 20h ,al #向20h端口写入一个字节
中断
中断信息要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。
使CPU不再接着向下执行指令,而是转去处理这个特殊信息。
内中断
前言
中断来源
中断信息要包含识别来源的编码,来区分接受到的来源。
通常用中断类型码(字节型数据)来区分。
中断处理程序
收到终端信息后,通过我们编写的中断处理程序来处理。
而中断程序的入口(中断向量),则要凭借中断向量表来记录。
它通常在程序的内存地址0处。
(中断向量表中的一个表占两个字)
中断过程凭借call指令记录原来的CS:IP值,执行完中断处理程序切换。
中断过程
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- IP = N4 ,CS=N4+2
中断处理程序
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
iret指令作用:
pop IP
pop CS
popf
外中断
计算机除了能够运算,还要有I/O能力。
IO能力体现在两个问题上:
CPU从何处得到外设输入
外设输入首先送入相关的接口芯片的端口中,再由端口芯片送到cpu;CPU向外设输出也是端口→芯片→外设。CPU对外设输出的控制指令同理。
关键点在端口
CPU怎样得到外设输入
外设输入随时到达,cpu需要及时收到并处理。
这种情况的处理机制即外中断。
外中断有以下两种类型:
-
可屏蔽中断
可屏蔽,意味着CPU可以不响应这个外中断。响应与否的条件是标志寄存器IF位的设置:IF=1 响应中断,否则不响应。
可屏蔽中断信息来自cpu外部,中断类型码是通过数据总线送入cpu的。(相比而言,内中断在cpu内部产生)IF位指令:
sti #IF=1
cli #IF=0
-
不可屏蔽中断
CPU必须响应的外中断。8086cpu中,其中断类型码固定为2,所以不需要取码。其他流程同内中断。
几乎所有外设的外中断都是可屏蔽中断。不可屏蔽中断是系统的紧急情况。
INT指令
系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。
int指令能够主动触发内中断。
格式:int n
,n为中断类型码,功能是引发中断过程。
扩展(BIOS)
系统板的ROM中存放着一套程序,成为BIOS。BIOS主要有以下内容:
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于硬件设备进行I/O操作的中断例程
- 其他和硬件系统相关的中断例程
DOS也提供了中断例程。从os角度看,DOS的中断例程就是os向程序员提供的编程资源。BIOS和DOS的这些中断例程实现了程序员编程时经常用到的功能。
直接定址表
数据标号
定义示例
定义:包含内存单元地址和内存单元长度的标号。
例子:
assume cs:code
code segment
assume cs:code
code segment
a: db 1,2,3,4,5,6,7,8,9
b: dw 0
start:
mov si,offset a #mov si,0
mov di,offset b
mov cx,8
s: mov al,cs:[si] #mov al,a[si]
mov ah,0
add cs:[bx],ax #mov b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
如例子中的绿色字体,他们后面没有‘ :’,同时描述了地址和长度(长度由数据类型定义关键字确定)。
mov ax,b 相当于 mov ax,cs:[8]
mov b,2 相当于 mov word ptr cs:[8] , 2
mov al ,a[3] 相当于 mov al,cs:0[3]
其他段示例
定义示例是在代码段中定义数据。在其他段中使用数据标号还要注意:
assume cs:code,ds:data
data segment
a: db 1,2,3,4,5,6,7,8,9
b: dw 0
data ends
code segment
start:
mov ax,data
mov ds,ax
…
在代码段code中用data段的数据标号a、b,你就要用assume将一个寄存器和data相联。
想在段中用数据标号直接访问数据,要用伪指令assume将该标号所在的段和某个段寄存器联系起来,以确定标号的段地址所在寄存器。(段寄存器不会真的存放该段地址,只是声明作用。我们还需要在后续代码进行段操作)
标号当做数据定义示例
data segment
a db 1,2,3,4,5,6,7,8,9
b dw 0
c dw a,b #相当于 offset a,offset b
data ends
数据标号c处存储的两个字型数据为标号a、b的偏移地址
也可以是: c dd a,b
存储的两个双字型数据为标号a、b的偏移地址和段地址。
相当于c dw offset a,seg a,offset b, seg b
seg操作符:取得标号段地址
直接定址表
将给出的数据计算得到结果的方法,转化为给出数据作为查表的依据,通过查表得到结果的方法。
像这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为:直接定址表。
示例程序:
以十六进制形式在屏幕中间显示给定的字节型数据
:
;用al传送要显示的数据
showtyte:
jmp short show
table db ‘0123456789ABCDEF’ ;字符表
show:
push bx
push es
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1 ;右移4位,ah中得到高4位的值。
and al,00001111b ;al中为低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;用高4位的值作为相对table的偏移,取得对应字符。
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah ;显示高4位的十六进制字符码
mov bl,al
mov bh,0
mov al,table[bx] ;用低4位的值作为相对table的偏移,对得对应字符
mov es:[160*12+40*2+2],al ;显示低4位的十六进制字符码
pop es
pop bx
ret
定址表配合子程序
可以将子程序的入口地址存储在一个表中,类似一个函数指针数组。
下面的示例中,子程序的表中位置和功能号相对应:
功能号*2 = 对应的功能子程序在地址表中的偏移
实现一个子程序setscreen,为显示输出提供如下功能:
清屏;
设置前景色;
设置背景色;
向上滚动一行。
- 入口参数说明:
- 用ah寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行;
- 对于2、3号功能,用al传送颜色值,(al)∈{0,1,2,3,4,5,6,7}
- 各种功能的实现:
- 清屏:将显存中当前屏幕中的字符设为空格符;
- 设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;
- 设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;
- 向上滚动一行:依次将第n+1行的内容复制到第n行处,最后一行为空。
解决:
普通方法:
setscreen: jmp short set
table: dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3 ;判断功能号是否大于3
ja sret
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能计算对应子程序在table表中的偏移
call word ptr table[bx] ;调用对应的功能子程序
sret: pop bx
ret
直接定址表配合子程序:
setscreen:
cmp ah,0
je do1
cmp ah,1
je do2
cmp ah,2
je do3
cmp ah,3
je do4
jmp short sret
do1: call sub1
jmp short sret
do2: call sub2
jmp short sret
do3: call sub3
jmp short sret
do4: call sub4
sret: ret
子程序实现在这里:
;清屏
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
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
;设置背景色
sub3: push bx
push cx
push es
mov cl,4
shr al,cl
mov bx,0b800h
mov es,bx
mov bx,1
sub3s: and byte ptr es:[bx],10001111b
shl al,1
shl al,1
shl al,1
shl al,1
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cx
pop bx
pop bx
ret
;向上滚动一行
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld
mov cx,24
sub4s: push cx
mov cx,160 ;一行的长度为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 ds
pop es
pop di
pop si
pop cx
ret
实际上直接定址表的方法还能达到解耦的效果,方便扩充维护。