构建自己的操作系统 1

 

文章翻译自:www.codeproject.com

原作者:Shalom Keller.
关键字:Building your own operating system

翻译:FISH

日期:2007-08-27

 

 

介绍:

如果你了解操作系统的工作原理, 这对你的编程会大有裨益,特别是对那些开发设备驱动的系统程序员。即使是非系统程序员也会从中获得很多知识。我相信:每个搞程序开发的人都想有一个自己制作的操作系统。

通过阅读本篇文章你也会学到怎样从磁盘驱动器读写原始扇区。

 

背景知识:

这篇文章, 我会解释构建 OS 的第一部分。 

当你启动计算机时会发生下面的动作::

1.       BIOS (基本输入输出系统--是和主板一起的一段小程序,存放在主板的芯片中)检查所有的计算机组建确保他们能够正常工作。

2.       如果所有的组建能够正常工作,BIOS 会开始查找可能含有操作系用的磁盘驱动器。BIOS 能够查找硬盘,软盘,光驱等等。BIOS 检查的顺序可以在BIOS的设置程序里进行设置。要进入到 BIOS 的设置程序,在计算机打开时按 "DELETE" 键,直到进入BIOS 设置程序。不同的计算机进入BIOS的设置的方法可能不同,具体要看启机时屏幕有关的提示信息。

3.       BIOS 检查第一个驱动器查看它是否有一个有效的启动扇区“BOOT SECTOR”(磁盘被划分为许多小的区域,每个小区域叫做扇区)。大部分的驱动器扇区的大小是 512 字节.) 如果该驱动器有一个有效的启动扇区,BIOS 将会把该扇区中的数据加载到内存,加载地址是 0: 7c 00(=31.744),然后把就控制权交给这块内存区域。

4.       这个小程序从启动扇区被加载,然后继续加载操作系统,在操作系统加载完毕后,初始化操作系统并把控制权交给操作系统。


制作启动磁盘

步骤如下:

1.       找到一张你不再需要的软盘

2.       使用本文章附带的程序 BOOTSectorUtility.exe BOOT.bin 拷贝到软件的启动扇区中。

3.       检查你的 BIOS 设置为从软盘启动, 这样我们的操作系统就会贝加载。

4.       把软件插入计算机,重新启动,你会看到我们自己的简陋OS

利用BOOTSectorUtility.exe, 我们能把当前正在使用操作系统的启动扇区保存在特定的文件中,作为备份。

要读取文件,打开cmd 命令行,键入 debug <文件名>  ( 我们使用的是 windows)

debug 命令行运行一个 16 位的调试器(任何启动扇区都是 16 位的,因为当计算机启动时它处于 16 模式下,仅在运行之后它从把CPU 转到 32 位或者是 64 位模式).  输入 u (unassembler) 然后回车,如下图所示:

   反汇编16 位代码

 

 

读取原始字节

下面的代码说明了怎么从磁盘读取原始字节:

//
//  Reading/writing raw sectors.
//
// pBuffer has to be at least 512 bytes wide.
//

BOOL ReadSector(char chDriveName, char *pBuffer, DWORD nSector)
{
 
    char Buffer[256];
    HANDLE hDevice;
    DWORD dwBytesReaden;
    
    //
    //Init the drive name (as a Driver name).
    //
   
    sprintf(Buffer,".//%c:",chDriveName);

    hDevice = CreateFile(Buffer,                  // drive to open.
                 GENERIC_READ,
                 FILE_SHARE_READ |    // share mode.
                 FILE_SHARE_WRITE,
                 NULL,                // default security attributes.
                 OPEN_EXISTING,       // disposition.
                 0,                   // file attributes.
                 NULL);               //
   
    //
    //if Error Openning a drive.
    //

    if(hDrive==INVALID_HANDLE_VALUE)
    { 
           return FALSE;
    }

    //
    //Move the read pointer to the right sector.
    //
   
    if(SetFilePointer(hDevice,
             nSector*512,
             NULL,
             FILE_BEGIN)==0xFFFFFFFF)
           return FALSE;
    //
    //Read the Sector.
    //

    ReadFile(hDevice,
           pBuffer,
           512,
           &dwBytesReaden,
           0);
    
    //
    //if Error reading the sector.
    //
   
    if(dwBytesReaden!=512)
           return FALSE;

    return TRUE;
}


制作启动程序

现在我将解释一下启动程序的基本知识 ( 要理解启动程序, 你需要熟悉汇编语言, 中断和中断向量表 )

中断向量表 从地址 0 1024 保存着 256 个结构体(每个大小是 4 字节), 这些结构体以 CS:IP = xxxx:xxxx 的形式保存地址。这样你会有一个从 INT 1 INT 256 的地址。,每个中断向量都有一个在此表中的索引。向下面这样使用此表:                                                                                                                                                                              例如 你使用指令 INT 10H, 那么CPU 检查中断向量中 10H 索引位置, 该位置的数值是处理 INT 10H 例程的地址,CPU 然后跳转到该地址,把控制权交给该处理程序(中断处理程序)。

下面我会解释仅用 BIOS 怎样打印字符串, 读扇区,等待按键。

打印字符串:
mov ah , 0ah
int 10h
移动光标
mov ah , 2h
int 10h
读取扇区
mov ah , 2h
int 13h
等待击键
mov ah , 0h
int 16h
 

我使用的 TASM V3.1 TLINK 制作的启动扇区, 但是你可以使用任何的 x86 16 位汇编器。

现在我解释一下启动程序的大体步骤:

1.       作一个 Stack Frame。如果你没有进行此步,将得不到任何堆栈.

2.       设置 DS 数据段寄存器, 这样你能访问数据 

3.       在我的启动扇区中,我添加了这些功能 (给用户显示一条消息,等待击键消息,继续)  

4.       设置磁盘参数块结构体(Disk Parameter Block)   (磁盘参数块结构体保存一些有关驱动器的信息,例如它有多少扇区,驱动控制器通过它知道怎样读取驱动器上的磁盘)

5.       要设置磁盘参数块结构体,得到它的地址它的地址是由 INT  1EH 指向的,在内存中这是一个 1E x 4 bytes= 78h = 30 x 4 bytes = 120) 

              下面展示了怎样初始化一个3.5 英寸(1.44MB) 软磁盘的 Disk Parameter Block 

      StepRateAndHeadUnloadTime                     db      0DFh

      HeadLoadTimeAndDMAModeFlag                    db      2h

      DelayForMotorTurnOff                          db      25h

      BytesPerSector                                db      2h             

      SectorsPerTrack                               db      12h           

      IntersectorGapLength                          db      1bh

      DataLength                                    db      0FFh

      IntersectorGapLengthDuringFormat              db      54h

      FormatByteValue                               db      0F 6h

      HeadSettlingTime                              db      0Fh

      DelayUntilMotorAtNormalSpeed                  db      8h

 

      DisketteSectorAddress(as LBA)OfTheDataArea    db      0

      CylinderNumberToReadFrom                      db      0

      SectorNumberToReadFrom                        db      0

      DisketteSectorAddress (as LBA) OfTheRootDirectory    db      0                                                                                                                

          6.  设置 INT 1E 指向的地址为我们上面设置的 Disk Parameter Block 

          7.  重设驱动器(使用 INT 13h ,ah = 0  

          8.  通过 INT 31h , ah = 2 开始读取磁盘

          9.  把控制权交给已加载的操作系统  这是通过在加载的操作系统中插入 jump 代码实现的。即你从磁盘的启动扇区( 0: 7c 00 )加载了操作系统的 512 字节, 或者是你能使用另外一种方式: call address, 然后在该地址处改变堆栈的返回地址为自己想要调转的地址,像这样:

 

call GiveControlToOS
 
GiveControlToOS:
        Pop ax
        Pop ax
        Mov ax , CodeSegement            ; Push the new CS to return
        Push ax
        mov ax , InstructionPointer        ; Push the new IP to return
        Push ax

 
        ret                                                   ; Return to the modified address

 

在启动扇区的结尾是 0x55h,0x0AAh。如果启动扇区没有这些值,BIOS 不会加载启动扇区。

在这之后我们就完成了 Boot Sector  的全部工作, 然后我们启动系统。


启动扇区的程序

.MODEL SMALL

.CODE         

ORG 7c 00h      ;Because BIOS loades the OS at
                       ; address 0: 7C 00h so ORG 7C 00h
                       ; makes that the refrence to date
                       ; are with the right offset ( 7c 00h).
 
ProgramStart:

                      
   ; CS = 0 / IP = 7C 00h // SS = ? / SP = ?
   ; You are now at address 7c 00.
jmp start   ;Here we start the, BIOS gave us now the control.

 

;///
;//Here goes all the data of the program.
;///

xCursor db 0
yCursor db 0


nSector db 0
nTrack  db 0
nSide   db 0
nDrive  db 0

nTrays  db 0

'Are You Ready to start Loading the OS...',0
szReady                db
'Error Reading Drive, Press any Key to reboot...',0
szErrorReadingDrive    db
;//Done Reading a track.
szPlaceMarker          db  '~~~~',0
szDone                 db  'Done',0

pOS                    dw   7E00h
;//Points to were to download the Operating System.

;//Disk Paremeter Table.
StepRateAndHeadUnloadTime                     db      0DFh
HeadLoadTimeAndDMAModeFlag                    db      2h
DelayForMotorTurnOff                          db      25h
;// (1 = 256) //(2 = 512 bytes)
BytesPerSector                                db      2h
;// 18 sectors in a track.
SectorsPerTrack                               db      18
IntersectorGapLength                          db      1Bh
DataLength                                    db      0FFh
IntersectorGapLengthDuringFormat              db      54h
FormatByteValue                               db      0F 6h
HeadSettlingTime                              db      0Fh
DelayUntilMotorAtNormalSpeed                  db      8h

DisketteSectorAddress_as_LBA_OfTheDataArea    db      0
CylinderNumberToReadFrom                      db      0
SectorNumberToReadFrom                        db      0
DisketteSectorAddress_as_LBA_OfTheRootDirectory      db      0

;/
;//Here the program starts.
;/


Start:

CLI     ;Clear Interupt Flag so while setting
        ;up the stack any intrupt would not be fired.

        mov AX,7B0h    ;lets have the stack start at 7c 00h-256 = 7B00h
        mov SS,ax      ;SS:SP = 7B0h:256 = 7B00h:256
        mov SP,256     ;Lets make the stack 256 bytes.

        Mov ax,CS      ;Set the data segment = CS = 0
        mov DS,ax
       
        XOR AX,AX      ;Makes AX=0.
        MOV ES,AX      ;Make ES=0


STI     ;Set Back the Interupt Flag after
        ;we finished setting a stack fram.
      
        Call ClearScreen       ;ClearScreen()
        LEA AX,szReady         ;Get Address of szReady.
        CALL PrintMessage      ;Call PrintfMessage()
        CALL GetKey                    ;Call GetKey()
       
        CALL SetNewDisketteParameterTable
        ;SetNewDisketteParameterTable()
       
        CALL DownloadOS
        CALL GetKey                    ;Call GetKey()
        CALL FAR PTR  GiveControlToOS  ;Give Control To OS.

ret

;/
;//Prints a message to the screen.
;/
PrintMessage PROC

        mov DI,AX      ;AX holds the address of the string to Display.
        Mov xCursor,1  ;Column.
       
ContinuPrinting:

        cmp byte ptr [DI],0    ;Did we get to the End of String.
        JE EndPrintingMessage  ;if you gat to the end of the string return.
       
        mov AH,2               ;Move Cursor
        mov DH,yCursor         ;row.
        mov DL,xCursor         ;column.
        mov BH,0               ;page number.
        INT 10h
        INC xCursor
       
        mov AH,0Ah             ;Display Character Function.
        mov AL,[DI]            ;character to display.
        mov BH,0               ;page number.
        mov CX,1               ;number of times to write character
        INT 10h
       
       
              
        INC DI                 ;Go to next character.
       
        JMP ContinuPrinting    ;go to Print Next Character.
              
EndPrintingMessage:
       
        Inc yCursor            ;So Next time the message would
                               ;be printed in the second line.
       
        cmp yCursor,25
        JNE dontMoveCorsurToBegin
        Mov yCursor,0
       
dontMoveCorsurToBegin:
        ret
       
              
PrintMessage EndP
;//
;//Watis for the user to press a key.
;//
GetKey PROC

        mov ah,0
        int 16h ;Wait for a key press.
        Ret
       
GetKey EndP
;///
;//Gives Control To Second Part Loader.
;///
GiveControlToOS PROC

        LEA AX,szDone
        Call PrintMessage
        CALL GetKey
       
        db 0e9h        ;Far JMP op code.
        dw 512         ;JMP 512 bytes ahead.
       
;       POP AX         ;//Another why to make
                       ;the CPU jump to a new place.
;       POP AX
;       Push 7E0h      ;Push New CS address.
;       Push 0          ;Push New IP address.
               ;The address that comes out is 7E00:0000.
               ;(512 bytes Higher from were BIOS Put us.)
;       ret
       
       
GiveControlToOS EndP
;///
;//Clear Screen.
;///
ClearScreen PROC

        mov ax,0600h   ;//Scroll All Screen UP to Clear Screen.
        mov bh,07
        mov cx,0
        mov dx,184fh  
        int 10h
       
        Mov xCursor,0  ;//Set Corsur Position So next
                        //write would start in
                        //the beginning of screen.
        Mov yCursor,0

        Ret
       
ClearScreen EndP
;/
;//PrintPlaceMarker.
;/
PrintPlaceMarker PROC


        LEA AX,szPlaceMarker
        CALL PrintMessage  ;Call PrintfMessage()
        CALL GetKey        ;Call GetKey()
        ret
       
PrintPlaceMarker EndP
;/
;//Set New Disk Parameter Table
;/
SetNewDisketteParameterTable PROC

        LEA DX,StepRateAndHeadUnloadTime
        ;//Get the address of the Disk Parameters Block.
       
               ;//Int 1E (that is in address 0:78h)
               ;//holds the address of the disk parametrs
               ;//block, so now change it to
               ;//our parametr black.
        ;//DX holds the address of our Parameters block.
        MOV WORD PTR CS:[0078h],DX
        MOV WORD PTR CS:[007Ah],0000
       
        ; Reset Drive To Update the DisketteParameterTable.
        MOV AH,0
        INT 13H
      
        ret
       
SetNewDisketteParameterTable EndP
;///
;//DownloadOS
;///
DownloadOS PROC

        mov nDrive,0
        mov nSide,0
        mov nTrack,0
        mov nSector,1
       
ContinueDownload:
       
        INC nSector            ;Read Next Sector.
        cmp nSector,19         ;Did we get to end of track.
        JNE StayInTrack
        CALL PrintPlaceMarker  ;Print now '~~~~' so the user would
                               ;now that we finished reding a track
        INC nTrack             ;If we gat to end of track Move to next track.
        mov nSector,1          ;And Read Next Sector.
        CMP nTrack,5           ;Read 5 Tracks (Modify this value
                               ;to how much Tracks you want to read).
        JE      EndDownloadingOS
       
StayInTrack:
       
        ;ReadSector();
        Call ReadSector
       
       
        JMP     ContinueDownload
        ;If diden't yet finish Loading OS.
       
EndDownloadingOS:

        ret
       
DownloadOS EndP
;
;//Read Sector.
;
ReadSector PROC

        mov nTrays,0
       
TryAgain:

        mov AH,2               ;//Read Function.
        mov AL ,1               ;//1 Sector.
        mov CH,nTrack
        mov CL,nSector         ;//Remember: Sectors start with 1, not 0.
        mov DH,nSide
        mov DL,nDrive
        Mov BX,pOS             ;//ES:BX points to the address
                               ;to were to store the sector.
        INT 13h
       

        CMP AH,0               ;Int 13 return Code is in AH.
        JE EndReadSector       ;if 'Sucsess' (AH = 0) End function.

        mov AH,0               ; Else Reset Drive . And Try Again...
        INT 13h
        cmp nTrays,3           ;Chack if you tryed reading
                               ;more then 3 times.
       
        JE DisplayError        ; if tryed 3 Times Display Error.
       
        INC nTrays
       
        jmp TryAgain       ;Try Reading again.
       
DisplayError:
        LEA AX,szErrorReadingDrive
        Call PrintMessage
        Call GetKey
        mov AH,0                       ;Reboot Computer.
        INT 19h
       

EndReadSector:
        ;ADD WORD PTR pOS,512  ;//Move the pointer
                               ;(ES:BX = ES:pOS = 0:pOS) 512 bytes.
                               ;//Here you set the varible
                               ;pOS (pOS points to were BIOS
                               ;//Would load the Next Sector).
        Ret

ReadSector EndP
;
;//
;
END ProgramStart


 

总结

我花了一段时间才把启动扇区做好下面是我在此过程中遇到的一些bugs

1.       没有设置正确的堆栈

2.       我没有修改  Disk Parameter Block

3.       I loaded the operating system to areas that are used by BIOS routines (and even to the Interpret table).

4.       我把 OS 加载到了BIOS 例程使用的区域(设置是 中断向量表区域)

5.       在启动扇区的结束,必须是 0x55,0xAA .( 标志着有效的启动扇区 )

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值