相关环境的准备将位于本系列的第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指针指向此内存。简化为:
- 寻找
- 加载
- 跳转
由于汇编语言的特殊性,难于理解其中逻辑。所以在这里我将使用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程序的开发。