上篇文章中我们实现了一个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则只需一或几句即可实现,整体实现代码如下:
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 + $ ; 栈顶
分享到:
相关推荐
本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...
自己动手写操作系统在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用C语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架。本书不同于其他的理论型书籍,而是提供给读者一个动手...
本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...
第1章 马上动手写一个最小的“操作系统”1 1.1 准备工作1 1.2 10分钟完成的操作系统1 1.3 Boot Sector3 1.4 代码解释3 1.5 水面下的冰山5 1.6 回顾6 第2章 搭建你的工作环境7 2.1 虚拟计算机(Virtual PC)7 2.1.1 ...
>kernel.S————1)中断处理程序 和 2)加载 idt到IDTR寄存器的函数lodidt >kernel.h————lodidt函数的声明 >my_interrupt.S————创建中断描述符 >my_interrupt.h————声明idt_inti函数 >main.c——...
try-catch 语句由一个 try 块和其后所跟的一个或多个 catch 子句(为不同的异常指定处理程序)构成。try-catch 语句采用下列形式之一: try try-block catch (exception-declaration-1) catch-block-1 catch ...
1.5.1 模块化和简洁的操作系统 1.5.2 实时操作系统 1.5.3 硬件支持 1.5.4 CE 6.0 R2的新特性 1.5.5 定制的UI 1.5.6 有线和无线连接 1.5.7 图形和多媒体 1.5.8 多语言的国际化定位 1.5.9 实时通信和VolP 1.5.10 OS...
Android Volley Jar,Android Volley框架是android开发中不可或缺的一个重要框架,想使用Android开发更加便捷的途径就是使用Volley框架了。 Android Volley Jar框架简介 我们平时在开发Android应用的时候不可避免...
11.5加载器Loader 251 11.6按钮对象SimpleButton 254 11.7文本框TextField 255 11.7.1文本类型 255 11.7.2外观控制 256 11.7.3设置文本 256 11.7.4处理HTML 258 11.7.5输入限制 260 11.7.6文本格式化 260 11.7.7多行...
nand_init()函数在两个文件中实现。其调用与 CFG_NAND_LEGACY 宏有 关,如果没有定义这个宏,系统调用 drivers/nand/nand.c 中的 nand_init();否则调用自己在 本文件中的 nand_init()函数,本例使用后者。fs2410.c...
第一部分 重新思考Web应用 第1章 一种新的Web设计方法 1.1 为什么需要Ajax富客户端? 1.1.1 比较用户体验 1.1.2 网络延迟 1.1.3 异步交互 1.1.4 独占或瞬态的使用模式 1.1.5 忘掉Web 1.2 Aiax的四个基本原则 1.2.1 ...