BIOS编程-1

可看: https://blog.csdn.net/qq_37232329/article/details/79939184

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

阅读更多

没有更多推荐了,返回首页