可看:
PS: 因为BIOS编程中INT 13h中断都是对磁盘操作,所以我放弃用DosBox改成VM虚拟机里装上32位XP系统。
这样在虚拟8086模式即dos下可以直接执行16位程序比如debug.exe,masm.exe还有link.exe
INT 9 键盘输入:
BIOS提供了int 9中断例程来处理键盘输出,一般完成int9中断例程后键盘输入都会放置到内存中的键盘缓冲区,这个缓冲区一共有16字长度,相当于32Byte,可以放15个按键的扫描码和相对应的ASCII码。高字节放入扫描码,低字节放入ASCII码。
键盘缓冲区是用环形队列结构管理内存区的,即FIFO先入先出。
INT 16h 键盘输入的读取 :
int 16h中断例程中的0号子程序可以从键盘缓冲区中读取一个输盘输入。调用方法:
mov ah, 0 ;ah指明了子程序编号
int 16h ;输出结果在ax,ah为扫描码,al为ascii码
例子, 这个例子是读取r,g,b会分别让dos窗口字体颜色变成红绿蓝= =
assume cs:code
code segment
start:
mov ah, 0 ;读取键盘缓冲区
int 16h
mov ah, 1 ;这个是前景色即001,蓝色
cmp al, 'r' ;是否为红色
je red
cmp al, 'g' ;是否为绿色
je green
cmp al, 'b' ;是否为蓝色
je blue
jmp short sret
;下面改变颜色的代码
red:
shl ah, 1 ;从001变成了010 变绿色
green:
shl ah, 1 ;从010变成了100 变红色
blue:
mov bx, 0b800h
mov es, bx
mov bx, 1 ;bx是第二个字节就是控制颜色
mov cx, 2000 ;执行80 * 25次,即整页
s:
and byte ptr es:[bx], 11111000b ;把原本字体颜色清除
or es:[bx], ah ;把新颜色存入
add bx, 2 ;保持控制颜色
loop s
sret:
mov ax, 4c00h
int 21h
code ends
end start结果如下图:
这个例子主要是为了演示一下int 16h中0号子程序的读取功能,下面说一下0号子程序的运行过程
1.首先不停扫描键盘缓冲区
2.如果没有发现任何字符就返回1步骤
3.读取字符
由于马上要开始IA-32架构的x32汇编(MASM),所以下面开始用IA-32指令集语法:
这个例子是实现堆栈功能:
.MODEL small ;这个指代模式,限制一个代码段,一个数据段,如果是x32汇编则是flat平坦模式意味着4BG内存都是任意使用,不存在段的概念了,就一个"段"
.STACK 100h ;堆栈
.386 ;最低CPU要求是80836处理器
.data ;数据段
.code ;代码段
main PROC
start:
call getstr ;这里跳转至getstr
mov ax, 4c00h
int 21h
charstack:
jmp short charstart
table dw charpush, charpop, charshow
top dw 0 ;栈顶指针
;选择对堆栈的操作
charstart:
push bx ;保护寄存器中的内容
push dx
push di
push es
cmp ah, 2 ;ah超过2表示不符合,因为只有0,1,2有功能
ja sret
mov bl, ah ;把功能号放入bl
mov bh, 0
add bx, bx ;加倍是因为直接寻址表中是以字为单位的,宽度是两字节
jmp word ptr table[bx] ;跳到charpush,charpop或者charshow
;堆栈的具体操作
charpush: ;ah = 0
; al is the character which should be pushed into the stack
mov bx, top
mov [si][bx], al
inc top
jmp sret
charpop: ;ah = 1
; al is the character which popped from the stack
cmp top, 0
je sret
dec top
mov bx, top
mov al, [si][bx]
jmp sret
;下面这段代码是显示字符
charshow: ;al = 2
mov bx, 0b800h
mov es, bx
mov al, 160
mov ah, 0
mul dh
mov di, ax
add dl, dl
mov dh, 0
add di, dx
mov bx, 0 ;为了和栈顶指针比较
charshows:
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 charshows
sret:
pop es ;结束后把堆栈中的内容返回
pop di
pop dx
pop bx
ret
;从键盘缓冲区中获取字符
getstr: ;先保护ax寄存器中的数据
push ax
getstrs:
mov ah, 0
int 16h ;读取键盘缓冲区
cmp al, 20h ;20h一下是控制字符,所以不会有显示
jb nochar ;小于就跳转至nochar
mov ah, 0 ;不然就把al中的字符push到堆栈
call charstack
jmp getstrs
nochar:
cmp ah, 0eh ;0eh是backspace的ascii码
je backspace
cmp ah, 1ch ;1ch是enter的ascii码
je enter1
jmp getstrs ;跳回getstrs段
backspace:
mov ah, 1 ;ah = 1意味着从堆栈中pop出一个字符
call charstack
mov ah, 2 ;ah = 2意味着pop掉一个字符后的堆栈内容显示出来
call charstack
jmp getstrs ;继续获取字符直到遇到回车键
enter1:
mov al, 0 ;表示字符串结束了
mov ah, 0 ;ah = 0意味着push一个字符进入堆栈
call charstack
mov ah, 2 ;显示
call charstack
pop ax ;把堆栈中的ax返回因为已经结束了
ret ;返回主程序
main endp
end main
到现在我们总结一下:
int 9中断例程的作用是把键盘输入放入到内存中的键盘缓冲区区域,这个缓冲区是以环形队列结构来管理的。每个字符都是通过60h号端口读出键盘扫描码,并且转换为ascii码后放入键盘缓冲区。
int 16h中断例程的0号子程序的作用是不停扫描键盘缓冲区然后把里面的字符读出,ah放扫描码,al放ascii码。
主要是这两个中断例程。
INT 13h 磁盘读取或写入:
书上使用3.5英寸软盘做例子的,所以先总结一下该软盘的参数:
3.5英寸软盘 :分上下两面,分别有两个磁头附在盘面上,每一面有80个磁道,每个磁道有18个扇区,每个扇区是512字节(这就是为什么主引导记录只有512B因为就只有一个扇区大小)。扇区号从1开始,磁道和磁头号从0开始。
3.5英寸软盘的入口参数:
(ah) = int 13h的功能号(2表示读取,3表示写入)
(al) = 读取的扇区数
(ch) = 磁道号
(cl) = 扇区号
(dh) = 磁头号
(dl) = 驱动器号 软盘从0开始(一般那时候的机器都有2个软盘驱动A和B),硬盘从80h开始即C盘
es:bx 指向接收从扇区读入数据的内存区
返回参数:
操作成功: (ah) = 0, (al) = 读入或写入扇区数目
(ah) = 出错代码
下面是实验17,我觉得题目中的一些资料很有用,所以我也记录一下:
用磁头号,磁道号,扇区号来访问磁盘是很不方便的,所以对于不同面和磁道中的扇区我们进行统一编号,即成为一种叫做逻辑扇区号的东西,这个概念是这么来的:
物理磁盘号: 逻辑磁盘号
0面0道1扇区 0
0面0道2扇区 1
...................................................................
0面0道18扇区 17
0面1道1扇区 18
...................................................................
1面0道1扇区 1440
..................................................................
所以可以看出来一面有1440个扇区,两面就是2880个扇区也就是一共有2880 * 512B = 1440KB 整个软盘差不多是1.44MB(和现在的U盘真是天壤之别啊。。。)
计算公式:
逻辑扇区号 = (磁头号 * 80 + 磁道号) * 18 + 扇区号 - 1
解释一下:如果磁头号是1,那就代表其中一面已经写满了(磁头号从0开始的), 一面一共有80个磁道,所以磁头号 * 80就意味着一面的总磁道数目,在加上磁道号(这里磁道号是第二面的磁道号)就是总共的磁道数目,在乘以18的意思是消耗掉的总共扇区数目(一个磁道有18个扇区),因为磁道号从0开始,所以还差一个磁道,这个磁道就是现在正在使用的那个,所以要加上扇区数。最后减去1是因为扇区号从1开始的,现在正在用的那个扇区是不算的
面号(磁头号) = 逻辑扇区号 / 1440
磁道号 = (逻辑扇区号 % 1440)/18
扇区号 = ((逻辑扇区号 % 1440) % 18) + 1
下面是实验17,上代码:
.model small
.STACK 100h
.386
.data
.code
main PROC
start:
mov ax, cs
mov ds, ax
mov si, offset int7chstart
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset int7chend - offset int7chstart
cld
rep movsb
mov word ptr es:[4 * 7ch], 200h
mov word ptr es:[4 * 7ch + 2], 0
mov ah, 1
mov dx, 1440
int 7ch
mov ax, 4c00h
int 21h
;这里是7ch中断例程的代码
int7chstart:
cmp ah, 1 ;ah只能是0或者1,即读或者写
ja none ;不然就直接返回主程序
;为了保护通用寄存器中的内容
push ax
push bx
push cx
push dx
push ax ;这是ah即功能号,还有al是写入扇区数目,压栈保护
mov ax, dx
mov dx, 0
mov cx, 1440
div cx
push ax ;磁头号
mov cx, 18
mov ax, dx
mov dx, 0
div cx
push ax ;磁道号
inc dx
push dx ;扇区号
pop ax ;把扇区号出栈至ax
mov cl, al ;al是因为扇区号最多是18个,不可能超过255,所以8位足够了
pop ax ;把磁道号出栈至ax
mov ch, al ;ch即磁道号
pop ax ;把磁头号出栈至ax
mov dh, al ;dh即磁头号
mov dl, 0 ;驱动器号
pop ax ;把功能号出栈
mov al, 1 ;写入的扇区数目
cmp ah, 0
je read
cmp ah, 1
je write
read:
mov ah, 2 ;因为int 13h中断例程本来ah = 2才是读操作,但是题目要求是0所以要转换一下
jmp short ok
write:
mov ah, 3 ;因为int 13h中断例程本来ah = 2才是读操作,但是题目要求是0所以要转换一下
ok:
int 13h ;调用13h中断例程
pop dx ;结束了所以可以把寄存器中的内容还回去
pop cx
pop bx
pop ax
none:
iret ;返回主过程,因为7ch号中断已经调用结束了
int7chend:
nop
main endp
end main
这个程序把前面中断例程的内容和现在的知识结合了起来,新写一个7ch中断,并且在中断中调用INT 13h中断例程的功能。相当于改变一下中断例程的使用条件,但是最终的结果还是要使用13h中断例程。
PS:到今天为止,8086汇编就复习总结结束了。接下去是x86汇编的复习总结和BIOS编程的深入复习,那个课程设计2下次在写了,因为要吃饭了。bye
(完)
接下去:BIOS编程-2