从零开始写一个操作系统——1.boot

749 阅读5分钟

相关环境的准备将位于本系列的第0部分。由于不同用户终端的环境各有不同,所以第0章将会在综合之后再予以撰写。

0.创建一个启动盘

dd if=/dev/zero of=./boot.img bs=512 count=2880

上述shell命令作用是创建一个大小为1.44M且所有数据都是0的名为boot.img的文件。这也就是我们操作系统的载体。


1.boot的作用

计算机开始后将会运行硬盘中第一扇区的内容,而第一扇区的大小仅有512B,所以boot的作用将会是一个跳板,用于实现文件系统以及读取硬盘上的loader与跳转。

1.1 文件系统FAT12

文件系统的实现在boot中并不是必须的,但是实现一个文件系统可以使我们的开发过程更加方便。

什么是文件系统?

文件系统本身也是一种数据结构,所以它也需要保存在一个地方——第一扇区

文件系统的数据结构如下:

文件系统扇区分布情况如下:


2.boot的实现

根据上述,boot的功能也就是为了能够找到硬盘中的loader.bin且将其加载至内存中,并将cs:ip指针指向此内存。简化为:

  1. 寻找
  2. 加载
  3. 跳转

由于汇编语言的特殊性,难于理解其中逻辑。所以在这里我将使用C语言作为伪代码来帮助读者理解boot中的调用逻辑。

2.1 寻找loader

由上述文件系统扇区分布可知,当我们需要寻找一个文件的时候,我们应该前往根目录区进行寻找。寻找loader的c语言伪代码示意为:

//从20扇区开始,寻找到33扇区
for(int sector=20;i<34;i++)
{
    //读取sector开始的一个扇区并放置在Loader开始处的内存中
    readSector(sector, 1, offsetOfLoader);
    //一个扇区有512/32=16个目录,遍历这16个目录
    for(int index=0;i<16;i++)
    {
        //将index转化成目录的指针并与文件名字指针比较
        if(cmpFileName(indexToDirPointer(index), fileNamePointer))
        {
            //if条件成立,找到了loader,并且此时di寄存器内容依然为loader目录指针
            LoaderFound();
        }
    }
}

其中涉及到的函数有:

void readSector(int startOfSector, int numberToRead, int* whereToPlace)
//读取第startOfSector个扇区开始的共numberToRead个扇区,放置在whereToPlace指针所指处
int cmpFileName(int* fileNamePointer, int* dirPointer)
//当目录指针所指的目录文件的文件名与文件名指针所指的文件名相同时,返回1,否则0。
int* indexToDirPointer(int index)
//将目录的下标转化成目录的指针

汇编语言实现上述函数:

void readSector(int startOfSector, int numberToRead, int* whereToPlace)

Func_ReadSector:
    push bp
    mov bp, sp
    mov word[ss:bp - 2], di
    mov word[ss:bp - 4], si
    mov word[ss:bp - 6], dx
    mov ax, word[ss:bp - 2]
    mov bl, 36
    div bl
    mov ch, al
    mov al, ah
    mov ah, 0
    mov bl, 18
    div bl
    mov dh, al
    mov cl, ah
    mov ax, word[ss:bp - 4]
    mov bx, 0
    mov es, bx
    mov ah, 02h
    mov dl, 00h
    mov bx, word[ss:bp - 6]
    int 13h
    pop bp
    ret

int cmpFileName(int* fileNamePointer, int* dirPointer)

    
Func_CmpFileName:
    mov bx, 0
Label_GoOnCmpFileName:
    lodsb
    cmp byte[di + bx], al
    je Label_GoOn
    cmp bx, LENGTHOFFILENAME
    je Label_FileFound
    jmp Label_FileNameIncorrect
Label_FileFound:
    mov ax, 1
    ret
Label_FileNameIncorrect:
    mov ax, 0
    ret
Label_GoOn:
    inc bx
    jmp Label_GoOnCmpFileName

int* indexToDirPointer(int index)

Func_IndexToDirPointer:
    mov ax, di
    mov bl, 32
    mul bl
    add ax, OFFSETOFLOADER
    ret

2.2 加载与跳转loader

经过上述的查找后,我们已经进入到了LoaderFound函数中,而这个函数的主要功能就是将loader加载进内存中。c语言伪代码示意:

void LoaderFound(int* dirPointer)
{
    int startOfLoader = dirPointerToStartSectorOfFile(dirPointer);
    int sizeOfLoader = dirPointerToSizeOfFile(dirPointer);
    int* whereToPlace = (int*) 0x7e00;
    readSector(startOfLoader, sizeOfLoader, whereToPlace);
    jmp 0x7e00 <-汇编代码跳转
}

其中涉及到的函数有:

int dirPointerToStartSectorOfFile(int* dirPointer)
//将文件目录指针转化成文件开始的扇区号
int dirPointerToSizeOfFile(int* dirPointer)
//将文件目录指针转化成文件的大小(单位:扇区)

汇编语言实现:

int dirPointerToStartSectorOfFile(int* dirPointer)

Func_DirPointerToStartSectorOfFilePointer:
    mov bx, di
    add bx, OFFSETOFFIRSTCLUS
    mov ax, word[bx]
    add ax, StartOfFileSector
    ret

int dirPointerToSizeOfFile(int* dirPointer)

Func_DirPointerToSectorOfFile:
    mov ax, word[di + OFFSETOFFILESIZE]
    mov dx, word[di + OFFSETOFFILESIZE + 2]
    mov bx, 512
    div bx
    cmp dx, 0
    je  Label_FileSizeComplete
    inc ax
Label_FileSizeComplete:
    ret

2.3整体代码参考

boot.asm文件内容:

org 0x7c00
jmp Label_Start
BS_OEMName	db	'MINEboot'
BPB_BytesPerSec	dw	512
BPB_SecPerClus	db	1
BPB_RsvdSecCnt	dw	1
BPB_NumFATs	db	2
BPB_RootEntCnt	dw	224
BPB_TotSec16	dw	2880
BPB_Media	db	0xf0
BPB_FATSz16	dw	9
BPB_SecPerTrk	dw	18
BPB_NumHeads	dw	2
BPB_HiddSec	dd	0
BPB_TotSec32	dd	0
BS_DrvNum	db	0
BS_Reserved1	db	0
BS_BootSig	db	0x29
BS_VolID	dd	0
BS_VolLab	db	'FAT12      '
BS_FileSysType	db	'FAT12   '

LOADERFILENAME: db "LOADER  BIN"

OFFSETOFFILESIZE    equ  0x1c
OFFSETOFFIRSTCLUS   equ  0x1a
LENGTHOFFILENAME    equ  0xb

StartOfFileSector   equ     32

OFFSETOFLOADER  equ 0x7e00

StartOfDirSector   equ  20

CurrentSector   dw  0
FileIndex   dw  0

Label_Start:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ax, 0x0
    mov ss, ax
    mov sp, 0x7c00
    jmp Label_SearchLoader

;====== 
; void readSector(int* startOfSector, int* numberToRead, int* whereToPlace)
;======
Func_ReadSector:
    push bp
    mov bp, sp
    mov word[ss:bp - 2], di
    mov word[ss:bp - 4], si
    mov word[ss:bp - 6], dx
    mov ax, word[ss:bp - 2]
    mov bl, 36
    div bl
    mov ch, al
    mov al, ah
    mov ah, 0
    mov bl, 18
    div bl
    mov dh, al
    mov cl, ah
    mov ax, word[ss:bp - 4]
    mov bx, 0
    mov es, bx
    mov ah, 02h
    mov dl, 00h
    mov bx, word[ss:bp - 6]
    int 13h
    pop bp
    ret

Func_DirPointerToStartSectorOfFilePointer:
    mov bx, di
    add bx, OFFSETOFFIRSTCLUS
    mov ax, word[bx]
    add ax, StartOfFileSector
    ret

; int(sectorOfFile) DirPointerToSizeOfFile(int* dirPointer)
Func_DirPointerToSectorOfFile:
    mov ax, word[di + OFFSETOFFILESIZE]
    mov dx, word[di + OFFSETOFFILESIZE + 2]
    mov bx, 512
    div bx
    cmp dx, 0
    je  Label_FileSizeComplete
    inc ax
Label_FileSizeComplete:
    ret

; int* indexToDirPointer(int index)
Func_IndexToDirPointer:
    mov ax, di
    mov bl, 32
    mul bl
    add ax, OFFSETOFLOADER
    ret


; bool CmpFileName(int* dirPointer, int* fileNamePointer)
Func_CmpFileName:
    mov bx, 0
Label_GoOnCmpFileName:
    lodsb
    cmp byte[di + bx], al
    je Label_GoOn
    cmp bx, LENGTHOFFILENAME
    je Label_FileFound
    jmp Label_FileNameIncorrect
Label_FileFound:
    mov ax, 1
    ret
Label_FileNameIncorrect:
    mov ax, 0
    ret
Label_GoOn:
    inc bx
    jmp Label_GoOnCmpFileName


Label_SearchLoader:
    mov word[CurrentSector], 20
Label_ForSectorInRange:
    mov di, word[CurrentSector]
    mov si, 1
    mov dx, OFFSETOFLOADER
    call Func_ReadSector
    mov word[FileIndex], 0
Label_ForIndexInRange:
    mov di, word[FileIndex]
    call Func_IndexToDirPointer
    mov di, ax
    mov si, LOADERFILENAME
    call Func_CmpFileName
    cmp ax, 1
    je Label_LoaderFound
    inc word[FileIndex]
    cmp word[FileIndex], 17
    je Label_ForSectorInRangeEnd
    jmp Label_ForIndexInRange
Label_ForSectorInRangeEnd:
    inc word[CurrentSector]
    cmp word[CurrentSector], 35
    je Label_LoaderNoFound
    jmp Label_ForSectorInRange
Label_LoaderNoFound:
    jmp $

Label_LoaderFound:
    call Func_DirPointerToSectorOfFile
    mov si, ax
    call Func_DirPointerToStartSectorOfFilePointer
    mov di, ax
    mov dx, OFFSETOFLOADER
    call Func_ReadSector
    jmp 0x7e00

times 510 - ($ - ?) db 0
dw 0xaa55

3 将boot加载至磁盘中

上述的boot文件将会在被编译后写入开头就做好的操作系统载体boot.img中。具体操作如下:

nasm boot.asm -o boot.bin
//使用nasm编译器将boot.asm编译成二进制文件boot.bin
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
//将二进制文件boot.bin直接放入载体boot.img的第一扇区中

此时我们的boot.img就像是一块磁盘镜像一样(正如你重装电脑时插入的光盘),我们可以使用如下命令加载此镜像:

mount boot.img ./

这样我们当前目录就会多出一个FAT12的文件夹,正如我们将u盘插入电脑一样。在后续的开发中,我们就可以直接将我们操作系统中的loader文件复制进此文件夹(镜像)中了。接下来就是loader程序的开发。