编写一个可以自行启动的计算机,不需要在现有操作系统环境中运行的程序
-----王爽汇编语言 课程设计2
一、相关资料
二、设计思路
(1)首先思考引导程序是干什么的?
答:引导计算机找到用户代码(引导程序就是把用户代码读到相应内存中,因为软(硬)盘的第一个扇区只有512个字节,有些稍大的程序不可能一次性就能放到第一扇区里面的,更多的是放在其它扇区,然后由引导程序(第一扇区的程序)负责把其它扇区的代码读到内存中,所以我们写的引导程序任务就是利用int13中断把我们的主程序读到内存中,然后跳转并执行)
(2)怎么把程序写入软盘?
答:首先我们利用int 13把引导程序写入软盘第一扇区,然后再把主程序写入其它扇区(引导程序应该知道在哪些扇区,好读取代码)
(3)怎么完成代码编写?
答:知道我们可以先编写功能3,4,(4的改时间功能在虚拟8086模式下不能真正改时间),然后在写功能1,2,当完成一个
小任务(或函数)时就及时测试,减少后面的负担,最后在真正真机测试,修改,测试。。。成功!
三、测试环境
虚拟机+win7 32位+一个u盘+dos系统
说明:因为虚拟机里装了一个win7所以就直接用它来测试了,首先我从网上下载了一个U盘dos系统制作工具,将下载的msdos7.1写入了u盘,然后制作一个软盘镜像,可以网上下载工具进行制作,我用的是自带的软盘镜像,准备工作做好后就可以开始了(当然还有写好的程序),设置虚拟机从u盘启动,然后进入dos系统,在纯dos系统下,运行编写好的程序(exe文件),将程序写入软盘扇区,最后设置虚拟机从软盘启动,如果一切顺利,那么就会进入的写的程序的主界面,当然也许运气不好,没法一次性成功,那么就只有检查程序,有可能把软盘写“坏”了,没关系,格式化一下就好,重复以上工作就好了。
(当然也可以不用win7系统,直接在虚拟硬盘里面装dos,这样就不需要u盘了,在dos下写扇区,这样引导的也就是dos系统了而不是win7系统)
注:不能在windos系统下直接运行程序写扇区,因为此时运行在虚拟8086模式,在windos系统保护下,没法写软盘。
下图节选自Windows环境下32位汇编语言程序设计.罗云彬.第三版
我的程序运行效果如下:
(1)写软盘(从u盘启动)
(2)从软盘启动后的主界面
(3)功能3(显示时间)
(4)功能3--q功能(改变颜色)
(5)功能4(设置时间)
修改后的效果
功能4中的数据合法性检测
1号功能就是重启,再次出现主程序界面(应该太快,没有截图)
2号功能就会引导win7系统,进入win7系统
四:程序源码参考
.model small
.data
error db 'fail' ;写入软盘是否成功
ok db 'success'
.code
start:
mov ax,cs
mov es,ax ;es:bx指向要写的数据开始位置
;==================================把引导程序写入磁盘的程序
lea bx,boot ;引导程序的偏移地址
mov al,1;写入的扇区数
mov ch,0;磁道号
mov cl,1;扇区号
mov dl,0;驱动器号(软驱从0开始,0:软驱A,1:软驱B,硬盘从80h开始,80h:硬盘C,81h:硬盘D)
mov dh,0;磁头号(面)
mov ah,3;int 13h的功能号,3表示写入扇区
int 13h
cmp ah,0
jne showerror
;============================把主程序写入磁盘的程序===============
;算出需要写入的写入的扇区数
lea bx,Main
lea ax,MainEnd
sub ax,bx
xor dx,dx
mov bx,512
div bx
add ax,1
lea bx,Main ;引导程序的偏移地址
mov ch,0;磁道号
mov cl,2;扇区号
mov dl,0;驱动器号(软驱从0开始,0:软驱A,1:软驱B,硬盘从80h开始,80h:硬盘C,81h:硬盘D)
mov dh,0;磁头号(面)
mov ah,3;int 13h的功能号,3表示写入扇区
int 13h
cmp ah,0
jne showerror
;================写入软盘成功
mov ax,@data
mov ds,ax
mov si,offset ok
mov di,160*7+26*2
mov cx,7
call print
over: mov ax,4c00h
int 21h
;===============写入软盘失败
showerror:
mov ax,@data
mov ds,ax
mov si,offset error
mov di,160*7+26*2
mov cx,4
call print
jmp over
;==================================引导程序===========================
org 7c00h
boot:
jmp BootStart
no db 'failure'
BootStart:
xor ax,ax
mov es,ax
mov bx,7e00h
mov al,4;读入的扇区数 (前面计算所得)
mov ch,0;磁道号
mov cl,2;扇区号
mov dl,0;驱动器号
mov dh,0;磁头号(面)
mov ah,2;int 13h的功能号,2表示从扇区读进内存
int 13h
cmp ah,0
je continue
call fail
jmp $ ;无限循环
continue: jmp bx
;================================磁盘读写失败显示===========================
fail proc
push ax
push es
push si
push bx
push cx
mov ax,0b800h
mov es,ax
xor si,si
lea bx,no
mov cx,7 ;字符串长度
FailStart:
mov al,cs:[bx]
mov es:[si],al
add si,2
inc bx
loop FailStart
pop cx
pop bx
pop si
pop es
pop ax
ret
fail endp
lea ax,boot
db 510-($- boot) dup(0)
dw 0aa55h ;以aa55结尾
;=========================================主程序===========================
ORG 7e00h
Main: jmp MainStart
;子功能直接定址表
keyNum dw resetPC,startSys,Clock,SetClock
;菜单字符串
str0 db ' System Startup Options: Made By ztgreat 2015/02/1 ',0
str1 db ' ===================================================',0
str2 db ' 1. Restart The Computer ',0
str3 db ' 2. Guide The Current System ',0
str4 db ' 3. Into Clock Program ',0
str5 db ' 4. Set Time ',0
str6 db 'Please input the number of the menu: ',0
stringtable dw offset str0,offset str1,offset str2,offset str3,offset str4,offset str5,offset str6
MainStart:
call clearScreen ;清屏
mov ax,cs
mov ds,ax
mov cx,68
xor bx,bx
mov di,160*2 ;提示信息首句,第2行第7列起
printf: mov si,cs:[stringtable+bx]
call print
add bx,2
add di,160+160;换两行
cmp bx,14
jne printf
;设置光标位置
mov ah,2
mov bh,0 ;页号
mov dh,13 ;行号
mov dl,116 ;列号
int 10h
; --------接收键盘输入
checkKey : mov ah,0
int 16h
mov bx,0b800h
mov es,bx
mov es:[14*160+36*2],al ;显示输入结果
call delay ;便于看到输入结果
;----------------数据合法性判断------------
cmp al,31h
jb checkKey
cmp al,34h
ja checkKey
xor bx,bx
mov bl,al
sub bl,30h ;将数字1~4的ASCII码转换为0~3
dec bl
add bx,bx ;bx=bx*2 换算出定址表地址
call clearScreen
call word ptr cs:keyNum[bx] ;根据直接定址表跳转到相应功能区
;若调用返回则重新生成菜单
jmp near ptr MainStart ;从头开始
;================字符串显示函数===========================
;入口参数说明:
;ds:si指向字符串地址
;di显存偏移地址
;cx字符长度
print proc
push di
push cx
push ax
push es
mov ax,0b800h
mov es,ax
@@ : mov al,[si]
mov byte ptr es:[di],al
inc si
inc di
mov byte ptr es:[di],00000010b
inc di
loop @B
pop es
pop ax
pop cx
pop di
ret
print endp
;=======================================重启计算机===============================
resetPC: jmp reset
p dw 0,0ffffh
reset: jmp dword ptr p
;========================================引导现有操作系统===========================
startSys:
mov ax,0
mov es,ax
mov bx,7c00h
mov al,1
mov ch,0
mov cl,1
mov dl,80h ;硬盘C
mov dh,0
mov ah,2
int 13h
push es
push bx
retf ;跳转到操作系统引导程序所在内存区:0:7c00h
;=================================循环显示时钟===========================
Clock proc
push ax
xor al,al
call setframe
C_S: call showClock ;显示时钟函数
in al,60h ;读取60h端口的键盘输入
cmp al,10h ;q键的扫描码为10h (因为电脑原因,改为q键)
je setcolor
cmp al,01h ;Esc键的扫描码为01h
jne C_S
pop ax
ret
setcolor:
push bx
push cx
mov ax,0b800h
mov es,ax
mov bx,160*12+30*2+1
mov cx,17
@@:inc byte ptr es:[bx]
add bx,2
loop @B
pop cx
pop bx
jmp C_S
Clock endp
;=================================设置显示时间边框============================
;al=0 时钟显示边框,否则为时钟设置时的边框
setframe proc
push cx
push ds
push di
push bx
push si
push cs
pop ds
xor bx,bx
mov cx,8
mov di,160*7+26*2 ;定位边框显示位置
lea bx,frametable ;时钟显示时的边框地址偏移
cmp al,0
je frame
lea bx,frametable2 ;时钟设置时的显示边框地址偏移
add cx,2
mov di,160*5+26*2 ;定位边框显示位置
jmp frame
framea db ' [<--] move left ',0
frameb db ' [-->] move right ',0
framec db ' [Esc] return menu ',0
framed db ' [Enter] set over ',0
frame0 db ' [q] change color ',0
frame1 db ' [Esc] return menu ',0
frame2 db ' ',0
frame3 db '=========================',0
frame4 db '== ==',0
frame5 db '== ==',0
frame6 db '== ==',0
frame7 db '=========================',0
frametable dw offset frame0,offset frame1,offset frame2,offset frame3,offset frame4,offset frame5,offset frame6,offset frame7
frametable2 dw offset framea,offset frameb,offset framec,offset framed,offset frame2,offset frame3,offset frame4,offset frame5,offset frame6,offset frame7
frame:push cx
mov cx,25
mov si,[bx]
call print
add bx,2
add di,160
pop cx
loop frame
pop si
pop bx
pop di
pop ds
pop cx
ret
setframe endp
showClock proc
push ax
push si
push bx
push dx
push di
push cx
jmp ShowStart
;日期时间单元号列表
cmos db 9,8,7,4,2,0 ;年,月,日,时,分,秒
signs db '/','/',' ',':',':',' ' ;分隔符
ShowStart:
xor si,si
xor di,di
mov ax,0b800h
mov es,ax
xor bx,bx
mov cx,6
show: push cx
mov al,cmos[si]
out 70h,al
in al,71h
;将BCD码转换为相应字符的ASCII码
mov ah,al
mov cl,4
shr ah,cl;右移4位让高4位变为第4位(得到0-9之间的数)
and al,00001111b ;获得低4位
add ah,30h ;获得各字相应的ASCII码
add al ,30h
mov byte ptr es:[160*12+30*2+bx],ah
mov byte ptr es:[160*12+30*2+bx+2],al
mov ah,signs[di]
mov byte ptr es:[160*12+30*2+bx+4],ah
add bx,6
inc di
inc si
pop cx
loop show
ShowRet:
pop cx
pop di
pop dx
pop bx
pop si
pop ax
ret
showClock endp
;===================================时间设置================================
SetClock proc near
push ax
push si
push es
push dx
push di
mov al,1
call setframe
call showclock
jmp set
bit db 0,2,6,8,12,14,18,20,24,26,30,32
set: mov si,0
mov ax,0b800h
mov es,ax
xor dh,dh
settime:
mov di,160*12+30*2
mov dl,bit[si]
add di,dx ;es:dx指向屏幕要修改的日期和时间的单元地址
mov byte ptr es:[di+1],97h ;控制移动当前要修改的日期和时间的单元地址,背景色为蓝色闪烁
mov ah,0
int 16h ;读取键盘缓冲区第一个字单元中的键盘输入
cmp ah,01h ;Esc键的扫描码为01h
jne next
jmp clockret
next:
cmp ah,1ch ;enter键的扫描码为1ch
jne next_left
jmp befor_enter
next_left:
cmp ah,4bh ;←键的扫描码为4bh
jne next_right
jmp left
next_right:
cmp ah,4dh ;→键的扫描码为4dh
jne next_1
jmp right
next_1:
cmp al,30h ;数字0的ASCⅡ码为30h
jb settime
cmp al,39h ;数字9的ASCⅡ码为39h
ja settime
next_2:
mov byte ptr es:[di],al ;修改当前的日期和时间的单元内容
mov al,0 ;清空al值,防止right重复检测al
jmp right ;修改完成自动跳向下一个修改字单元
jmp settime
clockret:
pop di
pop dx
pop es
pop si
pop ax
ret
;------------------------方向键向左移动--------------------------------
left:
cmp al,34h ;判断是否为数字键盘4,数字键盘ah=4bh,al=34h
jne lefts
jmp next_2
lefts:
mov byte ptr es:[di+1],02h ;恢复当前字节的背景色
cmp si,0
je lefte
dec si
jmp settime
lefte:
mov si,11 ;移动超界跳到最右边
jmp settime
;------------------------方向键向右移动--------------------------------
right:
cmp al,36h ;判断是否为数字键盘6,数字键盘ah=4dh,al=36h
jne rights
jmp next_2
rights:
mov byte ptr es:[di+1],02h ;恢复当前字节的背景色
cmp si,11
je righte
inc si
jmp settime
righte:
mov si,0 ;移动超界跳到最左边
jmp settime
befor_enter:
mov si,0
mov cx,6
mov ax,0b800h
mov es,ax
xor dh,dh
xor bx,bx
enters:
push cx
mov di,160*12+30*2
mov dl,bit[si]
add di,dx
mov ah,byte ptr es:[di]
sub ah,30h ;十位显示字符转化为数字
mov al,byte ptr es:[di+2]
sub al,30h ;个位显示字符转化为数字
mov ch,cmos[bx] ;ch指向CMOS RAM中要修改的单元地址
mov cl,4
shl ah,cl
add ah,al ;ah指向CMOS RAM中要修改的单元数据
call checktime ;时间设置错误检测
cmp cl,1
je setagain ;如果数据不合法则重新输入
mov al,ch
out 70h,al ;向地址端口70h写入要访问的单元地址
mov al,ah
out 71h,al ;向数据端口71h写入指定单元的数据
inc bx
add si,2
pop cx
loop enters
jmp clockret
setagain:pop cx ;把刚刚push的cx出栈,否则没有平衡栈,将会出错
jmp set
SetClock endp
;------------------------范围检测函数----------------------------------
;功能:(1) (bx)=9,代表年份,ah代表数据,不需要检测
; (2) (bx)=8,代表月份,ah取值范围(01-12)
; (3) (bx)=7,代表天数,ah取值范围(01-31)
; (4) (bx)=4,代表小时,ah取值范围(00-23)
; (5) (bx)=2,代表分钟,ah取值范围(00-59)
; (6) (bx)=0,代表秒钟,ah取值范围(00-59)
bcd_12 equ 00010010b
bcd_23 equ 00100011b
bcd_31 equ 00110001b
bcd_59 equ 01011001b
checktime proc
push bx
xor bh,bh
mov bl,ch
mov cl,0 ;0表示没有问题,1表示出现问题
cmp bx,8
je checkmouth ;检测月
cmp bx,7
je checkday ;检测日
cmp bx,4
je checkhour ;检测时
cmp bx,2
je checkminute ;检测分
cmp bx,0
je checksecond ;检测秒
jmp checkret
checkmouth: ;检查月份
cmp ah,1
jb moutherror
cmp ah,bcd_12
ja moutherror
jmp checkret
checkday: ;检查日合法性
cmp ah,1
jb dayerror
cmp ah,bcd_31
ja dayerror
jmp checkret
checkhour: ;检查时合法性
cmp ah,0
jb hourerror
cmp ah,bcd_23
ja hourerror
jmp checkret
checkminute: ;检查分合法性
cmp ah,1
jb minerror
cmp ah,bcd_59
ja minerror
jmp checkret
checksecond: ;检查秒合法性
cmp ah,1
jb secerror
cmp ah,bcd_59
ja secerror
jmp checkret
moutherror: mov bx,0 ;月份出现问题,bx对应月份错误代码号
mov cl,1 ;标志置1
call error2 ;显示错误
jmp checkret
dayerror: mov bx,2
mov cl,1
call error2
jmp checkret
hourerror: mov bx,4
mov cl,1
call error2
jmp checkret
minerror: mov bx,6
mov cl,1
call error2
jmp checkret
secerror: mov bx,8
mov cl,1
call error2
checkret: pop bx
ret
checktime endp
;==========================时间错误提示==============================
error2 proc
jmp errorstart
errort db 'mouth error,please change again! ',0
errord db 'day error,please change again! ',0
errorh db 'hour error,please change again! ',0
errorm db 'minute error,please change again! ',0
errors db 'second error,please change again! ',0
errorkey dw offset errort,offset errord,offset errorh,offset errorm,offset errors
errorstart: push ax
push ds
push si
push cx
push di
push cs
pop ds
mov si,cs:[errorkey+bx]
mov cx,34
mov di,160*17+23*2
call print
pop di
pop cx
pop si
pop ds
pop ax
ret
error2 endp
;=======================================清屏函数===========================
clearScreen proc
push ax
push es
push di
push cx
mov ax,0b800h
mov es,ax
mov di,0
mov cx,2000
clear: mov byte ptr es:[di],' '
add di,2
loop clear
pop cx
pop di
pop es
pop ax
ret
clearScreen endp
;===============================延时程序==============================
delay proc
push ax
push dx
mov dx,10h
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
delay endp
MainEnd:nop
end start