一开始时思路错了,想着把所有按键的操作都写到 int9中断中,如果这样写会产生一系列错误,与要求也不相符。
看了看别人的思路后明白了,功能选择应该在主程序中利用 int 16中断来完成,而时钟功能中的esc返回功能和 f 1改变颜色应该借助 int 9中断来完成。
;课程设计2
;列出功能选项,让用户通过键盘进行选择,界面如下。
;(1)reset pc 重新启动计算机
;(2)start system 引导现有操作系统
;(3)clock 进入时钟程序
;(4)set clock 设置时间
assume cs:code,ds:data,ss:stack
data segment
db '1.reset pc '
db '2.start system'
db '3.clock '
db '4.set clock '
data ends
stack segment
db 256 dup(0)
stack ends
code segment
begin:mov ax,stack
mov ss,ax
mov ax,data
mov ds,ax
call anint9 ;安装新的int9中断例程
gn6:call show ;显示主菜单
jmp short en
gn3:call day ;功能3显示日期
gn3_1:call day_color ;功能3中的改变时钟颜色
jmp short gn3
gn4:call change_day ;功能4改变时钟
en:mov ah,0
int 16h
cmp ah,04h
je gn3
cmp ah,05h
je gn4
jmp short en
mov ax,4c00h
int 21h
show:push ax ;显示功能函数
push es
push bx
push cx
push ds
push di
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
shows1:mov byte ptr es:[bx],' '
add bx,2
loop shows1
mov bx,160*5+2*4
mov di,0
mov cx,4
shows2:push cx
mov cx,14
shows3:mov al,[di]
mov es:[bx],al
inc di
add bx,2
loop shows3
add bx,132
pop cx
loop shows2
pop di
pop ds
pop cx
pop bx
pop es
pop ax
ret
anint9:push ax ;安装新的int9中断例程
push ds
push cx
push es
push di
push si
mov ax,cs
mov ds,ax
mov ax,0
mov es,ax
mov si,offset int9
mov di,204h ;把新的int9安装到0:0204
mov cx,offset int9end-offset int9
cld
rep movsb
push es:[9*4] ;将原int9的入口地址保存到0:0200-0204
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
cli ;设置新的int9的中断向量表
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti
pop si
pop di
pop es
pop cx
pop ds
pop ax
ret
int9:
in al,60h
pushf ;调用原int9中断例程进行处理
call dword ptr cs:[200h]
cmp al,01h
jne r2
mov ax,seg gn6
mov si,offset gn6
jmp short retend1
r2:cmp al,3bh
jne retend2
mov ax,seg gn3_1
mov si,offset gn3_1
jmp short retend1
jmp short retend2
retend1:add sp,4
push ax
push si
mov ah,0
int 16h
retend2:
iret
int9end:nop
day:pop cx ;实时动态日期显示
pop cx
mov cx,2
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
days:mov byte ptr es:[bx],' ' ;显示日期前先清屏
add bx,2
loop days
day0:mov bx,160*12+40*0 ;显示日期的位置
mov dl,9
mov cx,6
day1:mov si,cx
mov al,dl
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
add al,30h
add ah,30h
mov byte ptr es:[bx],ah
mov byte ptr es:[bx+2],al
mov byte ptr es:[bx+4],':'
add bx,6
dec dl
cmp bx,792h
je day2
cmp bx,792h
ja day4
jna day3
day4:inc dl
day2:sub dl,2
day3:mov cx,si
loop day1
jmp short day0
ret
day_color:push ax ;更改时间颜色
push es
push bx
mov ax,0b800h
mov es,ax
mov bx,160*12+40*0+1
mov cx,6
colors:mov byte ptr es:[bx],2
mov byte ptr es:[bx+2],2
add bx,6
loop colors
pop bx
pop es
pop ax
ret
change_day:push ax ;更改CMOS时间
push es
push bx
push cx
push dx
mov ax,0b800h
mov es,ax
mov bx,160*10+40*0
mov cx,12
changes:mov byte ptr es:[bx],'0'
add bx,2
loop changes
mov bx,160*10+40*0
ch1:
mov ah,0
int 16h
cmp ah,1ch
je ch3
cmp ah,0eh
jne ch0
cmp bx,640h
je ch2
dec bx
dec bx
mov byte ptr es:[bx],'0'
jmp short ch2
ch0:cmp bx,658h
je ch2
cmp ah,02h
jb ch2
cmp ah,0ah
ja ch2
mov byte ptr es:[bx],al
add bx,2
ch2:jmp short ch1
ch3:
mov cx,6
mov dl,9
mov bx,160*10+40*0
ch4:push cx
mov ax,es:[bx]
sub ah,30h
sub al,30h
shl al,1
shl al,1
shl al,1
shl al,1
and ah,00001111b
or al,ah
mov ah,al
mov al,dl
out 70h,al
mov al,ah
out 71h,al
add bx,4
dec dl
cmp bx,160*10+6
je ce1
ja ce2
jna ce3
ce2:inc dl
ce1:sub dl,2
ce3:pop cx
loop ch4
pop dx
pop cx
pop bx
pop es
pop ax
ret
code ends
end begin
功能选择是调用 int 16的0号功能,判断用户输入的数字在结合 cmp 和跳转指令去调用相关功能。
而时钟程序中的符 f 1功能和 esc返回主菜单功能 是靠重写int 9 中断对其输入进行处理。
在主程序中主菜单显示功能处有其调用标号,也有改变时钟颜色的调用语句及其调用处标号,
在int 9 中先判断输入,如果输入为 f 1或 esc则分别在获得主程序中调用其功能处标号的段地址和偏移地址,想要去执行他们就要改变cs 和 ip就可以了,这时就可以利用int 9 中段中的 iret返回指令,其会把栈顶的两个元素设为cs 和 ip,所以可以把栈顶的元素换成先前保存的 主程序中调用f1
功能和 esc 功能(显示主菜单)标号处的cs 和 ip。
注意:
(1)不要利用int 9 判断输入后直接call相关功能,因为这样你的int 9中段是没有执行完的,栈底会残留一开始中段过程压入的标志位寄存器和cs ip,因此多次调用int 9后会使栈发生溢出。
(2)在重写int 9的时候在每次输入的时候如果不是 f1 或 esc则键盘缓冲区会把其保存下来,所以在esc返回主菜单中继续选择功能时利用无限循环先把键盘缓冲区要清理干净,再继续判断输入的功能号
(3)因为实时显示时钟是靠循环读取cmos呈现的,所以显示时钟的子程序其ret指令是永远不会执行的,所以要在前面把call时压入栈的cs ip取出,防止多次调用其使栈发生溢出。
(4)在改变时钟颜色时注意调用完颜色改变程序后就立刻去继续执行时钟显示程序,实现实时时钟颜色改变