;本程序在VMWARE中调试通过,可以写入软盘,并正常执行
;可以使用Bochs 但是操作细节比较复杂
;程序界面不够友好
;程序的切换方式是 ESC 注意不要进入时间设置就退不出来了.
;在时间设置界面没有设置提示语句,进去就是黑屏幕,不过只要输入数字就会显示.注意输入 的
;数字的大小,世界上没有24:00:00以后的时间.我也懒得去判断输入了.
;输入后按回车或者超过6个有效 数字,程序会自动设置时间,并切换回主界面
;在Windows 下看不到时间的更改,原因是,Windows只在系统引导的时候读取CMOS时间,并把它的
;初值给8253,其余时间都由8253为操作系统提供时钟,因此尽管在Windows状态下可以更改CMOS
;中的时间,但是右下脚的时间是不会产生变化的,也就是说更改时间是看不到的.关机的时候,
;系统把8253中的时间经过计算后写回CMOS。这点在WINDOWS下调试程序应当注意.
;在Windows 2000以后的操作系统中,系统内核不允许直接端口操作,因此,在DEBUG中执行IN,OUT
;操作是无效的.因此端口的调试应在9X或DOS下进行,比如在虚拟机中的Windows 98中.
;中断嵌套问题。
;以一个8253作为中断处理芯片来算,则同时可能发生的最大中断数量为8级,因此堆栈必须至少足够
;8个以上中断的最大数目才不会发生可能的问题。
;286 以上系统使用两片8259A级联管理15级可屏蔽中断。从中断0到中断15。比较常见的分配方式:
; IRQ0 系统定时器
; IRQ1 键盘
; IRQ2 可编程中断控制器8259A
; IRQ3 COM2 ( 串口 )
; IRQ4 COM1 ( 串口 )
; IRQ6 软盘控制器
; IRQ7 并行口LPT1
; IRQ8 系统CMOS/实时钟
; IRQ12 PS/2鼠标
; IRQ13 数学协处理器
; IRQ14 第一IDE控制器(硬盘)
; IRQ15 第二IDE控制器 (CDROM )
; IRQ5 可用 (如声卡)
; IRQ9 可用 (如网卡)
; IRQ10 可用 (如USB)
; IRQ11 可用 (如SCSI主适配器)
;按照16级中断来处理,那么,在极端的情况下如果16级同时发生,则事先准备的堆栈区域必须满足
;16级中断的要求。每级中断至少14个寄存器。即14*16/8*16=448B, 堆栈应大于此数.
;CPU工作在 8086模式下,BIOS在初始化完系统后,在1M内存的使用情况
;384KB上位内存----- ************************
; * *
; * *
;保留 * *
; * *
; * *
;640KB常规内存----- ************************------9FFFFH
; * * BIOS扩展数据区 1KB
; ************************------9FC00H
; * *
; * * ___607KB
; * *
; * *
; ************************------07E00H
; * * 系统引导扇区安放位置 512B
; ************************ ------07C00H
; * *
; * * _______29KB
; * *
; ************************------00800H
; * * BIOS数据区 1KB
; ************************------003FFH
; * * BIOS中断向量表 1KB
; ************************------00000H
;
;只可以使用常规的640K中的没有被占用的空间。
Assume cs:code
Data Segment
Error03 db 'Disk Write-Protect Error','$' ;磁盘写保护错误
Error04 db 'Can Not Find The Right Sector','$' ;磁道寻找错误,软盘损坏
Error20 db 'Floppy Drive Error ,Maybe It Can Not Work !','$' ;软盘不存在或工作部正常
Error80 db 'No Answer From Disk Drive!','$' ;驱动器不响应
ErrorExit db 'Press Any Key To Try Again Or Press ESC To Quit','$'
Data Ends
Code Segment
Start:
;安装程序
;写入以下的程序到第一个和后面的扇区
;不需要确定程序的确切固定长度,因为int 13h只能按照512字节的倍数传递,所以即使比512多
;一个字节,也要传递512个。
mov ax,Data
mov ds,ax
mov ax,cs
mov es,ax ;要写的内存基址
lea bx,Real ;要写的内存偏移
mov ah,3 ;功能号,写
mov al,((ProgrameEnd-Start)/512+1);写扇区的数目,浪费一个扇区以获得编写的方便。
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面)
int 13h
;磁盘读写容错处理
cmp ah,0
je WriteFinish
cmp ah,03h
je WriteError03
cmp ah,04h
je WriteError04
cmp ah,20h
je WriteError20
cmp ah,80h
je WriteError80
WriteFinish:
mov ah,4ch
int 21h
WriteError03:
mov si,Offset Error03
jmp WriteErrorManage
WriteError04:
mov si,Offset Error04
jmp WriteErrorManage
WriteError20:
mov si,offset Error20
jmp WriteErrorManage
WriteError80:
mov si,Offset Error80
jmp WriteErrorManage
WriteErrorManage:
mov dh,4
mov dl,4
call near ptr Show_Str
mov si,Offset ErrorExit
mov dh,6
mov dl,4
Call near ptr Show_Str
mov ah,0
int 16h
Call near ptr ClearScrean
cmp al,1bh
jne Start
jmp WriteFinish
;实际程序体
Real:
;第一个扇区的数据
;第一个扇区只作为引导第二个扇区的引导程序 作用是把第二个扇区读到0:800处,
;并置cs:ip到0:800,原因是在引导现有的操作系统的时候,需要用到0:7c00内存区
;其中int 9h 需要占用0040:17作为状态字
;必须把0:7c00内存空闲出来。
;由于硬盘引导记录只能被存放到0:7c00处才能被正确引导,因此,需要在引导前把本程序
;移出内存。
;程序转移
;设置系统用堆栈内存区,由于没有操作系统支持,因此必须自己设定堆栈范围
;栈顶设置在9FBFF:0处,9FC00H(BIOS)保留数据区前一个位置.
cli ;避免堆栈设置时发生中断
mov ax,9000H
mov ss,ax
mov ax,0FBFFH
mov sp,ax
sti
;内存基址设置,一次设置,以后不用更改,因为程序小于64K
mov ax,0
mov ds,ax
mov es,ax
;读取第二和以后的扇区到内存的0:800处
mov bx,800h ;要读的内存偏移
mov ah,2 ;功能号,读
mov al,((ProgrameEnd-T)/512+1) ;读扇区的数目,浪费512个字节以获得编写的方便。
mov ch,0 ;磁道号
mov cl,2 ;扇区号
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面)
int 13h
;置CS,IP
mov ax,0
mov bx,800h
push ax
push bx
retf ;retf 相当于
;pop ip
;pop cs
;cs ip 的值不能通过普通的mov指令修改
;凑足一个扇区,主要满足int 19 对'55AA'标志的要求
;当前指令减去标号指令地址来计算应当的填充数据.
db 510-($- Real) dup( 'A')
DW 0AA55H
;一扇区以后的扇区的数据
T:
jmp ReturnPoint;实际程序代码区
;Data Segment
Reset db '1)Reset PC' ,0ah,0ah,0ah ;换行 ;'$'
StartS db '2)Start System',0ah,0ah,0ah ;'$'
Clock db '3)Clock',0ah,0ah ,0ah ;'$'
Set db '4)Set Clock','$'
;时钟设置界面提示信息
SetTimeMessage db 'Please Input The Right Time :','$'
TimeBuffer db 3 dup(0),'e'
;'$'的ASCII为36 即 0x24 会出现错误BCD 码中有0x24 e的ASCII为101 即 0x65
;判断是否为终止符号
Counter dw 0 ;用在时钟设置中的字符输入计数器
TimeShowBuffer db 6 dup('0'),'$'
;Data ends
ReturnPoint: ;程序返回点
;设置屏幕显示
;清屏
call ClearScrean
;由于使用静态地址分配,因此只有采用标号差来计算距离数据区的长度,最终确定数据区的位置。
mov si,(Reset-T+800H)
mov dh,4
mov dl,12
call Show_Str
;循环读取键盘缓冲区
ReadKeybordBuffer:
mov ax,0 ;实际参数传递为ah ,但要在循环读取中要清空 al 因此直接清空 ax
int 16h
cmp al,'1'
je RebootFar ;程序超过JE的最大跳转范围
cmp al,'2'
je StartSystemFar
cmp al,'3'
je TimeShow
cmp al,'4'
je Clock_SetFar
jmp ReadKeybordBuffer
Clock_SetFar:
jmp Clock_Set
RebootFar:
jmp Reboot
StartSystemFar:
jmp StartSystem
;时间显示
TimeShow:
call ClearScrean
;读取时间
TimeShowStart:
mov bx,(TimeBuffer-T+800H)
call ReadTime
;转换时间
mov bx,(TimeBuffer-T+800H)
mov si,(TimeShowBuffer-T+800H);偏移地址
call BCDChange
;向显示时间的格式转换
mov bx,(TimeShowBuffer-T+800H)
mov si,bx
call ChangeForShow
;屏幕显示
mov si,(TimeShowBuffer-T+800H)
mov dh,16
mov dl,20
call Show_Str
;检查键盘输入
mov ah,01h;检查缓冲区,若有字符则ZF=0,否则ZF=1 同时 AH=扫描码,AL=ASCII
int 16h ;在调用int 16h的过程中共需要多次PUSH数据,因此堆栈应满足需要(消耗100字节以内的内存)。
jnz CheckCharacter
jmp TimeShowStart
CheckCharacter:
mov ah,00h
int 16h
cmp ah,01h ;判断是否为ESC
je ExitTimeShow
jmp TimeShowStart
ExitTimeShow:
call ClearScrean ;清屏
jmp ReturnPoint
Clock_Set:
;清屏
Call ClearScrean
mov si,(SetTimeMessage-T+800H)
mov dh,3
mov dl,6
Call Show_Str
;获取TimeShowBuffer的偏移地址作为字符串输入的接收内存
mov si,(TimeShowBuffer-T+800H)
;清除切换界面后内存的数据残留问题
mov ax,0
mov [si-2],ax
mov al,'0'
mov bx,0
mov cx,6
ClearDump:
mov [si][bx],al
inc bx
loop ClearDump
;字符输入循环控制部分
CharInputControl:
;16号中断获取字符
mov ah,0
int 16h
;判断是否是数字
;判断是否小于0
cmp al,'0'
jnb next
;非数字则判断是否为控制字符
jmp ControlCharInput
;判断是否大于9
next:
cmp al,'9'
;数字则执行数字的输入
jna NumberCharInput
;非数字则判断是否为控制字符
jmp ControlCharInput
;控制字符判断
ControlCharInput:
;判断是否为左删除键
cmp al,08h
je BackSpace
;判断是否为回车
cmp al,0dh
je SetTimeNow
;判断是否为ESC
cmp al,1bh
je ReturnPointLong
;返回字符输入
jmp CharInputControl
ReturnPointLong:
jmp ReturnPoint
;是有效的数字输入
NumberCharInput:
;判断是否超过6个数字的输入
mov dx,[si-2]
cmp dx,5
ja SetTimeNow
;数字写入相应得内存区域
mov ah,0
;程序超过CALL最大长度,要使用CALL far PRT或者CALL near PRT 实现跳转,
;但是这样长跳并不能解决所有问题。
;因为长跳是CS IP入栈 并重新设置编译器计算的地址,而本程序的地址不能通过编译器计算,
;此时的call 和JMP 有很大的差别.
;只能实行近跳(Near)
;注意JUMP FAR PTR 和jump near ptr 的区别很大,尤其在静态地址分配问题上.
call near ptr CharactorInputControl
;显示刚才的输入数字
mov ah,2
mov dh,9
mov dl,11
Call near ptr CharactorInputControl
;返回字符输入
jmp CharInputControl
;处理左删除
BackSpace:
;字符出栈
mov ah,1
call near ptr CharactorInputControl
;屏幕显示出栈后剩余的字符
mov ah,2
mov dh,9
mov dl,11
call near ptr CharactorInputControl
;返回字符输入
jmp CharInputControl
;时间设置开始
SetTimeNow:
;设置偏移
mov si,(TimeShowBuffer-T+800H)
mov bx,si
call near ptr ChangeFromKeyboardInput
mov si,(TimeShowBuffer-T+800H)
mov bx,(TimeBuffer-T+800H)
call near ptr ChangeToBCD
mov si,(TimeBuffer-T+800H)
call near ptr WriteToCMOS
jmp ReturnPoint
;屏幕显示子程序,以'$'为结束标志
;以ds:si指向要显示的内存区,dh存放行,dl存放列
Show_Str proc near
;寄存器入栈保护
push es
push ax
push bx
push cx
mov ax,0b800h
mov es,ax
mov al,160
mul dh
mov bx,ax
mov dh,0
add dl,dl
add bx,dx
mov cx,bx ;为换行标志准备
LoopShow_Str:
;判断结束标志
cmp ds:[si],byte ptr '$'
je EndLoopShow_Str
;判断换行标志
cmp byte ptr ds:[si], 0ah
jne NoLF
add cx,160
mov bx,cx
inc si
jmp LoopShow_Str
NoLF:
mov al,ds:[si]
mov es:[bx],al
add bx,2
inc si
jmp LoopShow_Str
EndLoopShow_Str:
pop cx
pop bx
pop ax
pop es
ret
Show_Str endp
;BCD码向十进制转换得子程序
;ds:[bx]指向要处理的数据的内存地址
;es:[si]指向处理完成后数据存放的地址
;'$'指示处理内存的数据的终止符号。
BCDChange proc near
;寄存器保护
push ax
push cx
BCDChangeStart:
mov al,ds:[bx]
cmp al,'e' ;'$'的ASCII为36 即 0x24 会出现错误 e的ASCII为101 即 0x65
;判断是否为终止符号
je ExitBCDChange
mov ah,al ;保护AL
mov cl,4 ;设置移动位数
;高四位,并存储
shr ah,cl
mov es:[si],ah
inc si
;低四位
;清高四位
shl al,cl
shr al,cl
mov es:[si],al
inc si
inc bx
jmp BCDChangeStart
ExitBCDChange:
pop cx
pop ax
ret
BCDChange Endp
;数字向屏幕显示转化子程序
;ds:[bx]指向要处理的数据的内存地址
;es:[si]指向处理完成后数据存放的地址
;'$'指示处理内存的数据的终止符号。
ChangeForShow proc near
push ax
ChangeForShowStart:
mov al,ds:[bx]
cmp al,'$'
je ExitChangeForShow
add al,30h
mov es:[si],al
inc bx
inc si
jmp ChangeForShowStart
ExitChangeForShow:
pop ax
ret
ChangeForShow Endp
;时间读取子程序
;ds:[bx]指向存放数据的内存地址
ReadTime proc near
push ax
;时
mov al,4
out 70h,al
in al,71h
mov ds:[bx],al
inc bx
;分
mov al,2
out 70h,al
in al,71h
mov ds:[bx],al
inc bx
;秒
mov al,0
out 70h,al
in al,71h
mov ds:[bx],al
pop ax
ret
ReadTime Endp
;清屏子程序
ClearScrean proc near
push ax
push bx
push cx
push ds
mov ax,0b800h
mov ds,ax
mov cx,2000
mov bx,0
loopClearScrean:
mov ds:[bx],byte ptr ' '
add bx,2
loop loopClearScrean
pop ds
pop cx
pop bx
pop ax
ret
ClearScrean Endp
;转化用户的键盘输入到实际的数据输入的子程序
;ds:[si]指向要转化的内存区于
;ES:[bx]指向转化完成后的内存地址
;用'$'作为结束的标志
ChangeFromKeyboardInput proc near
push ax
StartChangeFromKeyboardInput:
mov al,ds:[si]
cmp al,'$'
je ExitChangeFromKeyboardInput
sub al,30h
mov es:[bx],al
inc si
inc bx
jmp StartChangeFromKeyboardInput
ExitChangeFromKeyboardInput:
pop ax
ret
ChangeFromKeyboardInput Endp
;十进制向BCD码转化的子程序
;ds:[si]指向要处理的内存区域用'$'作为内存结束标志
;es:[bx]指向处理完的内存区域
ChangeToBCD proc near
push cx
push ax
mov cl,4
StartChangeToBCD:
mov al,ds:[si]
cmp al,'$'
je ExitChangeToBCD
shl al,cl
inc si
mov ah,ds:[si]
add al,ah
inc si
mov es:[bx],al
inc bx
jmp StartChangeToBCD
ExitChangeToBCD:
pop ax
pop cx
ret
ChangeToBCD Endp
;数据输入和屏显子程序
;ah存放功能号,0表示入栈,1表示出栈,2表示显示
;ds:[si]指向字符栈空间
;ds:[si]的前面两个字节用于存放计数器的数值
;对于0号功能:(al)=入栈字符
;对于1号功能:(al)=返回的字符
;对于2号功能:(dh)、(dl)=字符在屏幕上显示的行、列位置
CharactorInputControl proc near
push bx
push dx
push di
push es
cmp ah,0
je charpush
cmp ah,1
je charpop
cmp ah,2
je charshow
jmp sret
charpush:
mov bx,[si-2]
mov [si][bx],al
inc word ptr [si-2]
jmp sret
charpop:
cmp word ptr [si-2],0
je sret
dec word ptr [si-2]
mov bx,[si-2]
mov al,[si][bx]
mov byte ptr [si][bx],0 ;清除数据
jmp sret
charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov bx,0
charshows:
cmp bx,[si-2]
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows
sret:
pop es
pop di
pop dx
pop bx
ret
CharactorInputControl Endp
;数据写入CMOS
;ds:[si]指向内存区域
;CPU速度太快,端口来不及取走数据,因此,必须设置CPU等待。
;不等待的话,总是设置错误,调了好几天才注意到.
;究竟等待多长,不清楚,因此干脆写入一个数据后,就一直从端口读出,和写入的比较,直到相同了,
;才设置下一个.
;数据从端口的读入读出时间正好使得CMOS有时间取走数据。
WriteToCMOS proc near
push ax
;时
mov al,4
out 70h,al
mov al,ds:[si]
mov ah,al
out 71h,al
;延时代码,CPU从端口取走数据会自动匹配端口速度,因此下列代码相当于延时,同时又提高了
;程序的执行速度和代码的简化
in al,71h
inc si
;分
mov al,2
out 70h,al
mov al,ds:[si]
mov ah,al
out 71h,al
inc si
in al,71h
;秒
mov al,0
out 70h,al
mov al,ds:[si]
out 71h,al
in al,71h
pop ax
ret
WriteToCmos Endp
Reboot:
mov ax,0ffffh
mov bx,0
push ax
push bx
retf ;retf 相当于
;pop ip
;pop cs
;cs ip 的值不能通过普通的mov指令修改
StartSystem:
;读取C盘的第一扇区到内存的0:7c00处
mov bx,7c00h ;读到内存中的地址。
mov dl,80h
mov ah,2 ;读磁盘操作
mov dh,0 ;0盘面
mov ch,0 ;0磁道
mov cl,1 ;1扇区
mov al,1 ;只读一个扇区
int 13h
;置CS,IP
mov ax,0
mov bx,7c00h
push ax
push bx
retf ;retf 相当于
;pop ip
;pop cs
;cs ip 的值不能通过普通的mov指令修改
;计算代码长度
ProgrameEnd:
Code ends
end Start