整体程序通过重写19号中断例程实现。在19号中断例程中调用不同的子程序,实现不同的功能。
1.动态显示时间的子程序
通过循环读取cmos中的时间信息实现动态显示时间。显示过程中还要实现对F1和esc键做出相应动作。但是,不能用16h号中断来获得相应的键,然后做相应的动作。因为要动态显示时间,必须要循环读取cmos中的时间。在循环中放入读取键盘输入的16h中断,当键盘输入缓冲区为空的时候,16h中断会阻塞,不能达到动态显示的效果。因此要通过9号中断响应键盘中断的方式处理键盘输入。重写9号中断处理程序,定义按下F1和ESC键时的动作。
在此子程序中注意保存原先9号中断的入口地址,因为要重写9号中断必须调用原先的9号中断处理与硬件相关的问题。在上面显示时间的部分,使用预先设定好的bh存放显示的颜色属性值,bl存放字符的ASCII码,这样只要修改bh值就可以修改时间显示的颜色。因此在9号中断中,如果判断按下的是F1键,直接修改bh。9号中断返回到循环读取cmos时间并显示的程序段中时,bh值是新的了,显示的颜色也变了。对于esc键,首先要实现按下esc后跳出无限循环读取并显示时间的程序段。如何跳出?因为在9号中断中,最后使用IRET返回。返回的地址为中断过程压入栈中保存的CS:IP指向的地址。所以我们可以通过在栈中修改CS:IP来实现IRET返回后跳到期望的地址处。我们期望的地址就是循环显示时间的程序段下面的地址showover(显示结束)。CS部分不用修改,只要修改IP。所以在此子程序中还要预先保存esc跳转的目的地址IP。ESC的处理还要包括恢复9号中断原来的处理程序地址。子程序准备结束。
两个bug:
(1)循环显示时间的时候,用si和di分别定位要显示的字符和在屏幕上要显示的位置。在循环开始之前先初始化了两个值。在loop循环中,会修改这两个值。因此在下次刷新时间的时候需要重新对着两个值进行初始化。这样才能保证显示在同一位置和显示内容的正确。之前使用的方法是在每次显示之前使用栈来保存si和di值,一次显示结束后,再用pop恢复si和di的初始值,执行下次循环显示。这里的问题是,在出现esc键盘中断的时候,栈的push和pop操作不能保证是成对出现的,导致错误。例如,显示时间的程序段刚好运行到显示月份,这时候栈中保存着用于初始化si和di的值。按下ESC后,引发键盘中断,保存标志寄存器、CS、IP,然后进入9号键盘中断。我们定义的ESC键盘中断处理直接修改了中断返回的地址,不会回到原先保存的地址处。当我返回到showover处的时候,问题出现了。showover主要做一些寄存器的恢复和子程序的返回。但是之前压入的si和di还在栈中,这样必然导致错误。对于F1键盘中断不存在这样的问题,因为它只是修改BH值,然后返回中断时的地址处继续执行显示时间的程序段。
这里面其实涉及到一个窗口问题。对于ESC中断,如果发生在最后pop结束准备下一个循环显示的时候,程序也不会出错。但是,这个概率是相当小的,不会导致出错的窗口是在太小。
(2)新的9号中断是在原先的19H中断中出现并处理的。原先引发的19h中断会将IF置0,即在19h中断期间不允许其他可屏蔽中断发生。大部分中断都属于可屏蔽中断,9号中断就是其中之一。因此要使9号中断得以起作用,必须在在这个循环显示时间的子程序中将IF置1,然后,在按下ESC后,程序跳转到showover处,子程序准备结束。这时候要把IF重新置0.
19h中断处理程序安装代码:
assume cs:code code segment start:mov ax,0 mov es,ax mov di,200h mov ax,cs mov ds,ax mov si,offset int19 mov cx,offset int19_e - offset int19 mov ax,offset rst - offset int19 add ax,200h mov [si+2],ax mov ax,offset stt - offset int19 add ax,200h mov [si+4],ax mov ax,offset showt - offset int19 add ax,200h mov [si+6],ax mov ax,offset sett - offset int19 add ax,200h mov [si+8],ax cld rep movsb cli mov word ptr es:[19h*4],200h mov word ptr es:[19h*4+2],0 sti mov ax,4c00h int 21h int19:jmp short s dw 0,0,0,0 ;存储子程序地址 dw 0,0ffffh ;第一个子程序需要转到的地址 dw 7c00h,0 ;第二个子程序需要的地址 s:push bx push ds mov bl,ah mov bh,0 add bx,bx mov ax,cs mov ds,ax call word ptr [bx+202h] pop ds pop bx iret rst:jmp dword ptr ds:[20ah] ret stt:mov ax,0 mov es,ax mov bx,7c00h mov al,1 mov ch,0 mov cl,1 mov dh,0 mov dl,80h mov ah,2 int 13h jmp dword ptr ds:[20eh] ret showt:jmp short showst db 9,8,7,4,2,0 db '// ::',0 dw 0,0 ;存储原先9号中断处理程序的段地址和偏移地址 dw 0 ;esc后,跳出循环后的地址 showst:push ax ;显示时间的子程序需要实现对F1键和esc键的相应动作,需要在此子程序中 push bx ;重新设定9号键盘中断处理程序。此程序结束后要恢复原先的中断处理程序 push cx push si push di push ds push es mov ax,cs ;cs=0 mov ds,ax ;ds=0 mov ax,0b800h mov es,ax mov bh,42h mov di,720h mov si,offset showt- offset int19 add si,202h mov ax,si add ax,12 mov bp,ax ;bp用于寻址原先的9号中断处理程序的地址 push ds:[36] ;保存原先9号中断处理程序的入口地址 pop ds:[bp] push ds:[38] pop ds:[bp+2] push cs ;设置新的9号中断处理程序地址 pop ds:[38] mov ax,offset int9 - offset int19 add ax,200h mov ds:[36],ax mov ax,offset showover - offset int19 add ax,200h mov ds:[bp+4],ax ;按下esc后跳转出来的地址 pushf ;打开IF标志,因为此时是在19h中断中,if=0,后面键盘中断无法响应 pop ax or ax,0200h push ax popf refresht:mov si,bp ;出现问题了,对于按下F1键,没问题,因为程序处理完会返回到中断的位置继续执行。 sub si,12 mov di,720h ;而按下esc键却要跳转到一个新位置执行。采用的方法是直接在栈内修改iret执行返回的地址IP mov cx,6 ;但是看下被键盘中断的程序段,循环显示时间的程序中有push si和push di的栈操作 lpt:mov al,[si] ;如果压栈结束被esc中断,我们确实可以跳到showover处执行。但接下来刚才压入的si和di并未 out 70h,al ;出栈,而是被当作原先栈中保存的内容出栈。显然会引起错误,程序也无法正确回到int19的主程序中 in al,71h ;简单说来,就是push和pop有很大可能不配对。解决方法:不使用栈来保存si和di。 push ax push cx mov cl,4 shr al,cl pop cx add al,30h mov bl,al mov es:[di],bx pop ax and al,0fh add al,30h mov bl,al mov es:[di+2],bx mov bl,[si+6] mov es:[di+4],bx inc si add di,6 loop lpt jmp short refresht showover:pushf ;显示时间程序结束,重新关闭IF pop ax and ax,0fdffh push ax popf pop es pop ds pop di pop si pop cx pop bx pop ax ret int9:push ax push bp in al,60h pushf call dword ptr ds:[bp] cmp al,3bh ;F1键 je sf1 cmp al,1 ;esc键 je sesc jmp short int9ok sf1:inc bh jmp short int9ok sesc:push ds:[bp] push ds:[bp+2] pop ds:[38] pop ds:[36] mov ax,ds:[bp+4] mov bp,sp mov [bp+4],ax int9ok:pop bp pop ax iret sett:ret int19_e:nop code ends end start
主程序:
assume cs:code,ds:data,ss:stack data segment table dw hint,reset,boot,clock,set_c hint db 'pls select the program you want to run','$' reset db 'reset pc ------- 0','$' boot db 'start system ----1','$' clock db 'clock -----------2','$' set_c db 'set clock -------3','$' data ends stack segment dw 64 dup (0) stack ends code segment start:mov ax,stack mov ss,ax mov sp,128 mov ax,data mov ds,ax mov ax,0b800h mov es,ax mov bh,0 mov dh,6 mov dl,16 mov cx,5 mov bp,0 s:push dx mov ah,2 ;置光标 int 10h mov dx,table[bp] ;显示字符串 mov ah,9 int 21h pop dx inc dh add bp,2 loop s mov ah,2 int 10h ;置光标,等待输入 push dx ;计算输入字符偏移地址的时候会修改dx,先保护之,供后面使用 mov al,160 mul dh mov dh,0 add dx,dx add ax,dx mov di,ax pop dx getch:mov ah,0 int 16h cmp ah,0bh je rst cmp ah,2 je stt cmp ah,3 je showt cmp ah,4 je sett jmp short getch rst:mov ah,0 jmp short ok stt:mov ah,1 jmp short ok showt:mov ah,2 jmp short ok sett:mov ah,3 ok:push ax mov ah,42h mov es:[di],ax e_b:mov ah,0 int 16h cmp ah,1ch ;回车 je int19 cmp ah,0eh ;退格 jne short e_b mov ah,2 int 10h mov al,0 mov ah,01110111b mov es:[di],ax pop ax jmp short getch int19:pop ax int 19h mov ax,4c00h int 21h code ends end start