65.9K
CodeProject 正在变化。 阅读更多。
Home

构建你自己的操作系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (217投票s)

2006年10月6日

CPOL

7分钟阅读

viewsIcon

955644

downloadIcon

30752

编写你自己的操作系统。

引言

如果你了解操作系统的工作原理,那会对你的编程有很大帮助,特别是对于设备驱动程序等系统程序;即使对于非系统编程,它也能提供很多帮助。而且,我相信每个人都希望拥有自己的操作系统。

你还将从本文中学到如何从磁盘读写原始扇区。

背景

在本文中,我将解释构建操作系统这方面的第一部分。

当你启动计算机时会发生什么

  1. BIOS(基本输入输出系统——这是一个随主板附带的程序,它位于主板上的一个芯片中)会检查所有计算机组件以确保它们都正常工作。
  2. 如果所有组件都正常工作,BIOS会开始寻找可能安装有操作系统的驱动器。(BIOS可以检查硬盘、软盘驱动器、CD-ROM驱动器等。BIOS检查的顺序可以在BIOS设置程序中设置。要进入BIOS设置,在计算机开机时立即按DELETE键,直到你看到BIOS设置程序;在某些计算机中,它可能是除DELETE之外的其他按钮,所以请查阅你的主板规格以了解如何进入BIOS设置程序。另外,(如果你不知道如何操作)请查找如何更改BIOS在寻找操作系统时的搜索顺序。)
  3. BIOS检查第一个驱动器,看它是否有有效的启动扇区。(磁盘被分成称为扇区的小区域。大多数驱动器的启动扇区大小为512字节。)如果驱动器有一个有效的启动扇区,BIOS会将该扇区加载到内存地址0:7c00 (=31,744),并将控制权交给该内存区域。
  4. 现在,从启动扇区加载的这个小程序会继续加载操作系统,在操作系统加载完成后,它会初始化操作系统并将控制权交给操作系统。

制作一个可启动磁盘

步骤如下

  1. 拿一张你不需要的软盘。
  2. 使用本文附带的程序 BOOTSectorUtility.exe,将文件 BOOT.bin 复制到软盘的启动扇区。
  3. 确保你的BIOS设置为首先从软盘驱动器启动,以便我们的操作系统能够加载。
  4. 重新启动你的计算机(确保软盘在驱动器中),并观察我们的启动扇区会做什么。

使用 BOOTSectorUtility.exe,你还可以通过将操作系统驱动器的启动扇区保存到文件中来搜索你的常规操作系统。

要读取该文件,启动命令行并输入 Debug <文件路径>(如果你使用的是Microsoft操作系统)。

Debug 命令启动一个16位调试器(任何启动扇区都是16位代码,因为计算机启动时处于16位模式,只有在运行启动扇区后才能将CPU更改为32位模式或64位模式)。按 u <回车>(u = 反汇编)来反汇编文件。图1显示了一个例子。

图 1 反汇编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(hDrive==INVALID_HANDLE_VALUE)//if Error Openning a drive.
    {
           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 -> 1,024 包含 256 个结构(大小为 4 字节),这些结构保存着 CS:IP = xxxx:xxxx 形式的地址。因此,你有从 INT 1 -> INT 256 的地址。每个中断在该表中都有一个索引。此表的使用方式如下:例如,如果你使用指令 INT 10h,CPU 会在中断表的索引 10h 处检查处理 INT 10h 的例程的地址,然后 CPU 会跳转到该地址执行它]。

现在,我将解释如何仅使用BIOS来打印字符串、读取扇区和等待按键。

要打印字符串,我使用 INT 10h 函数 0Ah (AH = 0Ah)。要移动光标,我使用 INT 10h 函数 2h (AH = 2h)。要读取扇区,我使用 INT 13h 函数 2h (AH = 2h)。要等待按键,使用 INT 16h 函数 0h (AH = 0h)。

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

(如果你无法获取TASM和TLINK的副本,你可以给我发邮件,如果合法,我会给你寄一份。)

(TASM 和 TLINK v3.1 由 Borland International 于 1992 年发布)。

现在我将解释启动程序所执行的步骤。

  1. 建立一个堆栈帧(如果你没有堆栈的话)。
  2. 设置 DS(数据段),以便您可以访问数据。
  3. 在我的启动扇区中,我添加了这些内容:(向用户显示消息,等待按键,继续)。
  4. 设置磁盘参数块(磁盘参数块是一个结构,它保存着关于驱动器的信息,例如它有多少个扇区等,驱动器控制器使用它来了解如何读取驱动器中的磁盘)。
  5. 要设置磁盘参数块,获取它的地址(它的地址由 INT 1Eh 指向(在内存中,这是 1E x 4 字节 = 78h = 30 x 4 字节 = 120))。

    这是一个如何初始化3.5英寸软盘的磁盘参数块的例子。3.5 英寸(1.44 MB)软盘。

    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      0F6h
    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 指向的地址设置为你设置的磁盘参数块的地址。
  7. 重置驱动器(通过使用 INT 13h 函数 0)。
  8. 使用 INT 13h 函数 2h 开始读取软盘。
  9. 将控制权交给加载的操作系统(这是通过在代码中插入跳转到加载操作系统位置的操作码来完成的。假设你将操作系统加载到BIOS加载启动扇区(0:7c00h)之后512字节处)。
    db 0E9h  ; FAR JMP op code.
    db 512   ; 512 bytes

    或者你可以使用另一种方式:调用某个地址,然后在堆栈中改变返回地址到你想要跳转的位置,像这样

    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.
    
  10. 在启动扇区的末尾,字节将是 0x55h 0x0AAh。如果启动扇区没有这个值,BIOS就不会加载该启动扇区。

此后,启动扇区完成其工作,操作系统开始运行。

你可以在此处下载我用于我的启动扇区的文件,文件是 BOOT.asm, BOOT.bin (这是扇区本身)。

启动扇区程序

.MODEL SMALL

.CODE          

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

                       
   ; CS = 0 / IP = 7C00h // SS = ? / SP = ?
   ; You are now at address 7c00.
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      0F6h
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 7c00h-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

关注点

我花了一些时间才让一个启动扇区运行起来。我将列出我在编写启动扇区时一开始遇到的一些错误。

  1. 我没有设置正确的堆栈帧。
  2. 我没有修改磁盘参数块。
  3. 我将操作系统加载到BIOS例程使用(甚至解释表)的区域。
  4. 在启动扇区的末尾,它必须有字节 0x55h 0x0AAh (这是一个有效可启动扇区的签名)。

    (要使用 BOOTSectorUtility.exe,你的计算机上需要安装 .NET 2.0 Framework。要下载 .NET 2.0 Framework,请点击这里。)

如果我看到这篇文章引起人们的兴趣,我将撰写更多关于这个主题的文章。

© . All rights reserved.