2023年11月28日发(作者:)
1、 课程设计的目的、任务
1.1目的
编写一个可以自行启动计算机,不需要在现有操作系统环境中运行的程
序。
1.2任务
该程序完成的功能如下:
①列出功能选项,让用户通过键盘进行选择,界面如下。
reset pc ;重新启动计算机
;引导现有操作系统 start system
;进入时钟程序 clock
;设置时间 set clock
②用户输入“1”后重新启动计算机。
③用户输入“2”后引导现有的操作系统。
④用户输入“3”后,执行动态显示当前日期、时间的程序。
显示格式:年/月/日 时:分:秒,当按下 F1键后,改变显示颜色;按下 Esc
键后,回到主选单。
⑤用户输入“4”后可更改当前日期、时间,更改后返回到主选单。
2、 软件需求分析和设计
2.1分析
①学习汇编语言的主要目的:其一,为了“深入理解机器工作的基本原
理”,其二,“培养底层编程意识和思想”。为了提高自己对计算机工作基本原
理的认识和培养底层编程思想,所选题目在脱离当前操作系统的环境下运行,
摆脱了操作系统的束缚,实现了真正的对硬件编程。
②在DOS下编写安装程序,在安装程序中包含任务程序;
③运行安装程序,将任务程序写到软盘上;
④为了让任务程序可以在开机后自动执行,需要将任务程序写到软盘的0
道0面1扇区上(程序最后两个字节以55AA作为引导程序结束标志)。如果
程序长度大于512字节,则需要用多个扇区来存放,这种情况下,处于软盘
0道0面1扇区中的程序就必须负责将其他扇区中的内容读入内存。
2.2设计
(1)程序主体包含三个程序段,安装程序段(setupsg)、引导程序段
(initsg)、任务程序段(syssg)。
(2)安装程序流程图:
安装程序完成功能:
①将引导程序写入软盘第一扇区。
②将任务程序写入软盘第二扇区开始以后的3个扇区。
其中引导程序从0x7C00 到 0x7E00 共 512字节(写在软盘的第一扇
区);任务程序从 0x0000 到 0x0485 共1157字节(写在软盘的第二、三、
四扇区),所以引导程序、任务程序共占用软盘的4个扇区即可。
开始
(ES)←引导程序段地址
(BX)←引导程序偏移地址
(AH)←功能号03H,写扇区
(AL)←写扇区数04H,写4个扇区
(CH)←磁道号,0道
(CL)←扇区号,1号
(DH)←磁头号(面),0面
(DL)←驱动器号,0号软驱
调用BIOS INT 0x13H中断
向0号软驱写入4个扇区的数据
(AX)←0x4C00H
调用DOS INT 21H中断返回DOS系统
结束
(3)引导程序流程图:
引导程序完成功能:
①位于软盘第一扇区的引导程序,开机后被BIOS INT 19H中断服务
程序加载至内存 0x07C00位置。
②接着,引导程序负责将软盘中从第二扇区开始的3个扇区的任务程
序加载到内存0:8000H处,并设置CS:IP指向0:8000H处。
③引导程序必须是512字节,且以55AA作为结束标志,这样BIOS INT
19H中断才会认为该扇区的数据是引导程序,并将其加载至内存0x7C00处。
开始
设置下一条指令被加载至内存
0x7C00处
CLI→关中断,设置(IF)=0
避免在设置堆栈段时中断
设置栈顶指针SS:SP→0:0x7C00
STI→开中断,设置(IF)=1
允许响应外部中断
将任务程序加载至内存0:8000处
(ES)←0、(BX)←8000H
(AH)←功能号02H,读扇区
(AL)←读扇区数03H,读3个扇区
(CH)←磁道号,0道
(CL)←扇区号,2号
(DH)←磁头号(面),0面
(DL)←驱动器号,0号软驱
(AX)←0000H
(BX)←8000H
AX 入栈、BX 入栈
设置CS:IP→0:8000H
使程序跳转到任务程序
RETF(相当于 POP IP、POP CS)
重复定义数据,凑齐510字节数据
numbers equ $ - Offset init
times db 510-(numbers) dup (0)
定义变量字单元‘55AA’
作为引导程序结束标志
结束
(4)任务程序流程图:
任务程序完成功能:
①显示程序主菜单。
②根据用户输入,调用对应子程序。
其中调用对应子程序采用子程序入口地址直接定址表,定义如下:
systable dw sys_reset,sys_start,sys_clock,sys_setclock
CALL WORD PTR systable[bx]
当用户输入序列号:1(31H) →减30H→ 1 → 赋值给BL,BH清零 →
ADD BX,BX → (BX)=2,所以由此可以得到序列号1对应子程序入口地址
在表中的偏移地址,然后直接调用对应子程序。
开始
跳转显示菜单地址处
定义键盘输入字符栈、主菜单数据
子程序入口地址直接定址表
调用输出以0结尾的字符串子程序,
循环7次显示主菜单
调用BIOS INT 16H 0号功能中断,
接收键盘输入
将键盘缓冲区的ASCII码转换
AL←AL-30H
将输入的序列号1-4转换为0-3
BH←0,BL←AL-1
Y
BX < 0 ?
N
BX > 3 ?
N
Y
根据子程序入口地址直接定址
表调用对应子程序
CALL WORD PTR systable[bx]
子程序调用结束,返回
跳转处理用户输入状态,
等待用户继续输入
结束
(5)子程序1:重新启动计算机
①设置 CS:IP 指向 FFFF:0 单元,此处有一条跳转指令。
②CPU 执行该指令后,转去执行BIOS 硬件系统检测和初始化程序。
③初始化程序完成,建立BIOS所支持的中断向量,即将BIOS提供的
中断服务程序的入口地址登记在中断向量表中,以及安装BIOS中断处理程
序。
④硬件系统检测和初始化完成后,CPU 接收到一个BIOS INT 19H中断,
CPU根据中断类型号在中断向量表中找到该中断处理程序的入口地址,跳转
执行INT 19H的中断进程,即将硬盘(或软盘,根据BIOS开机启动项的设
置)的0道0面1扇区的512字节加载至内存0:7C00,进行操作系统的引导。
开始
AX ←0FFFFH
AX 入栈
AX←0000H
AX 入栈
设置CS:IP (RETF指令)相当于:
POP IP
POP CS
CS:IP→FFFF:0
结束
(6)子程序2:引导现有操作系统(从本地硬盘,默认为C盘)
①读取硬盘 C 的0道0面1扇区512字节数据到内存 0:7C00。
②将 CS:IP 指向 0:7C00,使CPU转去执行引导程序(即①中加载至
内存0:7C00处的程序)。
开始
ES:BX→0:7C00H
(AH)←功能号02H,读扇区
(AL)←读扇区数01H,读1个扇区
(CH)←磁道号,0道
(CL)←扇区号,1号
(DH)←磁头号(面),0面
(DL)←驱动器号80H,表示硬盘C
调用BIOS INT 13H中断
从硬盘C读取1个扇区的数据到内存
0:7C00处
(AX)←0000H
(BX)←7C00H
AX 入栈、BX 入栈
设置CS:IP→0:7C00H
RETF(相当于 POP IP、POP CS)
加载第一扇区引导程序至内存结束
CPU转去执行引导程序
结束
(7)子程序3:读取时钟
①读取系统当前日期、时间。系统日期以BCD 码形式存放在CMOS
RAM 存储时间单元,单元地址和日期对应关系如下:
0号单元:秒、2号单元:分、4号单元:时、
7号单元:日、8号单元:月、9号单元:年。
②将系统日期的BCD码形式转换为ASCII码,并动态显示时间。
③BIOS INT 08H时钟中断属于硬中断,每 1/18.5秒发生一次,其执行
的任务程序由BIOS程序控制,但是为了实现动态显示时间,将子程序3入
口地址设置在INT 08H中断向量表中,子程序返回时还原INT 08H中断向量
表中断处理程序的入口地址。
开始
关中断 IF→0
保存时钟中断偏移地址→int8H
段地址→int8H+2
ES:[8*4]← offset
start
ES:[8*4]←子程序3入口偏移地址
ES:[8*4+2]←cs
ES:[8*4+2]←CS(子程序段地址)
开中断 IF→1
start:调用清屏子程序
跳转时钟读取(read)地址处
定义时钟段数据,CMOS时钟存储单元数据表
read:SI←0、DI←0、CX←6
loop0: CX入栈
AL←CMOS 时间存储单元
偏移地址
70H地址端口←AL
AL←71H数据端口
AH←AL(时间单元BCD码形式)
时间单元BCD码→ASCII码
转换后的时间存储在变量cl1中
DI←DI+3
SI←SI+1
CX出栈
push es:[8*4]
pop int8h
push es:[8*4+2]
pop int8h+2
AL←clocktable[SI]
CL←4,AH右移4位,AH←AH+30H
AL←AL AND 0FH,AL←AL+30H
cl1[DI]←AH
cl1[DI+1]←AL
N
是Esc键,则返回,
还原INT 8H中断入口
地址,显示主菜单;
是F1键,则清除键盘
缓冲区,颜色变量自
增,跳转read
调用显示以0结尾字符串子程序
循环3次显示系统时间及提示信息
调用BIOS INT 16H 1号功能(不
清除键盘缓冲区),判断是否按
下Esc或F1
CX = 0 ?
Y
DH←6(行号),DL←30(列号)
BX←0(字符串表中的偏移地址)
AX←CS,DS←AX
CX←3,循环次数
clockprint_s:
CX 入栈
SI←clockdata[BX]
CL←clockcolor
CALL sys_showstr
BX←BX+2,DH←DH+2(换行显示)
CX 出栈
LOOP clockprint_s
结束
(8)子程序4:设置时钟
①DS:SI指向用户输入的时间字符串。
②将时间(两个字节表示的ASCII码)转换为一个字节表示的BCD码
形式,写入到CMOS对应的时间存储单元。
(9)子程序5:字符栈的入栈、出栈和显示。
①(ah)=功能号,0表示入栈、1表示出栈、2表示显示。
②DS:SI 指向字符栈空间:
对于0号功能:(al)=入栈字符;
对于1号功能:(al)=返回的字符;
对于2号功能:(dh)、(dl)=字符串在屏幕上显示的行、列位置,(cl)=颜
色。
(10)子程序6:字符的输入、输出、显示。
调用子程序4实现字符的输入、输出、显示。
(11)子程序7:显示以0结尾的字符串。
①入口参数: (dh)=行号,(dl)=列号,(cl)=颜色。
②DS:SI 指向字符串首地址。
(12)子程序8:清除屏幕。
①将 80*25 屏幕偶地址字节单元清0。
②将 80*25 屏幕奇地址字节单元赋值 07h
3、 程序实现说明
3.1程序实现过程
(1)各个程序段:
以下为各个程序段的大小及长度,对齐方式para(Paragraph 节)各个程
序段起始地址对齐到para,1 para = 16 Bytes 。
引导程序(initsg)512字节,任务程序(syssg)1157字节。
(2)安装程序:
负责将引导程序写入软盘第一扇区、任务程序写入软盘第二扇区开始以
后的3个扇区。以下为安装程序的关键代码:
(3)引导程序:
负责将软盘第二扇区开始的3个扇区数据加载至内存0:8000H处,加载结
束后并设置CS:IP指向0:8000H,使CPU转去执行任务程序。
以下为设置CS:IP指向0:8000H,接着重复定义数据保证引导程序为512字节,
且最后两个字节以55AA结束。
(4)任务程序:
①共包含8个子程序,主要完成4个功能。重新启动计算机、引导当前
操作系统、动态显示系统时间、设置系统时间。
②首先显示主菜单,使用BIOS INT 16H 中断的0号功能,等待用户输入
功能号;接着,处理用户输入,将用户输入的功能号(1-4)转换为(0-3)
在子程序入口地址表中,依据偏移量调用对应子程序;最后,子程序调用结
束返回主菜单继续等待用户输入。
③设置系统时间调用了:
子程序6:字符的输入、输出、显示(调用子程序5完成,子程序5:字
符栈的入栈、出栈和显示);
子程序7:显示以0结尾的字符串;
子程序8:清除屏幕。
3.2关键代码分析
(1)子程序5:字符栈的入栈、出栈和显示
①字符入栈:0号功能,(AL)=入栈字符。
②字符出栈:1号功能,(AL)=出栈字符。
③显示字符:2号功能,(dh)、(dl)=字符串在屏幕上显示的行、列位置,
(cl)=颜色。从栈底0号单元依次显示栈中字符,直到栈中字符全部输出。
(2)子程序6:字符的输入、输出、显示
①调用BIOS INT 16H中断的0号功能等待用户键盘的输入;
②将键盘缓冲区中的ASCII码赋值AL,判断AL,如果小于20H,则说
明不是字符,跳转非字符处理,大于20H,则按字符处理;
③非字符处理,如果是退格键,则调用子程序5的1号功能字符出栈,
然后显示栈中所有字符;如果是回车键,则调用子程序5的0号功能字
符入栈,将0入栈,显示栈中所有字符;
④字符处理,调用0号功能将字符入栈、2号功能显示栈中所有字符。
4、 程序总结
4.1软件完成情况
(1)由于任务程序需要写入软盘,在开机时设置开始启动项为软盘引导,进
而运行软盘中的任务程序。
首先,现在大多数计算机都没有软盘驱动器,而软盘也不常用,所以采用将
程序通过虚拟软驱写入软盘镜像(.img格式,1.44M),也就是用虚拟软驱来代
替软盘驱动器,软盘镜像文件代替软盘。
其次,将写好的软盘镜像文件加载到虚拟机的软驱中,并设置虚拟机开机启
动顺序,软驱优先。(虚拟机采用Oracle VM VirtualBox)
最后,将软盘镜像文件作为引导盘,启动虚拟机读取软盘镜像文件,运行软
盘镜像文件中的程序。
(2)程序并没有如期在虚拟机中得到正确运行。
首先,问题1:不能确定引导程序和任务程序是否已经写入软盘镜像文件。
其次,问题2:对虚拟软驱,软盘镜像文件的不了解。
最后,问题3:虚拟机的配置。
4.2收获与不足
(1)收获
通过对程序的调试,提高了汇编语言程序设计能力,查阅资料,了解了操作
系统启动的步骤,促使自己对操作系统、组成原理等核心等课程的学习。
(2)不足
程序还没有能在虚拟机中正常运行,希望查阅更多资料,完成配置,使其得
到运行。想法:如果将程序写入USB闪存存储器,程序在真实主机中便可运行。
源代码:
;============================================================
===========
;安装程序
;将任务程序写入到软盘
;============================================================
===========
;============================================================
===========
;BIOS中断INT 10H: 功能03H
;功能描述:写扇区
;入口参数:AH=03H
; AL=扇区数 CH=柱面 CL=扇区 DH=磁头 DL=驱动器,00H~7FH:
软盘;80H~0FFH:硬盘
; ES:BX=缓冲区的地址
;出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=
状态代码
;============================================================
===========
setupsg segment
assume cs:setupsg
setup:
mov ax,initsg ;ES:BX 指向引导程序开始处
mov es,ax
lea bx,init
;将引导程序安装到第一扇区
;任务程序从第二扇区开始安装(需要3个扇区)
mov ah,03h ;功能号03h,写扇区
mov al,04h ;写扇区数,4个扇区
mov ch,00h ;磁道号,0道
mov dh,00h ;磁头号(面),0面
mov cl,01h ;扇区号,1号
mov dl,00h ;驱动器号,0号软驱
int 13h
mov ax,4c00h ;安装结束,返回DOS系统
int 21h
setupsg ends
;============================================================
===========
;BIOS中断INT 10H: 功能02H
;功能描述:读扇区
;入口参数:AH=02H,其他参数参照功能03H
;============================================================
===========
;============================================================
===========
;引导程序,安装在软盘0道0面第一扇区,引导程序必须是 512 字节(一个扇区)
且以 55AA 作为结束标志
;开机启动后第一扇区的引导程序被 BIOS int 19 中断加载至内存 0:7C00 处
;该引导程序负责将软盘中其他扇区中的子程序加载至内存 0:8000处,并设置
CS:IP 指向 0:8000
;============================================================
===========
initsg segment
assume cs:initsg
org 7c00h
;避免设置堆栈时中断,关中段(IF=0) init: cli
;设置栈顶指向 0:7C00 mov ax,0
mov ss,ax
mov sp,7c00h
sti ;开中断(IF=1)
mov ax,0
mov es,ax ;读取软盘第二扇区和以后扇区的子程序到内存
0:8000处
mov bx,8000h
mov ah,02h ;功能号,读扇区
mov al,03h ;读扇区数,3个扇区
mov ch,00h ;磁道号,0道
mov cl,02h ;扇区号,2号
mov dh,00h ;磁头号(面),0面
mov dl,00h ;驱动器号,0号软驱
int 13h
mov ax,00h ;设置CS:IP
mov bx,8000h
push ax
push bx
retf ;用栈中数据同时改CS,IP,远转移
;相当于:
;pop ip
;pop cs
numbers equ offset init - $
times db 510-(numbers) dup (20h)
;凑足 510 字节
; $ 表示当前行被汇编后的偏移地址
;剩余两个字节由 55AA填充 dW 55AAH
initsg ends
; $$ 表示一个节的开始处被汇编后的地址。在这里程序只有1个节,
; 所以$$实际上就表示程序被编译后的开始地址,也就
是 0x7C00
; $-$$,它表示本行距离程序开始处的相对距离
;============================================================
===========
;第一扇区以后的数据
;子程序
;包含所有菜单需要调用的子过程
;============================================================
===========
syssg segment
assume cs:syssg
menu:
jmp near ptr menushow
int8h dw 0,0
;字符栈 charstacks db 128 dup(0)
top dw 0 ;字符栈栈顶
menudata dw offset md0,offset md1,offset md2,offset md3,offset md4,offset
md5,offset md6
md0 db "-------------------------",0
md1 db " Welcome ",0
md2 db " 1) Reset PC ",0
md3 db " 2) Start System ",0
md4 db " 3) Clock ",0
md5 db " 4) Set Clock ",0
md6 db "-------------------------",0
systable dw sys_reset,sys_start,sys_clock,sys_setclock
menushow:
mov dh,5
mov dl,30
xor bx,bx
mov ax,cs
mov ds,ax
mov cx,7
menushow_s:
push cx
mov si,menudata[bx]
mov cl,02h
call sys_showstr
add bx,2
add dh,2
pop cx
loop menushow_s
;处理用户输入
sys_input:
mov ah,0
int 16h
xor bx,bx
sub al,30h ;ASCII 码转为功能号
mov bl,al
sub bl,1 ;1-4 转换为 0-3
cmp bx,0
jb cycle
cmp bx,3
ja cycle
add bx,bx
call WORD ptr systable[bx] ;调用菜单功能
cycle:
jmp short sys_input
;============================================================
===========
;子程序0:重新启动计算机
;①设置 CS:IP 指向 FFFF:0 单元,此处有一条跳转指令。
;②CPU 执行该指令后,转去执行BIOS 硬件系统检测和初始化程序。
;③初始化程序完成,建立BIOS所支持的中断向量,
;即将BIOS提供的中断服务程序的入口地址登记在中断向量表中,以及安装中断
处理程序
;④硬件系统检测和初始化完成后,CPU 接收到一个 int 19中断,进行操作系统
的引导。
;============================================================
===========
sys_reset:
mov ax,0ffffh
push ax
mov ax,0h
push ax
retf
;============================================================
===========
;子程序1:引导现有操作系统(从本地硬盘,默认为C盘)
;①读取硬盘 C 的0道0面1扇区到 0:7C00。
;②将 CS:IP 指向 0:7C00。
;============================================================
===========
sys_start:
mov ax,0
mov es,ax
mov bx,7C00h
mov ah,02h ;功能号,读扇区
mov al,01h ;读扇区数,1个扇区
mov ch,00h ;磁道号,0道
mov cl,01h ;扇区号,1号
mov dh,00h ;磁头号(面),0面
mov dl,80h ;驱动器号,硬盘驱动器号 80h,表示 C盘
int 13h
;设置CS:IP 指向 0:7C00
mov ax,00h
mov bx,7C00h
push ax
push bx
retf
;============================================================
===========
;子程序2:读取时钟
;①读取系统当前日期、时间(BCD 码形式存放)
;CMOS RAM 的存储时间单元的地址:
;0:秒、2:分、4:时、7:日、8:月、9:年
;②将时钟BCD码转换为ASCII码,并动态显示时间
;(将该程序入口地址设置在int 8h中断向量表中,程序返回时还原int 8h中断入口
地址)
;============================================================
===========
sys_clock:
cli
mov ax,0
mov es,ax
push es:[8*4]
pop int8h ;保存原 int 8h 中断入
口地址
push es:[8*4+2]
pop int8h+2
mov WORD ptr es:[8*4],offset start ;设置新 int 8h 中断入口偏移地
址
mov es:[8*4+2],cs ;设置新 int 8h 中断入
口段地址
sti
start:
call cls
jmp short clockread
clockdata dw offset cl1,offset cl2,offset cl3
clockcolor db 02h
cl1 db 'yy/mm/dd hh:mm:ss',0
cl2 db 'press Esc return menu.'
cl3 db 'press F1 change color.'
clocktable db 9,8,7,4,2,0 ;各时间量在 CMOS RAM 中存放单元
clockread:
xor si,si
xor di,di
mov cx,06h ;循环读取6次
loop0:
push cx ;保存外循环计数器
mov al,clocktable[si] ;CMOS 9(87420)存储单元偏移地址赋值al
out 70h,al ;将要访问的CMOS存储单元写到 70h 地
址端口
in al,71h ;从 71h 数据端口读取数据到 al 寄存器
中
mov ah,al ;将读取到的时间 BCD 码形式赋值
ah
mov cl,4 ; BCD 码 → ASCII 码
shr ah,cl ;ah 中为时间的十位数码值
and al,00001111b ;al 中为时间的个位数码值
add ah,30h
add al,30h
mov cl1[di],ah
mov cl1[di+1],al
add di,3
inc si
pop cx ;还原外循环计数器
loop loop0
clockprint:
mov dh,6
mov dl,30
mov bx,0
mov ax,cs
mov ds,ax
mov cx,3
clockprint_s:
push cx
mov si,clockdata[bx]
mov cl,clockcolor
call sys_showstr ;显示系统时间
add bx,2
add dh,2 ;换行显示
pop cx
loop clockprint_s
mov ah,1
int 16h
cmp al,1bh ;判断是否按下 Esc 键
je clockreturn
cmp al,3bh ;判断是否按下 F1 键
je changecolor
jmp short clockread
clockreturn:
call cls
mov ah,0
int 16h
push int8h
push int8h+2
pop es:[8*4+2] ;还原 int 8h 中断入口地址
pop es:[8*4]
jmp near ptr menu
changecolor:
inc clockcolor
mov ah,0
jmp near ptr clockread
;============================================================
===========
;子程序3:设置时钟
;①
;②
;============================================================
===========
sys_setclock:
mov si,offset setsuccess
call sys_showstr
mov ah,0
int 16h
call cls
jmp near ptr menu
;ds:si 指向时间字符串
settime:
jmp short seting
settable db 9,8,7,4,2,0
seting:
mov bx,0
mov cx,6
settime_s:
mov dh,ds:[si]
mov dl,ds:[si+1]
add si,2
mov al,30h ;ASCII码 → BCD 码
sub dh,al
sub dl,al
shl dh,1
shl dh,1
shl dh,1
shl dh,1
or dl,dh ;将时间两个字节的ASCII码转换为一
个字节BCD码
mov al,settable[bx]
端口中
mov al,dl
out 71h,al ;将一个字节时间的BCD码形式写入
71h数据端口中
inc bx
loop settime_s
ret
;============================================================
====
;子程序4:字符栈的入栈、出栈和显示。
;参数说明:
;①(ah)=功能号,0表示入栈、1表示出栈、2表示显示;
;②ds:si 指向字符栈空间;
;对于0号功能:(al)=入栈字符;
;对于1号功能:(al)=返回的字符;
;对于2号功能:(dh)、(dl)=字符串在屏幕上显示的行、列位置,(cl)=颜色
;============================================================
=====
CHARSTACK PROC NEAR
jmp short charstart
table1 dw charpush,charpop,charshow ;子程序入口地址表
;top dw 0 ;字符栈栈顶指针
charstart:push bx
push dx
push di
push es
cmp ah,2 ;判断子程序功能号是否合法
ja sret ;非法返回子程序调用处
mov bl,ah
xor bh,bh
add bx,bx ;计算子程序入口地址,查表
定址
jmp WORD ptr table1[bx] ;跳转子程序入口处
charpush:mov bx,top
mov [si][bx],al ;将 al 中字符入栈
inc top ;栈顶指针加1
jmp sret ;返回子程序调用处
charpop: cmp top,0 ;判断字符栈中是否非空
je sret ;字符栈为空,返回
dec top ;字符栈非空,首先栈顶指针减
1
mov bx,top
mov al,[si][bx] ;出栈一个字符,送入al 寄存器
jmp sret ;返回子程序调用处
charshow: ;显示栈中所有字符
mov bx,0b800h
mov es,bx ;设置显存空间段地址
mov al,160
mov ah,0
mul dh ;计算要显示字符在屏幕的位
置
;ax = [ (dh)=行号 ×
(al)=160 字节 ]行偏移地址
mov di,ax
add dl,dl ;dl = [ (dl)=列号+(dl)=列号 ]
列偏移地址
xor dh,dh
add di,dx ;di = [ (di) + (dx) ] 在显存空间
的准确偏移地址
mov bx,0
charshows:
cmp bx,top ;判断字符栈是否非空
jne noempty
mov BYTE ptr es:[di],20h ;字符栈为空,在当前显存位置显
示一个空格
jmp sret ;返回子程序调用处
noempty: mov al,[si][bx] ;字符栈非空,将字符栈中从 0 号
单元处依次输出
mov es:[di],al ;将栈中的字符送入显存空间
inc bx ;bx 指向字符栈中下一个字符
add di,2 ;显存地址 加 2,显示一个字符
需要 2个字节的显存空间
jmp charshows ;循环显示栈中每一个字符,直
到 bx=top,即栈中所有字符已输出
sret: pop es
pop di
pop dx
pop bx
ret
CHARSTACK ENDP
;============================================================
===========
;子程序5:字符的输入、输出、显示。
;调用子程序4实现字符的输入、输出、显示
;============================================================
===========
getstr PROC NEAR
push ax
getstrs:mov ah,0 ;调用 BIOS int 16h中断
int 16h
cmp al,20h
jb nochar ;ASCII 码小于 20h,说明不是字符
mov ah,0
call CHARSTACK ;字符入栈
mov ah,2
call CHARSTACK ;显示栈中字符
jmp getstrs
nochar:
cmp ah,0eh ;退格键的扫描码
je backspace
cmp ah,1ch ;Enter 键的扫描码
je enters
jmp getstrs
backspace:
mov ah,1
call CHARSTACK ;字符出栈
mov ah,2
call CHARSTACK ;显示栈中字符
jmp getstrs
enters:
mov al,0
mov ah,0
call CHARSTACK ;0入栈
mov ah,2
call CHARSTACK ;显示栈中字符
pop ax
ret
getstr ENDP
;============================================================
===========
;子程序6:显示以0结尾的字符串
;①入口参数: (dh)=行号,(dl)=列号,(cl)=颜色
;②ds:si 指向字符串首地址
;============================================================
===========
sys_showstr:
push ax
push di
push es
mov ax,0b800h
mov es,ax ;设置显存空间段地址
mov al,160
mov ah,0
mul dh ;计算要显示字符在80×25屏幕行偏移位置
;ax = [ (dh)=行号 × (al)=160 字节 ]行偏移
地址
mov di,ax
add dl,dl ;dl = [ (dl)=列号+(dl)=列号 ] 列偏移地址
xor dh,dh
add di,dx ;di = [ (di) + (dx) ] 在显存空间的准确偏移地址
showstr_s:
mov ah,ds:[si]
cmp ah,'0'
je showstr_return
mov es:[di],ah
inc di
mov es:[di],cl
inc di
inc si
jmp showstr_s
showstr_return:
pop es
pop di
pop ax
ret
;============================================================
===========
;子程序7:清除屏幕
;①将 80*25 屏幕偶字节清0
;②将 80*25 屏幕奇字节置 07h
;============================================================
ret
syssg ends
end setup


发布评论