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

01扇区上(程序最后两个字节以55AA作为引导程序结束标志)如果

程序长度大于512字节,则需要用多个扇区来存放,这种情况下,处于软盘

001扇区中的程序就必须负责将其他扇区中的内容读入内存。

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:SP0: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:IP0:8000H

使程序跳转到任务程序

RETF(相当于 POP IPPOP 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]

当用户输入序列号:131H →减30H 1 赋值给BLBH清零

ADD BX,BX (BX)=2,所以由此可以得到序列号1对应子程序入口地址

在表中的偏移地址,然后直接调用对应子程序。

开始

跳转显示菜单地址处

定义键盘输入字符栈、主菜单数据

子程序入口地址直接定址表

调用输出以0结尾的字符串子程序,

循环7次显示主菜单

调用BIOS INT 16H 0号功能中断,

接收键盘输入

将键盘缓冲区的ASCII码转换

ALAL-30H

将输入的序列号1-4转换为0-3

BH0BLAL-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开机启动项的设

置)001扇区的512字节加载至内存0:7C00进行操作系统的引导。

开始

AX 0FFFFH

AX 入栈

AX0000H

AX 入栈

设置CS:IP (RETF指令)相当于:

POP IP

POP CS

CS:IPFFFF:0

结束

6)子程序2:引导现有操作系统(从本地硬盘,默认为C盘)

①读取硬盘 C 001扇区512字节数据到内存 0:7C00

②将 CS:IP 指向 0:7C00,使CPU转去执行引导程序(即①中加载至

内存0:7C00处的程序)

开始

ES:BX0: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:IP0:7C00H

RETF(相当于 POP IPPOP 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中断向量

表中断处理程序的入口地址。

开始

关中断 IF0

保存时钟中断偏移地址→int8H

段地址→int8H+2

ES:[8*4] offset

start

ES:[8*4]←子程序3入口偏移地址

ES:[8*4+2]cs

ES:[8*4+2]CS(子程序段地址)

开中断 IF1

start:调用清屏子程序

跳转时钟读取(read)地址处

定义时钟段数据,CMOS时钟存储单元数据表

read:SI0DI0CX6

loop0: CX入栈

ALCMOS 时间存储单元

偏移地址

70H地址端口←AL

AL71H数据端口

AHAL(时间单元BCD码形式)

时间单元BCD码→ASCII

转换后的时间存储在变量cl1

DIDI+3

SISI+1

CX出栈

push es:[8*4]

pop int8h

push es:[8*4+2]

pop int8h+2

ALclocktable[SI]

CL4,AH右移4,AHAH+30H

ALAL AND 0FH,ALAL+30H

cl1[DI]AH

cl1[DI+1]AL

N

Esc,则返回,

还原INT 8H中断入口

地址,显示主菜单;

F1,则清除键盘

缓冲区,颜色变量自

,跳转read

调用显示以0结尾字符串子程序

循环3次显示系统时间及提示信息

调用BIOS INT 16H 1号功能(

清除键盘缓冲区),判断是否按

EscF1

CX = 0 ?

Y

DH6(行号),DL30(列号)

BX0(字符串表中的偏移地址)

AXCS,DSAX

CX3,循环次数

clockprint_s:

CX 入栈

SIclockdata[BX]

CLclockcolor

CALL sys_showstr

BXBX+2,DHDH+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)各个程序段:

以下为各个程序段的大小及长度,对齐方式paraParagraph 节)各个程

序段起始地址对齐到para1 para = 16 Bytes

引导程序(initsg512字节,任务程序(syssg1157字节。

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,则按字符处理;

③非字符处理,如果是退格键,则调用子程序51号功能字符出栈,

然后显示栈中所有字符;如果是回车键,则调用子程序50号功能字

符入栈,将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

;功能描述:写扇区

;入口参数:AH03H

; AL=扇区数 CH=柱面 CL=扇区 DH=磁头 DL=驱动器,00H~7FH

软盘;80H~0FFH:硬盘

; ES:BX=缓冲区的地址

;出口参数:CF0——操作成功,AH00HAL=传输的扇区数,否则,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

;功能描述:读扇区

;入口参数:AH02H,其他参数参照功能03H

;============================================================

===========

;============================================================

===========

;引导程序,安装在软盘00面第一扇区,引导程序必须是 512 字节(一个扇区)

且以 55AA 作为结束标志

;开机启动后第一扇区的引导程序被 BIOS int 19 中断加载至内存 0:7C00

;该引导程序负责将软盘中其他扇区中的子程序加载至内存 0:8000处,并设置

CS:IP 指向 08000

;============================================================

===========

initsg segment

assume cs:initsg

org 7c00h

;避免设置堆栈时中断,关中段(IF=0 init: cli

;设置栈顶指向 07C00 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 001扇区到 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