详细设计:
4.双任务设计和键盘中断:
更改键盘中断的中断向量表的cs、ip的值,使其指向自定义的int9程序,在int9程序中完成模拟中断时要做的准备,包括pushf,将可屏蔽中断标志位置零,push cs,push ip,然后调用dos系统的键盘中断程序,来处理中断底层操作。然后编写针对不同的扫描码,做出不同的响应。比如按下ESC键,返回到dos命令行,按下Tab键切换运行程序……
如下图所示,左上侧为将原有init9h键盘中断例程的cs:ip存储在data段的前两个单元中,左下侧为整个程序退出时恢复原有9号键盘中断例程的cs:ip,右侧为自定义键盘中断程序。
程序一开始运行贪吃蛇,闪烁光标在左边窗口右下角:
按下Tab键后去运行俄罗斯方块,贪吃蛇暂停,闪烁光标在右边窗口右下角:
再按下Tab键后,保存俄罗斯方块的相关数据,恢复贪吃蛇的数据,从中断前的位置继续移动:
此时按下ESC键后,退出程序,返回到dos命令行:
5.双任务调度:
在按下Tab键后,响应左右两个程序之间相互切换。程序一开始运行左侧的贪吃蛇游戏,左侧窗口右下角边框为闪烁光标,按下Tab键后,贪吃蛇暂停移动。左侧窗口右下角边框变回“*”,使右侧窗口右下角边框变为闪烁光标,示意要运行右侧程序。然后保存按下Tab键之前运行贪吃蛇时的所有寄存器的值和堆栈中的值,去执行右侧俄罗斯方块程序。
在俄罗斯方块下落过程中,此时按下Tab键,俄罗斯方块停止下落,右侧窗口右下角边框变回“*”, 左侧窗口右下角边框变为闪烁光标示意要运行左侧程序。然后保存按下Tab键之前运行俄罗斯方块时的所有寄存器的值和堆栈中的值,恢复之前保存的贪吃蛇的所有寄存器的值和堆栈中的值,去接着中断之前的地方执行,显示为贪吃蛇从暂停的地方继续前进。
此后按下Tab键后,要做的事均为切换右下角光标,保存一个程序数据,然后恢复另一个程序数据,接着执行被恢复的程序。此中重点在于数据的存储与恢复,存储时,先利用堆栈SP、BP存储中断前的CS、IP,因为此后执行指令会更改他们,然后存储ax、ds,因为在存储其他寄存器时,需要这两个寄存器来辅助,然后将剩余所有寄存器的值存储,然后根据SP的值,将堆栈中的值一一弹出并存储。此时中断前的所有寄存器的值已被存储,堆栈已被清空,其中的值已被存储。
恢复动作时,与存储动作相反,先恢复堆栈中的值,然后将CS、IP的值存储在栈顶,然后去借用ax、ds恢复其他寄存器,恢复完毕后,执行retf去弹出CS、IP,去执行CS、IP所指向位置的指令。
原理图如下,首次table启动teris的初始化,后续按下table键键入右侧的循环过程。
存储动作如下图所示:首先将存储在堆栈中的寄存其值弹出保存,然后为如左下角堆栈区,剩余堆栈中的其他数据,将这些值也弹出并保存后,堆栈变为空堆栈,供另一个游戏使用。右侧为存储部分代码。
恢复动作如下图所示:恢复一开始堆栈为空,然后将原有堆栈数据恢复,将数据段中存储的寄存赋值给相应寄存器,把将要执行的程序的当前位置的cs:ip放在栈顶,最后通过retf跳转指令完成将栈顶的cs:ip值赋值给cs、ip,完成恢复。
键盘中断和任务调度部分代码如下:
;新的int 9中断服务程序
int9:
push ax
push bx
push ds
push dx
push bp
mov ax,data
mov ds,ax
;接收键盘中断所得到的扫描码
in al,60h
;模拟中断程序的调用准备工作
;模拟标志寄存器入栈
pushf
;
pushf
pop bx
and bh,11111100b
push bx
popf
;模拟push cs、push ip
call dword ptr ds:[0]
;ESC按键检测
ESC_key_check:
cmp al,1
jne Tab_key_check
jmp near ptr program_end
;Tab按键检测
Tab_key_check:
cmp al,0fh
;若不是table按键则去判断是否是其他按键
jne to_select_response_keys
jmp tab_key_response
to_select_response_keys:
jmp near ptr select_response_keys
tab_key_response:
;置光标,并修改程序调度状态码
call sclbsc
;判断是否是第一次按table键
mov ax,data
mov ds,ax
inc word ptr ds:[6]
cmp word ptr ds:[6],1
;若不是第一次按table键,则去存储当前程序数据,然后恢复被中断之前程序数据
jne program_dispatch
;若是第一次按table键
mov ax,snake_tempporary_storage
mov ds,ax
mov bp,sp
;存储中断前ip
mov ax,[bp+10]
mov ds:[0],ax
;存储中断前cs
mov ax,[bp+12]
mov ds:[2],ax
;存储中断前ax
mov ax,[bp+8]
mov ds:[4],ax
;存储中断前ds
mov ax,[bp+4]
mov ds:[6],ax
;堆栈恢复到调用键盘中断程序int 9之前
pop bp
pop dx
;弹出ds
popf
pop bx
pop ax
;弹出ip
popf
;弹出cs
popf
;弹出标志寄存器
popf
;存储中断前通用寄存器
mov ds:[8],bx
mov ds:[10],cx
mov ds:[12],dx
;存储中断前段寄存器
mov ds:[14],es
mov ds:[16],ss
;存储中断前其他寄存器
mov ds:[18],si
mov ds:[20],di
mov ds:[22],sp
mov ds:[24],bp
;存储中断前标志寄存器
pushf
pop ax
mov ds:[26],ax
;准备存储中断前栈中数据
mov ax,128
sub ax,sp
mov bl,2
div bl
mov ah,0
;(cx)=中断前堆栈中数据个数
mov cx,ax
cmp cx,0
;(cx) <= 0则跳过存储堆栈数据动作
jna skip_storage_data_in_stack_0
;前28字节存储14个寄存器,往后存储堆栈中数据
mov bx,28
storage_data_in_stack_0:
pop ds:[bx]
add bx,2
loop storage_data_in_stack_0
skip_storage_data_in_stack_0:
;第一次Tab到右边窗口时,先去启动俄罗斯方块
jmp near ptr play_teris
program_dispatch:
;判断按table键前正在运行的是哪个游戏
mov ax,data
mov ds,ax
cmp word ptr ds:[4],1
jne is_right
;按table键之前运行的是贪吃蛇
mov ax,snake_tempporary_storage
jmp storage_this_grogram
;按table键之前运行的是俄罗斯方块
is_right:
mov ax,teris_tempporary_storage
jmp storage_this_grogram
storage_this_grogram:
;利用ds、ax、sp先存储ip、cs,因为存储其他寄存器和数据时,选用ds、ax来作为辅助,故ds、ax先存储
mov ds,ax
mov bp,sp
;存储中断前ip
mov ax,[bp+10]
mov ds:[0],ax
;存储中断前cs
mov ax,[bp+12]
mov ds:[2],ax
;存储中断前ax
mov ax,[bp+8]
mov ds:[4],ax
;存储中断前ds
mov ax,[bp+4]
mov ds:[6],ax
;堆栈恢复到调用键盘中断程序int 9之前
pop bp
pop dx
;弹出ds
popf
pop bx
pop ax
;弹出ip
popf
;弹出cs
popf
;弹出标志寄存器
popf
;存储中断前通用寄存器
mov ds:[8],bx
mov ds:[10],cx
mov ds:[12],dx
;存储中断前段寄存器
mov ds:[14],es
mov ds:[16],ss
mov ds:[18],si
mov ds:[20],di
mov ds:[22],sp
mov ds:[24],bp
;存储中断前标志寄存器
pushf
pop ax
mov ds:[26],ax
;存储中断前栈中数据
mov ax,128
sub ax,sp
mov bl,2
div bl
mov ah,0
;(cx)=中断前堆栈中数据个数
mov cx,ax
cmp cx,0
;(cx) <= 0则跳过存储堆栈数据动作
jna skip_storage_data_in_stack
mov bx,28
storage_data_in_stack:
pop ds:[bx]
add bx,2
loop storage_data_in_stack
skip_storage_data_in_stack:
;判断按table键前正在运行的是哪个程序,去恢复另一个程序
mov ax,data
mov ds,ax
;查看Tab后要运行哪个游戏
cmp word ptr ds:[4],1
jne to_recover_snake
;等于一时表示要运行俄罗斯方块
mov ax,teris_tempporary_storage
jmp to_recover_program_data
;不等于1时表示要运行贪吃蛇
to_recover_snake:
mov ax,snake_tempporary_storage
jmp to_recover_program_data
to_recover_program_data:
mov ds,ax
;恢复中断前ss
mov ax,ds:[16]
mov ss,ax
mov sp,128
;读出中断前sp
mov bx,ds:[22]
mov ax,128
;算出中断前堆栈所占字节数
sub ax,bx
mov bx,ax
add bx,26
;恢复中断前堆栈数据,顺便恢复了sp
recover_stack:
cmp bx,26
je recover_stack_end
push ds:[bx]
sub bx,2
jmp recover_stack
recover_stack_end:
;将ip、cs放在栈顶
push ds:[2]
push ds:[0]
;恢复中断前标志寄存器
mov ax,ds:[26]
push ax
popf
mov bp,ds:[24]
mov di,ds:[20]
mov si,ds:[18]
;恢复中断前es
mov ax,ds:[14]
;恢复通用寄存器
mov dx,ds:[12]
mov cx,ds:[10]
mov bx,ds:[8]
;读出ax
mov ax,ds:[4]
push ax
;恢复ds
mov ax,ds:[6]
mov ds,ax
;恢复ax
pop ax
retf
;根据程序调度码选择性响应键盘中断
select_response_keys:
;ds指向data段
push ax
mov ax,data
mov ds,ax
pop ax
;查看要运行哪个游戏,去恢复那个程序数据
mov dx,ds:[4]
cmp dx,0
jne to_teris_response_keys
;等于表示要运行贪吃蛇,去恢复贪吃蛇数据
jmp near ptr snake_response_keys
;不等于0则表示要运行俄罗斯方块,去恢复俄罗斯方块
to_teris_response_keys:
jmp near ptr teris_response_keys
;贪吃蛇响应按键
snake_response_keys:
;贪吃蛇游戏状态:(游戏中\已结束)
mov dx,snake
mov ds,dx
mov dx,ds:[10]
cmp dx,0
jne R_key_check
;得到蛇头的方向存到dx(snake:4和snake:6存储了蛇头的行、列所对应的段地址和偏移地址
;,根据此段地址和偏移地址得到蛇头的显示字符和显示属性,属性又代表了方向)
mov dx,snake
mov ds,dx
mov dx,ds:[4]
mov bx,ds:[6]
mov ds,dx
mov dx,ds:[bx]
;查看扫描码是不是W按键的扫描码
W_key_check:
cmp al,11h
;若不是W键则跳转去检测是不是A键
jne A_key_check
;蛇头朝下时,按W键无效
cmp dh,2
je to_int9ret
;若不是朝下,则更改蛇头方向为上(01h表示朝上、02h表示朝下、03h表示朝左、04h表示朝右)
mov word ptr ds:[bx],012ah
jmp near ptr int9ret
;查看扫描码是不是A按键的扫描码
A_key_check:
cmp al,1eh
;若不是A键则跳转去检测是不是S键
jne S_key_check
;蛇头朝右时,按A键无效
cmp dh,4
je to_int9ret
;若不是朝下,则更改蛇头方向为左
mov word ptr ds:[bx],032ah
jmp near ptr int9ret
;查看扫描码是不是S按键的扫描码
S_key_check:
cmp al,1fh
jne D_key_check
cmp dh,1
je to_int9ret
mov word ptr ds:[bx],022ah
jmp near ptr int9ret
;查看扫描码是不是D按键的扫描码
D_key_check:
cmp al,20h
jne R_key_check
cmp dh,3
je to_int9ret
mov word ptr ds:[bx],042ah
jmp near ptr int9ret
;R按键检测
R_key_check:
cmp al,13h
jne to_int9ret
;贪吃蛇游戏状态置0
mov ax,snake
mov ds,ax
mov word ptr ds:[10],0
;准备退出本子程序
pop bp
pop dx
pop ds
pop bx
pop ax
popf
popf
popf
;跳转去重新运行贪吃蛇
jmp near ptr play_snake
to_int9ret:
jmp near ptr int9ret
;俄罗斯方块响应按键
teris_response_keys:
;俄罗斯方块游戏状态:(游戏中\已结束)
mov dx,teris
mov ds,dx
mov dx,ds:[2]
cmp dx,0
jne Enter_key_check
;上键按键检测
up_key_check:
cmp al,48h
;若不是上键,则检测是不是下键扫描码
jne down_key_check
;清除屏幕上当前俄罗斯方块
call clean_this_teris
;旋转此俄罗斯方块坐标
call revolve_teris
;显示旋转后的俄罗斯方块
call show_this_teris
jmp int9ret
;下键按键检测
down_key_check:
cmp al,50h
jne left_key_check
;检测是否已到底部,若到达底部,则按下键无作为
mov ax,0100h
call try_to_move
cmp al,2ah
je int9ret
call clean_this_teris
;向下移动一行
mov ax,0100h
call move_teris
call show_this_teris
jmp int9ret
;左键按键检测
left_key_check:
cmp al,4bh
jne right_key_check
;检测是否还能左移,若不能,则按左键无作为
mov ax,00ffh
call try_to_move
cmp al,2ah
je int9ret
call clean_this_teris
;向左移动一列
mov ax,0ffh
call move_teris
call show_this_teris
jmp int9ret
;右键按键检测
right_key_check:
cmp al,4dh
jne Enter_key_check
;检测是否还能右移,若不能,则按右键无作为
mov ax,0001h
call try_to_move
cmp al,2ah
je int9ret
call clean_this_teris
;向右移动一列
mov ax,1
call move_teris
call show_this_teris
jmp int9ret
;Enter按键检测
Enter_key_check:
cmp al,1ch
jne int9ret
;俄罗斯方块游戏状态置0
mov ax,teris
mov ds,ax
mov word ptr ds:[2],0
;俄罗斯方块游戏得分归零
mov word ptr ds:[14],0
;准备退出本子程序
pop bp
pop dx
pop ds
pop bx
pop ax
popf
popf
popf
;跳转到重新启动俄罗斯方块
jmp near ptr play_teris
int9ret:
pop bp
pop dx
pop ds
pop bx
pop ax
iret
上一篇:汇编语言贪吃蛇、俄罗斯方块双任务设计实现详解(三)——俄罗斯方块详细设计
完整代码:https://download.youkuaiyun.com/download/gduyt_gduyt/10924302