【我所認知的BIOS】-->自己動手啟動計算機

时间:2020-12-06 03:42:00

 

我所認知的BIOS-->自己動手啟動計算機

LightSeed

2009-11-5

我当时在做《汇编语言》王爽(第一版)著的那本书的最后一题“课程设计二”的时候有关于启动OS的操作,想想这个题目对理解我们BIOSOS的控制权的交付有比较形象的理解,故在这里也和大家探讨一下。

1、软盘的引导扇区格式及原理

1.1引导扇区的格式

(笔者:以下这段是参考

http://hi.baidu.com/rabbit5455/blog/item/7f482fdf2dccc310632798ec.html的文章。)

FAT12DOS时代就开始使用的文件系统,目前软盘上也一直在用。

引导扇区是软盘的第0个扇区,在这个扇区中有一个很重要的数据结构叫BPBBIOS Parameter Block),下面是引导扇区格式,其中BPB_开头的属于BPB,以BS_开头的只是BOOT Sector的一部分,不属于BPB。见图1

 【我所認知的BIOS】-->自己動手啟動計算機

 

1 软盘的启动扇区格式

紧接着引导扇区的是两个完全相同的FAT表,每个占用BPB_FATSz16(0x9)个扇区;第二个FAT之后就是根目录区的第一个扇区,根目录区后面是数据区。根目录区的位置:引导扇区占1Sec,两个FAT表共18Sec,所以根目录区开始扇区号为19。根目录区中由若干个目录入口(Directory Entry)组成,最多有BPB_RootEntCnt个,所以根目录区大小不固定。每个Directory Entry32字节。

1.2 提取其中的原理

其实看了那么多,我们不妨投机取巧一下,发现了一个现象了没有?原来这个扇区的第一条指令是jmp。那么我们就可以在这里做文章了。因为这里用了jmp指令,那么CPU取指令的时候必定是到这里来了的。所以我们只需要在这添上合理的(小于510 byte)的程序来完成引导等其他的任务即可。故我们只是那软盘的引导扇区来做我们自己的动作而已。

2、我们想要实现的功能

在明知道有C盘的情况下通过软盘来启动。如果可以顺便实现自己的功能。(比如说显示时钟呀,输入密码呀等等什么的。。。自己加了。)实现的功能程序在启动扇区中的逻辑图,在第一个扇区中的代码会把从第二扇区开始的代码copy到相应的内存中去。见图2

 

 

【我所認知的BIOS】-->自己動手啟動計算機

2 启动扇区程序结构图

3、构思一下我们的程序架构

可以分成这么几个小块:

①安装启动代码到软盘的启动扇区模块

②重定位启动程序实体模块(因为自己写的程序,大多会超过512byte

③检测键盘输入输出模块

④针对输入输出可以分支处理(进入各个功能的子程序)

⑤调用本来OS的启动程序。(调用原本的07C00H处的程序)

4、代码实例

外甥打灯笼,我们来看看实际的代码并加注。

;********************************************************************

;Note::这段代码是参考一位前辈的不过不记得是哪里了---------

;             如果您发现了是哪里的code还希望您提示我一下--------

;             在全部code基础之上,我做了简化,目的是为了结构明显易懂----

;Function   大致實現了 重啟電腦,啟動電腦-----------------------------

;                    查看系統時鐘,修改系統時鐘四個功能---------------------

;PS     值得學習的地方是怎麼通過自製啟動扇區來引導現有的OS,并在引導

;             OS之前可加入自己的function

;

; By LightSeed-2009-10-27-----------------------

;********************************************************************

 

 

 

 

              ASSUME    CS:CODE

        CODE  SEGMENT

;**********************************************************************************

;安装程序模块:

;写入以下的程序到第一个和后面的扇区

;不需要确定程序的确切固定长度,因为int 13h只能按照512字节的倍数传递,所以即使比512

;一个字节,也要传递512个。

;**********************************************************************************

              ERROR00H  DB        'successful completion!',0

 

begin:

     

              MOV       AX,CS

              MOV       ES,AX       ;要写的内存基址

              MOV       DS,AX

              LEA       BX,REAL     ;要写的内存偏移

                    

              MOV       AH,3        ;功能号,写

              MOV       AL,((PROGRAMEEND-REAL)/512+1)       ;写扇区的数目,浪费一个扇区以获得编写的方便。

              MOV       CH,0        ;磁道号

              MOV       CL,1        ;扇区号

              MOV       DL,0        ;驱动器号

              MOV       DH,0        ;磁头号(面)

              INT       13H

                    

;////////////////磁盘写容错处理////////////////////////

              ;容错的代码请加在这里

         

;/////////////////////////成功写入,显示提示符并返回DOS////////////////////

WriteFinish:

      ;////////////////清屏////////////////////

              CALL      CLEARSCREAN

              ;清屏后,显示您想要显示的信息

              MOV       SI,OFFSET ERROR00H

              MOV       DH,10       ;显示子函数入口参数---显示的横行位置

              MOV       DL,20       ;显示子函数入口参数---显示的纵行位置

              PUSH      CX

              MOV       CL,00001111B

              CALL      SHOW_STR

              POP       CX

      ;//////////////键盘等待输入按任意键返回DOS////////////////////     

              MOV       AH,0

              INT       16H       

ok_WriteErrorManage:

      ;////////////////清屏////////////////////

              CALL      CLEARSCREAN

              MOV       AX,4C00H ;返回DOS

              INT       21H

            

 

;**********************************************************************************

;程序实体:(在启动扇区中实际存在的程序)

;第一个扇区的数据

;第一个扇区只作为引导第二个扇区的引导程序作用是把第二个扇区读到0:800处,

;并置cs:ip0:800,原因是在引导现有的操作系统的时候,需要用到07c00内存区

;其中int 9h 需要占用0040:17作为状态字

;必须把07c00内存空闲出来。

;由于硬盘引导记录只能被存放到07c00处才能被正确引导,因此,需要在引导前把本程序

;移出内存。

;程序转移

;设置系统用堆栈内存区,由于没有操作系统支持,因此必须自己设定堆栈范围

;栈顶设置在9FBFF:0,9FC00H(BIOS)保留数据区前一个位置.

;负责把自己要做的功能程序copy到内存中去的模块

;**********************************************************************************

Real:

              CLI                   ;避免堆栈设置时发生中断

              MOV       AX,3000H

              MOV       SS,AX

              MOV       SP,1024

              STI

                                                                     

                                   ;/////////////内存基址设置,一次设置,以后不用更改,因为程序小于64K,

                                   ;/////////////也为后面提供统一的地址偏移提供了方便

              MOV       AX,0

              MOV       DS,AX

              MOV       ES,AX

                                   ;/////////////读取第二和以后的扇区到内存的0800

              MOV       BX,800H     ;要读的内存偏移

              MOV       AH,2        ;功能号,读

                                                                                                       

              MOV       AL,((PROGRAMEEND-REALCODE)/512+1)   ;读扇区的数目,浪费512个字节以获得编写的方便。

              MOV       CH,0        ;磁道号

              MOV       CL,2        ;扇区号

              MOV       DL,0        ;驱动器号

              MOV       DH,0        ;磁头号(面)

              INT       13H

                                  

                                   ;/////////////CS,IP

                                   ;/////////////以下程序还有一种方法用jmp

              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');处于以后的空间用字符A来填充

              DW        0AA55H

 

;**********************************************************************************

;前面是引导第二扇区的程序,以下程序才是课程中的实体代码

;**********************************************************************************

 

realCode:

              JMP       START

 

       FIRST  DB        "ENTER 1 YOU WILL RESET COMPUTER.",0

      SECEND  DB        " ENTER 2 YOU WILL START SYSTEM .",0

       THIRD  DB        "  ENTER 3 YOU WILL WATCH CLOCK .",0

      FOURTH  DB        "   ENTER 4 YOU WILL SET CLOCK . ",0

  ENTERCLOCK  DB        "PLEASE ENTER THE TIME:",0

 ENTERFORMAT  DB        "AND LIKE 080808080808 IT MEANS 2008/08/08 08:08:08"

 RESETBUFFER  DW        0,0

         TOP  DW        0           ;栈顶

  DATABUFFER  DB        16 DUP (0)  ;如果这里不用0来填充,后面就会出大问题!

     

 

         YES  DB        'It is a leap year!',0

          NO  DB        'It is not a leap year!',0

  DATA_ERROR  DB        'Enter month Error! Please enter again.',0

  TIME_ERROR  DB        'Enter time Error! Please enter again.',0

            FORMATOKFLAG  DW        0

            LEAPYEARFLAG  DW        0

    DECIMALISTDATABUFFER  DB        16 DUP (0)  ;大一倍,给自己留点余量

   

 

start:    

 

      ;*****************以上初始化**************

     

      ;////////////////清屏////////////////////

              CALL      CLEARSCREAN

      ;///////////////显示主菜单//////////////

              CALL      DISPLAYMENU

     

              MOV       AH,0

              INT       16H         ;循环检测是否you健按下

      ;////////////////下两句是调试时用的////////////////////

 

              CMP       AL,31H

              JE        RESETPC

              CMP       AL,'2'

              JE        STARTSYSTEM

              CMP       AL,33H

              JE        WATCHCLOCK

              CMP       AL,34H

              JE        SETCLOCK

              JMP       SHORT START

     

ResetPC:

              CALL      RESET_PC  ;可以实现重启的功能,jmpf000:fff0

              JMP       SHORT START

StartSystem:

              CALL      START_SYSTEM      ;通过调用0:7C00来启动OS

              JMP       SHORT START

WatchClock:

              CALL      WATCH_CLOCK     ;实时查看时钟,功能的话就随便您加了

              JMP       SHORT START

SetClock:

              CALL      SET_CLOCK     ;设置时钟

              JMP       SHORT START

 

 

;**************************************************

;入口:ClearScrean                                  

;功能:dos清屏

;

;入参:无     

;回参:无                                        

;**************************************************

 CLEARSCREAN  PROC      NEAR

              PUSH      AX

              PUSH      BX

              PUSH      CX

              PUSH      ES

                                  

              MOV       AX,0B800H      ;这的清屏方式用了直接访问B800H处的内存。

              MOV       ES,AX

              MOV       CX,2000

              MOV       BX,0

loopClearScrean:

              MOV       ES:[BX],BYTE PTR ' '

              ADD       BX,2

              LOOP      LOOPCLEARSCREAN

                                  

              POP       ES

              POP       CX

              POP       BX

              POP       AX

              RET

 CLEARSCREAN  ENDP

;end

 

;**************************************************

;入口:DisplayMenu                                  

;功能:显示主菜单

;

;入参:无     

;回参:无                                        

;**************************************************

 DISPLAYMENU  PROC      NEAR

              PUSH      BX

              PUSH      CX

              PUSH      DX

              PUSH      SI

              PUSH      ES

              PUSH      DI

                         

              MOV       CX,4        ;循环次数为4

              MOV       SI,(FIRST-REALCODE+800H)            ;要显示的内存地址偏移量,因为显示子函数的入口参数要求ds:si指向要显示的字符窜

              MOV       BX,10       ;要显示的横行位置基数

              MOV       DH,10       ;显示子函数入口参数---显示的横行位置

loopDisplayMenu:    

              PUSH      CX          ;由于要用到CX故,先保存现场

              MOV       DL,20       ;显示子函数入口参数---显示的纵行位置

             ;mov cx,di                ;显示子函数入口参数---字符的颜色属性

            

              PUSH      CX

              MOV       CL,00001111B            ;显示子函数入口参数---字符的颜色属性

            

              CALL      SHOW_STR

              POP       CX

            

              INC       BX          ;显示的行往下移一行

              MOV       DH,BL       ;bl加上的1dh

              ADD       SI,34       ;表行加1

              POP       CX

              LOOP      LOOPDISPLAYMENU

            

              POP       DI

              POP       ES

              POP       SI

              POP       DX

              POP       CX

              POP       BX

              RET

            

            

 DISPLAYMENU  ENDP

;end

;***********************************************

;入口:show_str

;功能:字符串显示子程序

;入参:dh=行号(取值范围0~24);dl=列号(取值范围0~79);

;      cl=颜色属性,dSSI指向字符窜首地址。

;回参:无

;***********************************************

    SHOW_STR  PROC      NEAR

 

              PUSH      AX

              PUSH      BX

              PUSH      CX

              PUSH      SI

              PUSH      DI

              MOV       AX,0

              MOV       BX,0

              MOV       DI,0

               

              MOV       AL,0A0H     ;行增量16d

              DEC       DH          ;行号在显存中下标从0开始,所以减1

              MUL       DH          ;行起始地址,类似于段地址

              MOV       BX,AX       ;把显存的行起始值,存入BX

              MOV       AL,2        ;字符显示是两个字节的,故增量为2

              MUL       DL          ;计算列地址,类似于偏移量

              SUB       AX,2        ;列号在显存中下标从0开始,又因为偶字节存放字符,所以减2

              ADD       BX,AX       ;此时bx中存放的是行与列号的偏移地址,就是要显示的位置

              MOV       AX,0B800H

              MOV       ES,AX       ;es中存放的是显存的第0(0--7)的起始的段地址

       

        ;--------------以上为计算显存的偏移量,方法又是用的段+偏移的方法来实现

       

              MOV       DI,0

              MOV       AL,CL

              MOV       CH,0

       

mov_color_and_str:     

              MOV       CL,DS:[SI]  ;变址寻址

              CMP       CL,0        ;检查是否到字符串结束,因为标志为‘0,是则结束

              JE        OK

              MOV       ES:[BX+DI],CL           ;偶地址存放字符

              MOV       ES:[BX+DI+1],AL         ;奇地址存放字符的颜色属性

              INC       SI

              ADD       DI,2

              JMP       SHORT MOV_COLOR_AND_STR

               

ok:    

              POP       DI

              POP       SI

              POP       CX

              POP       BX

              POP       AX

              RET                   ;显示字符串的子程序[定义结束]

   

    SHOW_STR  ENDP

;end

;**************************************************

;入口:reset_pc                                  

;功能:使CS:IP指向 FFFF:0000 电脑重启

;

;入参:无     

;回参:无                                        

;*************************************************

    RESET_PC  PROC      NEAR

              PUSH      AX

              PUSH      BX

              PUSH      DS

       

                      ;mov ax,0ffffh

                       ;mov bx,0

                       ;push ax

                       ;push bx

                       ;retf ;retf 相当于

                          ;pop ip

                          ;pop cs

                          ;cs ip 的值不能通过普通的mov指令修改

             ;注掉的部分是用retf指令来实现far jump两者都可以实现,笔者有调试过      

              LEA       BX,RESETBUFFER

              MOV       AX,0H

              MOV       WORD PTR DS:[BX],AX

              MOV       AX,0FFFFH

              MOV       WORD PTR DS:[BX+2],AX

              JMP       DWORD PTR DS:[BX]

                         

              POP       DS

              POP       BX

              POP       AX

              RET

       

    RESET_PC  ENDP

;end

;**************************************************

;入口:watch_clock                                   

;功能:从CMOS读出时间并显示

;

;入参:无     

;回参:无                                        

;*************************************************

 WATCH_CLOCK  PROC      NEAR

              ;加入您的子程式,实时地看时钟信息

              RET

       

 WATCH_CLOCK  ENDP

;end

;

;**************************************************

;入口:set_clock                                  

;功能:设置时间,并写入CMOS

;

;入参:无     

;回参:无                                        

;*************************************************

   SET_CLOCK  PROC      NEAR

              ;加入您的子程序,去设置时钟

              RET

   SET_CLOCK  ENDP

;end

 

START_SYSTEM  PROC      NEAR

             ;读取C盘的第一扇区到内存的07c00

              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指令修改

                         

             RET

        

            START_SYSTEM  ENDP

;end

;***********************************************

;入口:DIVDW 除法子程序

;功能:进行不会溢出的DWORD/WORD除法。

;入参:dx=被除数高16位;ax=被除数低16位;

;      cx=16位除数,适用被除数DWORD,除数WORD

;回参:dx=商的高16位;ax=商的低16位;cx=余数

;***********************************************

 

       DIVDW  PROC      NEAR        ;子程序定义开始

              PUSH      BX

              PUSH      AX          ;低位AX进栈

              MOV       AX,DX       ;16位给AX,方便把高16位除以除数

              MOV       DX,0        ;DX清零方便把高16位的除法的余数存入

              DIV       CX

              MOV       BX,AX       ;把高位除出的结果存在BX

              POP       AX          ;低位出栈

              DIV       CX          ;自动把DXAX当作DWORD的被除数来做除法

              MOV       CX,DX       ;把余数存到CX

              MOV       DX,BX       ;

    

              POP       BX

              RET                   ;子程序定义结束

       DIVDW  ENDP

;end

 

ProgrameEnd:

 

        CODE  ENDS

              END       BEGIN