上篇文章中我们实现了一个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 + $ ; 栈顶
分享到:
相关推荐
常见的引导加载器有GRUB (Grand Unified Bootloader) 和LILO (LInux Loader),它们可以识别并处理多种操作系统,包括Windows、Linux、FreeBSD等。Multios可能采用了类似的理念,通过简单的用户界面让用户能够方便地...
首先,"loader.bin"是一个启动加载器,它在计算机启动过程中负责加载操作系统内核。在Windows系统中,启动加载器通常由NTLDR(New Technology Loader)或BootMgr(Boot Manager)执行,但在特定情况下,如自定义或...
《Orange'S:一个操作系统的实现》这本书提供了从头开始构建一个简易操作系统的完整指南。本书的第一章就带领读者迅速进入实践阶段,通过十分钟的时间就能完成一个最基本的操作系统原型。这一章节不仅涵盖了必要的...
操作系统实验报告——操作系统启动与EOS内核调试 实验名称:操作系统的启动 实验目的:本实验旨在让学生熟悉操作系统集成实验环境OS Lab的基本操作,包括编译、调试EOS操作系统内核及应用程序,从而深入理解操作...
第二阶段(stage2)实现更复杂的任务,如加载内核和操作系统。 5. **操作模式** Boot Loader 通常包含两种不同的操作模式:自动模式和交互模式。自动模式下,Boot Loader 自动执行预定的任务序列;交互模式允许用户...
1. U-Boot:全称为Universal Boot Loader,是一个开放源码的、功能强大的引导加载程序。在Ultra96-V2中,U-Boot负责初始化硬件环境,加载操作系统映像,以及提供交互式命令行接口。它在系统启动过程中扮演着承上启下...
通常,这样的程序会有一个按钮,用户点击后触发上述Java代码,通过图形化的方式方便用户上传数据文件和控制文件,然后调用SQL*Loader进行数据导入。 总的来说,SQL*Loader是Oracle数据库管理中的重要工具,而通过...
在通信处理器中,Boot Loader的重要性尤为突出,因为它不仅要确保硬件平台的正确配置,还要负责引导操作系统——Windows CE.NET的启动。对于基于ARM9微处理器的$3C2410,Boot Loader的编写需要深入理解处理器的架构...
本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...
AsyncTask提供了一个简单的异步执行框架,包括onPreExecute(前置操作)、doInBackground(后台执行)、onProgressUpdate(更新进度)和onPostExecute(结果处理)等步骤。然而,由于AsyncTask存在内存泄漏的风险,...
这里提到的"LED40L288(0000)BOM1-C005 引导程序 loader.rar"是一个特定的引导程序,用于特定型号的设备——可能是某种基于LED显示技术的电子设备,如数字标牌或显示屏。"LED40L288"可能是设备的型号,"0000"可能是...
当操作系统启动一个程序时,加载器负责以下任务: 1. 加载程序:加载器将可执行文件从磁盘读入内存,并分配相应的内存空间。这包括代码、数据、堆栈等区域。 2. 重定位:与链接器类似,加载器也需要进行重定位,但...
自己动手写操作系统在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用C语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架。本书不同于其他的理论型书籍,而是提供给读者一个动手...
Bootloader是固件的一部分,负责在系统启动时加载操作系统或其他执行环境。STM32的Bootloader通常分为两种模式:系统模式(System Memory Boot)和用户模式(User Flash Boot)。在用户模式下,Bootloader可以通过...
实验1涉及到了操作系统的开发环境——OS Lab,以及如何编译和调试EOS操作系统内核和应用程序。操作系统通常会提供软件开发工具包(SDK),以便开发者能够利用操作系统的API来构建应用。在EOS SDK中,包含了头文件...
本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...
### Java杂谈——一个计算机专业学生几年的编程经验汇总谈 #### 一、关于动态加载机制 在Java中,动态加载机制是实现高效资源管理的关键之一。它允许Java虚拟机(JVM)根据需要加载类,而不是一次性加载所有的类。...
第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 ...
- `getView()`方法:这是ListView适配器的关键方法,当一个列表项即将显示时,系统会调用此方法。在`LazyList`中,我们在这个方法内判断图片是否已加载,如果未加载,则异步加载图片,并在加载完成后更新UI。 - `...
在安卓开发中,异步加载图片是一个至关重要的技术点,特别是在处理大数据量或者高分辨率的图像时,如何高效、优雅地加载图片对于用户体验有着决定性的影响。本资料"安卓Android源码——精典源码之演化理解异步加载...