欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一个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则只需一或几句即可实现,整体实现代码如下:

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 + $	; 栈顶

相关标签: Windows OS