`
chen6485
  • 浏览: 17992 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类

一个32位操作系统的实现2——Loader加载程序

阅读更多
       上篇文章中我们实现了一个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 + $	; 栈顶

分享到:
评论

相关推荐

    神奇的多系统引导小程序——Multios.zip

    常见的引导加载器有GRUB (Grand Unified Bootloader) 和LILO (LInux Loader),它们可以识别并处理多种操作系统,包括Windows、Linux、FreeBSD等。Multios可能采用了类似的理念,通过简单的用户界面让用户能够方便地...

    启动光盘引导文件loader.bin,w2ksect.bin

    首先,"loader.bin"是一个启动加载器,它在计算机启动过程中负责加载操作系统内核。在Windows系统中,启动加载器通常由NTLDR(New Technology Loader)或BootMgr(Boot Manager)执行,但在特定情况下,如自定义或...

    操作系统的实现

    《Orange'S:一个操作系统的实现》这本书提供了从头开始构建一个简易操作系统的完整指南。本书的第一章就带领读者迅速进入实践阶段,通过十分钟的时间就能完成一个最基本的操作系统原型。这一章节不仅涵盖了必要的...

    操作系统实验2.doc

    操作系统实验报告——操作系统启动与EOS内核调试 实验名称:操作系统的启动 实验目的:本实验旨在让学生熟悉操作系统集成实验环境OS Lab的基本操作,包括编译、调试EOS操作系统内核及应用程序,从而深入理解操作...

    Boot Loader 的设计与实现

    第二阶段(stage2)实现更复杂的任务,如加载内核和操作系统。 5. **操作模式** Boot Loader 通常包含两种不同的操作模式:自动模式和交互模式。自动模式下,Boot Loader 自动执行预定的任务序列;交互模式允许用户...

    用于Ultra96-V2的引导加载程序(U-Boot、FSBL、PMUFW、ATF)_Tcl_下载.zip

    1. U-Boot:全称为Universal Boot Loader,是一个开放源码的、功能强大的引导加载程序。在Ultra96-V2中,U-Boot负责初始化硬件环境,加载操作系统映像,以及提供交互式命令行接口。它在系统启动过程中扮演着承上启下...

    一个简单的调用sql loader批量数据导入程序

    通常,这样的程序会有一个按钮,用户点击后触发上述Java代码,通过图形化的方式方便用户上传数据文件和控制文件,然后调用SQL*Loader进行数据导入。 总的来说,SQL*Loader是Oracle数据库管理中的重要工具,而通过...

    通信处理器的Boot Loader及I_O接口驱动程序.pdf

    在通信处理器中,Boot Loader的重要性尤为突出,因为它不仅要确保硬件平台的正确配置,还要负责引导操作系统——Windows CE.NET的启动。对于基于ARM9微处理器的$3C2410,Boot Loader的编写需要深入理解处理器的架构...

    自己动手写操作系统(含源代码).part2

     本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...

    安卓Android源码——演化理解异步加载图片.zip

    AsyncTask提供了一个简单的异步执行框架,包括onPreExecute(前置操作)、doInBackground(后台执行)、onProgressUpdate(更新进度)和onPostExecute(结果处理)等步骤。然而,由于AsyncTask存在内存泄漏的风险,...

    LED40L288(0000)BOM1-C005 引导程序 loader.rar

    这里提到的"LED40L288(0000)BOM1-C005 引导程序 loader.rar"是一个特定的引导程序,用于特定型号的设备——可能是某种基于LED显示技术的电子设备,如数字标牌或显示屏。"LED40L288"可能是设备的型号,"0000"可能是...

    链接器和加载器中文版_链接器和加载器_linkerandloader

    当操作系统启动一个程序时,加载器负责以下任务: 1. 加载程序:加载器将可执行文件从磁盘读入内存,并分配相应的内存空间。这包括代码、数据、堆栈等区域。 2. 重定位:与链接器类似,加载器也需要进行重定位,但...

    自己动手写操作系统 pdf

    自己动手写操作系统在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用C语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架。本书不同于其他的理论型书籍,而是提供给读者一个动手...

    flash_loader_demo_v2.8.0

    Bootloader是固件的一部分,负责在系统启动时加载操作系统或其他执行环境。STM32的Bootloader通常分为两种模式:系统模式(System Memory Boot)和用户模式(User Flash Boot)。在用户模式下,Bootloader可以通过...

    操作系统实验讲学.pdf

    实验1涉及到了操作系统的开发环境——OS Lab,以及如何编译和调试EOS操作系统内核和应用程序。操作系统通常会提供软件开发工具包(SDK),以便开发者能够利用操作系统的API来构建应用。在EOS SDK中,包含了头文件...

    自己动手写操作系统(含源代码).part1

     本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从...

    java杂谈——一个计算机专业学生几年的编程经验汇总谈

    ### Java杂谈——一个计算机专业学生几年的编程经验汇总谈 #### 一、关于动态加载机制 在Java中,动态加载机制是实现高效资源管理的关键之一。它允许Java虚拟机(JVM)根据需要加载类,而不是一次性加载所有的类。...

    自己动手写操作系统 电子工业出版社 pdf

    第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 ...

    LazyList——对ListView加载网络图片的优化,解决各种问题

    - `getView()`方法:这是ListView适配器的关键方法,当一个列表项即将显示时,系统会调用此方法。在`LazyList`中,我们在这个方法内判断图片是否已加载,如果未加载,则异步加载图片,并在加载完成后更新UI。 - `...

    安卓Android源码——精典源码之演化理解异步加载图片.zip

    在安卓开发中,异步加载图片是一个至关重要的技术点,特别是在处理大数据量或者高分辨率的图像时,如何高效、优雅地加载图片对于用户体验有着决定性的影响。本资料"安卓Android源码——精典源码之演化理解异步加载...

Global site tag (gtag.js) - Google Analytics