一个32位操作系统的实现2——Loader加载程序
程序员文章站
2022-05-28 18:51:59
...
上篇文章中我们实现了一个MBR引导程序,该引导程序加载loader加载程序到内存0100h处执行,但是我们并没有实现loader.bin加载程序
所以在这一章中我们将实现这个加载程序。
1、Loader加载程序
加载程序的主要作用就是搜索并加载内核程序到内存中然后执行,这就存在一个问题:为啥不直接用MBR引导程序加载内核并执行?直接MBR程序加载内核至内存然后执行多省事,为啥要整这么多费劲的事情,说来话长,技术的进步从来不是一蹴而就,而是一代代升级而来,举个简单的例子来说,最开始的Intel 8086是16位的CPU,它有16位的寄存器、16位的数据总线以及20位的地址总线,一个位就1和0两种状态,20位地址总线意味着2的20次方,也就是1048576个地址,换句话说,在这种CPU上最多也就能表示出1048576=1048KB=1M的地址,超过1M的内存空间自然也就无法表示以至于无法使用了,或问曰:为什么不增加几个位呢?要想使用更大的地址当然得增加,但是这是后续升级版本的事情了,也就是后面的Intel 80386,从80386开始,CPU就进入32位时代,80386有32位地址,所以可以表示2的32次个地址,也就是4GB内存。在16位的时候,我们在使用某个地址的时候都是用"段值:偏移"的方式来表示,物理地址=段值*16+偏移,段值存在段寄存器中,偏移值则存在通用寄存器中,比如说上篇MBR引导程序中org 07c00h中的07c00h这个地址,亦可写成0x7c00,也就是段值为0x7c偏移是0x00的这个地址,当时代发展到32位CPU,一切都开始发生了变化,虽然新时代我们的地址依然使用"段值:偏移"的方式来表示,但是这个时候的'段值'已经和原来的'段值'不是同一个意思,此时它已经成为一个索引,这个索引指向一个数据结构的表项,这个表项定义了段的起始地址、界限、属性等内容,这个数据结构就是GDT,围绕着这一系列的升级改变,我们将前者称之为实模式,后者称之为保护模式,显然,我们的加载程序一开始是在16位实模式下运行的,这中间会有一个跳转进入32位保护模式的过程,这个过程包括加载GDT、打开A20、置cr0的PE位等等操作。接下来,进入我们的主题,即如何实现loader.bin加载程序,为此我们需要做出总结loader到底要实现哪些功能,功能如下:
1)搜索并加载kernel.bin内核文件
2)使用lgdt指令加载gdtr
3)打开A20
4)置cr0的PE位
5)设置VGA图形模式
6)进入保护模式
7)跳转执行内核
第1步我们在MBR引导程序中就已经实现,麻烦的是第2步,需要设置大量GDT的东东,3-7则只需一或几句即可实现,整体实现代码如下:
所以在这一章中我们将实现这个加载程序。
1、Loader加载程序
加载程序的主要作用就是搜索并加载内核程序到内存中然后执行,这就存在一个问题:为啥不直接用MBR引导程序加载内核并执行?直接MBR程序加载内核至内存然后执行多省事,为啥要整这么多费劲的事情,说来话长,技术的进步从来不是一蹴而就,而是一代代升级而来,举个简单的例子来说,最开始的Intel 8086是16位的CPU,它有16位的寄存器、16位的数据总线以及20位的地址总线,一个位就1和0两种状态,20位地址总线意味着2的20次方,也就是1048576个地址,换句话说,在这种CPU上最多也就能表示出1048576=1048KB=1M的地址,超过1M的内存空间自然也就无法表示以至于无法使用了,或问曰:为什么不增加几个位呢?要想使用更大的地址当然得增加,但是这是后续升级版本的事情了,也就是后面的Intel 80386,从80386开始,CPU就进入32位时代,80386有32位地址,所以可以表示2的32次个地址,也就是4GB内存。在16位的时候,我们在使用某个地址的时候都是用"段值:偏移"的方式来表示,物理地址=段值*16+偏移,段值存在段寄存器中,偏移值则存在通用寄存器中,比如说上篇MBR引导程序中org 07c00h中的07c00h这个地址,亦可写成0x7c00,也就是段值为0x7c偏移是0x00的这个地址,当时代发展到32位CPU,一切都开始发生了变化,虽然新时代我们的地址依然使用"段值:偏移"的方式来表示,但是这个时候的'段值'已经和原来的'段值'不是同一个意思,此时它已经成为一个索引,这个索引指向一个数据结构的表项,这个表项定义了段的起始地址、界限、属性等内容,这个数据结构就是GDT,围绕着这一系列的升级改变,我们将前者称之为实模式,后者称之为保护模式,显然,我们的加载程序一开始是在16位实模式下运行的,这中间会有一个跳转进入32位保护模式的过程,这个过程包括加载GDT、打开A20、置cr0的PE位等等操作。接下来,进入我们的主题,即如何实现loader.bin加载程序,为此我们需要做出总结loader到底要实现哪些功能,功能如下:
1)搜索并加载kernel.bin内核文件
2)使用lgdt指令加载gdtr
3)打开A20
4)置cr0的PE位
5)设置VGA图形模式
6)进入保护模式
7)跳转执行内核
第1步我们在MBR引导程序中就已经实现,麻烦的是第2步,需要设置大量GDT的东东,3-7则只需一或几句即可实现,整体实现代码如下:
org 0100h jmp LABEL_START %include "fat12hdr.inc" %include "load.inc" %include "pm.inc" LABEL_GDT: Descriptor 0, 0, 0 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3; 显存首地址 GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen ; 段界限 dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 BaseOfStack equ 0100h PageDirBase equ 100000h ; 页目录开始地址: 1M PageTblBase equ 101000h ; 页表开始地址: 1M + 4K LABEL_START: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack mov dh, 0 ; "Loading " call DispStrRealMode ; 显示字符串 ; 得到内存数 mov ebx, 0 ; ebx = 后续值, 开始时需为 0 mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构 .MemChkLoop: mov eax, 0E820h ; eax = 0000E820h mov ecx, 20 ; ecx = 地址范围描述符结构的大小 mov edx, 0534D4150h ; edx = 'SMAP' int 15h ; int 15h jc .MemChkFail add di, 20 inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 cmp ebx, 0 jne .MemChkLoop jmp .MemChkOK .MemChkFail: mov dword [_dwMCRNumber], 0 .MemChkOK: ; 下面在 A 盘的根目录寻找 KERNEL.BIN mov word [wSectorNo], SectorNoOfRootDirectory xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0; ┓ jz LABEL_NO_KERNELBIN ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN dec word [wRootDirSizeForLoop] ; ┛ mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile mov ax, [wSectorNo] mov cl, 1 call ReadSector mov si, KernelFileName ; ds:si -> "KERNEL BIN" mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+???? cld mov dx, 10h LABEL_SEARCH_FOR_KERNELBIN: cmp dx, 0 ; ┓ jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector dec dx ; ┛ mov cx, 11 LABEL_CMP_FILENAME: cmp cx, 0 ; ┓ jz LABEL_FILENAME_FOUND ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到 dec cx ; ┛ lodsb cmp al, byte [es:di] ; if al == es:di jz LABEL_GO_ON jmp LABEL_DIFFERENT LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; 继续循环 LABEL_DIFFERENT: and di, 0FFE0h ;┓这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数 add di, 20h ;┃ mov si, KernelFileName ;┣ di += 20h 下一个目录条目 jmp LABEL_SEARCH_FOR_KERNELBIN;┛ LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_KERNELBIN: mov dh, 2 ; "No KERNEL." call DispStrRealMode; 显示字符串 jmp $ ; 没有找到 KERNEL.BIN, 死循环在这里 LABEL_FILENAME_FOUND: ; 找到 KERNEL.BIN 后便来到这里继续 mov ax, RootDirSectors and di, 0FFF0h ; di -> 当前条目的开始 push eax mov eax, [es : di + 01Ch] ; ┓ mov dword [dwKernelSize], eax ; ┛保存 KERNEL.BIN 文件大小 pop eax add di, 01Ah ; di -> 首 Sector mov cx, word [es:di] push cx ; 保存此 Sector 在 FAT 中的序号 add cx, ax add cx, DeltaSectorNo ; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号) mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile mov ax, cx ; ax <- Sector 号 LABEL_GOON_LOADING_FILE: push ax ; ┓ push bx ; ┃ mov ah, 0Eh ; ┃ 每读一个扇区就在 "Loading " 后面打一个点, 形成这样的效果: mov al, '.' ; ┃ mov bl, 0Fh ; ┃ Loading ...... int 10h ; ┃ pop bx ; ┃ pop ax ; ┛ mov cl, 1 call ReadSector pop ax ; 取出此 Sector 在 FAT 中的序号 call GetFATEntry cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序号 mov dx, RootDirSectors add ax, dx add ax, DeltaSectorNo add bx, [BPB_BytsPerSec] jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: call KillMotor ; 关闭软驱马达 mov dh, 1 ; "Ready." call DispStrRealMode; 显示字符串 ;============================================================ ;准备进入保护模式 ;============================================================ ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) jmp $ ;-------------------------------------------------------------------- ;变量和字符 ;-------------------------------------------------------------------- wRootDirSizeForLoop dw RootDirSectors ; Root Directory 占用的扇区数 wSectorNo dw 0 ; 要读取的扇区号 bOdd db 0 ; 奇数还是偶数 dwKernelSize dd 0 ; KERNEL.BIN 文件大小 KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 MessageLength equ 9 LoadMessage: db "Loading " Message1 db "Ready. " Message2 db "No KERNEL" ;---------------------------------------------------------------------------- ; 函数名: DispStrRealMode ; 作用:实模式显示一个字符串(保护模式下显示字符串由函数 DispStr 完成) ;---------------------------------------------------------------------------- DispStrRealMode: mov ax, MessageLength mul dh add ax, LoadMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 add dh, 3 ; 从第 3 行往下显示 int 10h ; int 10h ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ; 作用:从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ;---------------------------------------------------------------------------- ReadSector: push bp mov bp, sp sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl push bx ; 保存 bx mov bl, [BPB_SecPerTrk] ; bl: 除数 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ; 作用:找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 ;---------------------------------------------------------------------------- GetFATEntry: push es push bx push ax mov ax, BaseOfKernelFile ; ┓ sub ax, 0100h ; ┣ 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT mov es, ax ; ┛ pop ax mov byte [bOdd], 0 mov bx, 3 mul bx mov bx, 2 div bx cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 LABEL_EVEN: xor dx, dx mov bx, [BPB_BytsPerSec] div bx push dx mov bx, 0 add ax, SectorNoOfFAT1; 此句执行之后的 ax 就是 FATEntry 所在的扇区号 mov cl, 2 call ReadSector pop dx add bx, dx mov ax, [es:bx] cmp byte [bOdd], 1 jnz LABEL_EVEN_2 shr ax, 4 LABEL_EVEN_2: and ax, 0FFFh LABEL_GET_FAT_ENRY_OK: pop bx pop es ret ;---------------------------------------------------------------------------- ; 函数名: KillMotor ; 作用:关闭软驱马达 ;---------------------------------------------------------------------------- KillMotor: push dx mov dx, 03F2h mov al, 0 out dx, al pop dx ret ;============================================================ ;由实模式跳入保护模式 ;============================================================ [SECTION .s32] ALIGN 32 [BITS 32] LABEL_PM_START: mov ax, SelectorVideo mov gs, ax mov ax, SelectorFlatRW mov ds, ax mov es, ax mov fs, ax mov ss, ax mov esp, TopOfStack ;push szMemChkTitle ;call DispStr ;add esp, 4 ;call DispMemInfo ;call SetupPaging mov ah, 0Fh ; 0000: 黑底 1111: 白字 mov al, 'P' mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 jmp $ [SECTION .data1] ALIGN 32 LABEL_DATA: ; 实模式下使用这些符号 _szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 _szRAMSize: db "RAM size:", 0 _szReturn: db 0Ah, 0 _dwMCRNumber: dd 0 ; Memory Check Result _dwDispPos: dd (80 * 6 + 0) * 2; 屏幕第 6 行, 第 0 列。 _dwMemSize: dd 0 _ARDStruct: ; Address Range Descriptor Structure _dwBaseAddrLow: dd 0 _dwBaseAddrHigh: dd 0 _dwLengthLow: dd 0 _dwLengthHigh: dd 0 _dwType: dd 0 _MemChkBuf: times 256 db 0 ; 保护模式下使用这些符号 szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize szReturn equ BaseOfLoaderPhyAddr + _szReturn dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh dwType equ BaseOfLoaderPhyAddr + _dwType MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf ; 堆栈就在数据段的末尾 StackSpace: times 1000h db 0 TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶
下一篇: itext生成word导航格标题
推荐阅读
-
未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
-
c#一个定时重启的小程序实现代码第1/2页
-
一个32位操作系统的实现2——Loader加载程序
-
未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
-
一个32位操作系统的实现2——Loader加载程序