完整的loader引导代码详解

 loader引导主要功能:

loader引导的主要功能分析介绍_What’smean的博客-CSDN博客loader引导的主要功能分析介绍https://blog.csdn.net/weixin_42492218/article/details/127766801?spm=1001.2014.3001.5501

 完整loader代码:

org	10000h
	jmp	Label_Start
 
%include	"fat12.inc"
 
BaseOfKernelFile	equ	0x00
OffsetOfKernelFile	equ	0x100000
 
BaseTmpOfKernelAddr	equ	0x00
OffsetTmpOfKernelFile	equ	0x7E00
 
MemoryStructBufferAddr	equ	0x7E00
 
[SECTION gdt]
 
LABEL_GDT:		dd	0,0
LABEL_DESC_CODE32:	dd	0x0000FFFF,0x00CF9A00
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200
 
GdtLen	equ	$ - LABEL_GDT
GdtPtr	dw	GdtLen - 1
	dd	LABEL_GDT
 
SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT
 
[SECTION gdt64]
 
LABEL_GDT64:		dq	0x0000000000000000
LABEL_DESC_CODE64:	dq	0x0020980000000000
LABEL_DESC_DATA64:	dq	0x0000920000000000
 
GdtLen64	equ	$ - LABEL_GDT64
GdtPtr64	dw	GdtLen64 - 1
		dd	LABEL_GDT64
 
SelectorCode64	equ	LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64	equ	LABEL_DESC_DATA64 - LABEL_GDT64
 
[SECTION .s16]
[BITS 16]
 
Label_Start:
 
	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ax,	0x00
	mov	ss,	ax
	mov	sp,	0x7c00
 
;=======	display on screen : Start Loader......
 
	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0200h		;row 2
	mov	cx,	12
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartLoaderMessage
	int	10h
 
;=======	open address A20
	push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al
	pop	ax
 
	cli
 
	db	0x66
	lgdt	[GdtPtr]	
 
	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax
 
	mov	ax,	SelectorData32
	mov	fs,	ax
	mov	eax,	cr0
	and	al,	11111110b
	mov	cr0,	eax
 
	sti
 
;=======	reset floppy
 
	xor	ah,	ah
	xor	dl,	dl
	int	13h
 
;=======	search kernel.bin
	mov	word	[SectorNo],	SectorNumOfRootDirStart
 
Lable_Search_In_Root_Dir_Begin:
 
	cmp	word	[RootDirSizeForLoop],	0
	jz	Label_No_LoaderBin
	dec	word	[RootDirSizeForLoop]	
	mov	ax,	00h
	mov	es,	ax
	mov	bx,	8000h
	mov	ax,	[SectorNo]
	mov	cl,	1
	call	Func_ReadOneSector
	mov	si,	KernelFileName
	mov	di,	8000h
	cld
	mov	dx,	10h
	
Label_Search_For_LoaderBin:
 
	cmp	dx,	0
	jz	Label_Goto_Next_Sector_In_Root_Dir
	dec	dx
	mov	cx,	11
 
Label_Cmp_FileName:
 
	cmp	cx,	0
	jz	Label_FileName_Found
	dec	cx
	lodsb	
	cmp	al,	byte	[es:di]
	jz	Label_Go_On
	jmp	Label_Different
 
Label_Go_On:
	
	inc	di
	jmp	Label_Cmp_FileName
 
Label_Different:
 
	and	di,	0FFE0h
	add	di,	20h
	mov	si,	KernelFileName
	jmp	Label_Search_For_LoaderBin
 
Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1
	jmp	Lable_Search_In_Root_Dir_Begin
	
;=======	display on screen : ERROR:No KERNEL Found
 
Label_No_LoaderBin:
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0300h		;row 3
	mov	cx,	21
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	NoLoaderMessage
	int	10h
	jmp	$
 
;=======	found loader.bin name in root director struct
 
Label_FileName_Found:
	mov	ax,	RootDirSectors
	and	di,	0FFE0h
	add	di,	01Ah
	mov	cx,	word	[es:di]
	push	cx
	add	cx,	ax
	add	cx,	SectorBalance
	mov	eax,	BaseTmpOfKernelAddr	;BaseOfKernelFile
	mov	es,	eax
	mov	bx,	OffsetTmpOfKernelFile	;OffsetOfKernelFile
	mov	ax,	cx
 
Label_Go_On_Loading_File:
	push	ax
	push	bx
	mov	ah,	0Eh
	mov	al,	'.'
	mov	bl,	0Fh
	int	10h
	pop	bx
	pop	ax
 
	mov	cl,	1
	call	Func_ReadOneSector
	pop	ax
 
;;;;;;;;;;;;;;;;;;;;;;;	
	push	cx
	push	eax
	push	fs
	push	edi
	push	ds
	push	esi
 
	mov	cx,	200h
	mov	ax,	BaseOfKernelFile
	mov	fs,	ax
	mov	edi,	dword	[OffsetOfKernelFileCount]
 
	mov	ax,	BaseTmpOfKernelAddr
	mov	ds,	ax
	mov	esi,	OffsetTmpOfKernelFile
 
Label_Mov_Kernel:	;------------------
	
	mov	al,	byte	[ds:esi]
	mov	byte	[fs:edi],	al
 
	inc	esi
	inc	edi
 
	loop	Label_Mov_Kernel
 
	mov	eax,	0x1000
	mov	ds,	eax
 
	mov	dword	[OffsetOfKernelFileCount],	edi
 
	pop	esi
	pop	ds
	pop	edi
	pop	fs
	pop	eax
	pop	cx
;;;;;;;;;;;;;;;;;;;;;;;	
 
	call	Func_GetFATEntry
	cmp	ax,	0FFFh
	jz	Label_File_Loaded
	push	ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance
 
	jmp	Label_Go_On_Loading_File
 
Label_File_Loaded:
		
	mov	ax, 0B800h
	mov	gs, ax
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	mov	al, 'G'
	mov	[gs:((80 * 0 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。
 
KillMotor:
	
	push	dx
	mov	dx,	03F2h
	mov	al,	0	
	out	dx,	al
	pop	dx
 
;=======	get memory address size type
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0400h		;row 4
	mov	cx,	24
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartGetMemStructMessage
	int	10h
 
	mov	ebx,	0
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	MemoryStructBufferAddr	
 
Label_Get_Mem_Struct:
 
	mov	eax,	0x0E820
	mov	ecx,	20
	mov	edx,	0x534D4150
	int	15h
	jc	Label_Get_Mem_Fail
	add	di,	20
 
	cmp	ebx,	0
	jne	Label_Get_Mem_Struct
	jmp	Label_Get_Mem_OK
 
Label_Get_Mem_Fail:
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0500h		;row 5
	mov	cx,	23
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructErrMessage
	int	10h
	jmp	$
 
Label_Get_Mem_OK:
	
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0600h		;row 6
	mov	cx,	29
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructOKMessage
	int	10h	
 
;=======	get SVGA information
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0800h		;row 8
	mov	cx,	23
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartGetSVGAVBEInfoMessage
	int	10h
 
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	0x8000
	mov	ax,	4F00h
 
	int	10h
 
	cmp	ax,	004Fh
 
	jz	.KO
	
;=======	Fail
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0900h		;row 9
	mov	cx,	23
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetSVGAVBEInfoErrMessage
	int	10h
 
	jmp	$
 
.KO:
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0A00h		;row 10
	mov	cx,	29
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetSVGAVBEInfoOKMessage
	int	10h
 
;=======	Get SVGA Mode Info
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0C00h		;row 12
	mov	cx,	24
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartGetSVGAModeInfoMessage
	int	10h
 
 
	mov	ax,	0x00
	mov	es,	ax
	mov	si,	0x800e
 
	mov	esi,	dword	[es:si]
	mov	edi,	0x8200
 
Label_SVGA_Mode_Info_Get:
 
	mov	cx,	word	[es:esi]
 
;=======	display SVGA mode information
 
	push	ax
	
	mov	ax,	00h
	mov	al,	ch
	call	Label_DispAL
 
	mov	ax,	00h
	mov	al,	cl	
	call	Label_DispAL
	
	pop	ax
 
;=======
	
	cmp	cx,	0FFFFh
	jz	Label_SVGA_Mode_Info_Finish
 
	mov	ax,	4F01h
	int	10h
 
	cmp	ax,	004Fh
 
	jnz	Label_SVGA_Mode_Info_FAIL	
 
	add	esi,	2
	add	edi,	0x100
 
	jmp	Label_SVGA_Mode_Info_Get
		
Label_SVGA_Mode_Info_FAIL:
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0D00h		;row 13
	mov	cx,	24
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetSVGAModeInfoErrMessage
	int	10h
 
Label_SET_SVGA_Mode_VESA_VBE_FAIL:
 
	jmp	$
 
Label_SVGA_Mode_Info_Finish:
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0E00h		;row 14
	mov	cx,	30
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetSVGAModeInfoOKMessage
	int	10h
 
;=======	set the SVGA mode(VESA VBE)
 
	mov	ax,	4F02h
	mov	bx,	4180h	;========================mode : 0x180 or 0x143
	int 	10h
 
	cmp	ax,	004Fh
	jnz	Label_SET_SVGA_Mode_VESA_VBE_FAIL
 
;=======	init IDT GDT goto protect mode 
 
	cli			;======close interrupt
 
	db	0x66
	lgdt	[GdtPtr]
 
;	db	0x66
;	lidt	[IDT_POINTER]
 
	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax	
 
	jmp	dword SelectorCode32:GO_TO_TMP_Protect
 
[SECTION .s32]
[BITS 32]
 
GO_TO_TMP_Protect:
 
;=======	go to tmp long mode
 
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	ss,	ax
	mov	esp,	7E00h
 
	call	support_long_mode
	test	eax,	eax
 
	jz	no_support
 
;=======	init temporary page table 0x90000
 
	mov	dword	[0x90000],	0x91007
	mov	dword	[0x90800],	0x91007		
 
	mov	dword	[0x91000],	0x92007
 
	mov	dword	[0x92000],	0x000083
 
	mov	dword	[0x92008],	0x200083
 
	mov	dword	[0x92010],	0x400083
 
	mov	dword	[0x92018],	0x600083
 
	mov	dword	[0x92020],	0x800083
 
	mov	dword	[0x92028],	0xa00083
 
;=======	load GDTR
 
	db	0x66
	lgdt	[GdtPtr64]
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	gs,	ax
	mov	ss,	ax
 
	mov	esp,	7E00h
 
;=======	open PAE
 
	mov	eax,	cr4
	bts	eax,	5
	mov	cr4,	eax
 
;=======	load	cr3
 
	mov	eax,	0x90000
	mov	cr3,	eax
 
;=======	enable long-mode
 
	mov	ecx,	0C0000080h		;IA32_EFER
	rdmsr
 
	bts	eax,	8
	wrmsr
 
;=======	open PE and paging
 
	mov	eax,	cr0
	bts	eax,	0
	bts	eax,	31
	mov	cr0,	eax
 
	jmp	SelectorCode64:OffsetOfKernelFile
 
;=======	test support long mode or not
 
support_long_mode:
 
	mov	eax,	0x80000000
	cpuid
	cmp	eax,	0x80000001
	setnb	al	
	jb	support_long_mode_done
	mov	eax,	0x80000001
	cpuid
	bt	edx,	29
	setc	al
support_long_mode_done:
	
	movzx	eax,	al
	ret
 
;=======	no support
 
no_support:
	jmp	$
 
;=======	read one sector from floppy
 
[SECTION .s16lib]
[BITS 16]
 
Func_ReadOneSector:
	
	push	bp
	mov	bp,	sp
	sub	esp,	2
	mov	byte	[bp - 2],	cl
	push	bx
	mov	bl,	[BPB_SecPerTrk]
	div	bl
	inc	ah
	mov	cl,	ah
	mov	dh,	al
	shr	al,	1
	mov	ch,	al
	and	dh,	1
	pop	bx
	mov	dl,	[BS_DrvNum]
Label_Go_On_Reading:
	mov	ah,	2
	mov	al,	byte	[bp - 2]
	int	13h
	jc	Label_Go_On_Reading
	add	esp,	2
	pop	bp
	ret
 
;=======	get FAT Entry
 
Func_GetFATEntry:
 
	push	es
	push	bx
	push	ax
	mov	ax,	00
	mov	es,	ax
	pop	ax
	mov	byte	[Odd],	0
	mov	bx,	3
	mul	bx
	mov	bx,	2
	div	bx
	cmp	dx,	0
	jz	Label_Even
	mov	byte	[Odd],	1
 
Label_Even:
 
	xor	dx,	dx
	mov	bx,	[BPB_BytesPerSec]
	div	bx
	push	dx
	mov	bx,	8000h
	add	ax,	SectorNumOfFAT1Start
	mov	cl,	2
	call	Func_ReadOneSector
	
	pop	dx
	add	bx,	dx
	mov	ax,	[es:bx]
	cmp	byte	[Odd],	1
	jnz	Label_Even_2
	shr	ax,	4
 
Label_Even_2:
	and	ax,	0FFFh
	pop	bx
	pop	es
	ret
 
;=======	display num in al
 
Label_DispAL:
 
	push	ecx
	push	edx
	push	edi
	
	mov	edi,	[DisplayPosition]
	mov	ah,	0Fh
	mov	dl,	al
	shr	al,	4
	mov	ecx,	2
.begin:
 
	and	al,	0Fh
	cmp	al,	9
	ja	.1
	add	al,	'0'
	jmp	.2
.1:
 
	sub	al,	0Ah
	add	al,	'A'
.2:
 
	mov	[gs:edi],	ax
	add	edi,	2
	
	mov	al,	dl
	loop	.begin
 
	mov	[DisplayPosition],	edi
 
	pop	edi
	pop	edx
	pop	ecx
	
	ret
 
 
;=======	tmp IDT
 
IDT:
	times	0x50	dq	0
IDT_END:
 
IDT_POINTER:
		dw	IDT_END - IDT - 1
		dd	IDT
 
;=======	tmp variable
 
RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0
OffsetOfKernelFileCount	dd	OffsetOfKernelFile
 
DisplayPosition		dd	0
 
;=======	display messages
 
StartLoaderMessage:	db	"Start Loader"
NoLoaderMessage:	db	"ERROR:No KERNEL Found"
KernelFileName:		db	"KERNEL  BIN",0
StartGetMemStructMessage:	db	"Start Get Memory Struct."
GetMemStructErrMessage:	db	"Get Memory Struct ERROR"
GetMemStructOKMessage:	db	"Get Memory Struct SUCCESSFUL!"
 
StartGetSVGAVBEInfoMessage:	db	"Start Get SVGA VBE Info"
GetSVGAVBEInfoErrMessage:	db	"Get SVGA VBE Info ERROR"
GetSVGAVBEInfoOKMessage:	db	"Get SVGA VBE Info SUCCESSFUL!"
 
StartGetSVGAModeInfoMessage:	db	"Start Get SVGA Mode Info"
GetSVGAModeInfoErrMessage:	db	"Get SVGA Mode Info ERROR"
GetSVGAModeInfoOKMessage:	db	"Get SVGA Mode Info SUCCESSFUL!"

实模式下:

代码块1:

org	10000h			
;本系统的内核程序起始地址位于物理地址0x100000 ( 1MB)处,
; 因为1 MB以下的物理地址并不全是可用内存地址空间,
; 这段物理地址被划分成若干个子空间段,
; 它们可以是内存空间、非内存空间以及地址空洞。
; 随着内核体积的不断增长,
; 未来的内核程序很可能会超过1 MB,
; 因此让内核程序跳过这些纷繁复杂的内存空间,
; 从平坦的1 MB地址开始,这是一个非常不错的选择。

	jmp	Label_Start
 
%include	"fat12.inc"		
;fat 12.inc文件是从Boot引导程序中提取出的FAT12文件系统结构
 
BaseOfKernelFile	equ	0x00
OffsetOfKernelFile	equ	0x100000
 
BaseTmpOfKernelAddr	equ	0x00

; 内存地址ox7E00是内核程序的临时转存空间,
; 由于内核程序的读取操作是通过BIOS中断服务程序INT 13h实现的,
; BIOS在实模式下只支持上限为1MB的物理地址空间寻址,
; 所以必须先将内核程序读入到临时转存空间,
; 然后再通过特殊方式搬运到1MB以上的内存空间中。
; 当内核程序被转存到最终内存空间后,
; 这个临时转存空间就可另作他用,
; 此处将其改为内存结构数据的存储空间,
; 供内核程序在初始化时使用。

OffsetTmpOfKernelFile	equ	0x7E00
 
MemoryStructBufferAddr	equ	0x7E00

代码块2:

[SECTION .s16]
[BITS 16]
 
Label_Start:
 
	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ax,	0x00
	mov	ss,	ax
	mov	sp,	0x7c00
 
;=======	display on screen : Start Loader......
 
	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0200h		;row 2
	mov	cx,	12
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartLoaderMessage
	int	10h

        这段程序定义了一个名为.s16的段,BITS伪指令可以通知NASM编译器生成的代码,将运行在16位宽的处理器上或者运行在32位宽的处理器上,语法是'BITS 16'或'BITS 32 '。

代码块3:

;=======	open address A20
	push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al
	pop	ax
 
	cli
 
	db	0x66
	lgdt	[GdtPtr]	
 
	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax
 
	mov	ax,	SelectorData32
	mov	fs,	ax
	mov	eax,	cr0
	and	al,	11111110b
	mov	cr0,	eax
 
	sti

        这段代码的起始部分开启地址A20功能(A20总线,是x86体系的扩充电子线路之一。A20总线是专门用来转换地址总线的第二十一位。),此项功能属于历史遗留问题。最初的处理器只有20根地址线,这使得处理器只能寻址1MB以内的物理地址空间,如果超过1 MB范围的寻址操作,也只有低20位是有效地址。随着处理器寻址能力的不断增强,20根地址线已经无法满足今后的开发需求。为了保证硬件平台的向下兼容性,便出现了一个控制开启或禁止1 MB以上地址空间的开关。当时的8042键盘控制器上恰好有空闲的端口引脚(输出端口P2,引脚P21),从而使用此引脚作为功能控制开关,即A20功能。如果A20引脚为低电平(数值O),那么只有低20位地址有效,其他位均为0。
        在机器上电时,默认情况下A20地址线是被禁用的,所以操作系统必须采用适当的方法开启它。由于硬件平台的兼容设备种类繁杂,进而出现多种开启A20功能的方法。
        开启A20功能的常用方法是操作键盘控制器,由于键盘控制器是低速设备,以至于功能开启速
度相对较慢。
        A20快速门(Fast Gate A20 ),它使用IO端口0x92来处理A20信号线。对于不含键盘控制器的操作系统,就只能使用Ox92端口来控制,但是该端口有可能被其他设备使用。
        使用BIOS中断服务程序INT 15h的主功能号AX=2401可开启A20地址线,功能号AX=2400可禁用A20地址线,功能号AX=2403可查询A20地址线的当前状态。
        还有一种方法是,通过读Oxee端口来开启A20信号线,而写该端口则会禁止A20信号线。本系统通过访问A20快速门来开启A20功能,即置位0x92端口的第1位。

代码块4:

主要功能实现:类似于boot引导中loader.bin文件的寻找,这里找的时kernel.bin文件,这里不过多赘述,详情请看:

完整boot引导代码详解(完整无注释代码boot.asm+简单loader.asm)_What’smean的博客-CSDN博客完整boot引导代码详解(完整无注释代码boot.asm+简单loader.asm)https://blog.csdn.net/weixin_42492218/article/details/127752246?spm=1001.2014.3001.5501

;=======	search kernel.bin
	mov	word	[SectorNo],	SectorNumOfRootDirStart
 
Lable_Search_In_Root_Dir_Begin:
 
	cmp	word	[RootDirSizeForLoop],	0
	jz	Label_No_LoaderBin
	dec	word	[RootDirSizeForLoop]	
	mov	ax,	00h
	mov	es,	ax
	mov	bx,	8000h
	mov	ax,	[SectorNo]
	mov	cl,	1
	call	Func_ReadOneSector
	mov	si,	KernelFileName
	mov	di,	8000h
	cld
	mov	dx,	10h
	
Label_Search_For_LoaderBin:
 
	cmp	dx,	0
	jz	Label_Goto_Next_Sector_In_Root_Dir
	dec	dx
	mov	cx,	11
 
Label_Cmp_FileName:
 
	cmp	cx,	0
	jz	Label_FileName_Found
	dec	cx
	lodsb	
	cmp	al,	byte	[es:di]
	jz	Label_Go_On
	jmp	Label_Different
 
Label_Go_On:
	
	inc	di
	jmp	Label_Cmp_FileName
 
Label_Different:
 
	and	di,	0FFE0h
	add	di,	20h
	mov	si,	KernelFileName
	jmp	Label_Search_For_LoaderBin
 
Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1
	jmp	Lable_Search_In_Root_Dir_Begin
	
;=======	display on screen : ERROR:No KERNEL Found
 
Label_No_LoaderBin:
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0300h		;row 3
	mov	cx,	21
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	NoLoaderMessage
	int	10h
	jmp	$

代码块5:

;=======	found loader.bin name in root director struct
 
Label_FileName_Found:
	mov	ax,	RootDirSectors
	and	di,	0FFE0h
	add	di,	01Ah
	mov	cx,	word	[es:di]
	push	cx
	add	cx,	ax
	add	cx,	SectorBalance
	mov	eax,	BaseTmpOfKernelAddr	;BaseOfKernelFile
	mov	es,	eax
	mov	bx,	OffsetTmpOfKernelFile	;OffsetOfKernelFile
	mov	ax,	cx
 
Label_Go_On_Loading_File:
	push	ax
	push	bx
	mov	ah,	0Eh
	mov	al,	'.'
	mov	bl,	0Fh
	int	10h
	pop	bx
	pop	ax
 
	mov	cl,	1
	call	Func_ReadOneSector
	pop	ax
 
;;;;;;;;;;;;;;;;;;;;;;;	
	push	cx
	push	eax
	push	fs
	push	edi
	push	ds
	push	esi
 
	mov	cx,	200h
	mov	ax,	BaseOfKernelFile
	mov	fs,	ax
	mov	edi,	dword	[OffsetOfKernelFileCount]
 
	mov	ax,	BaseTmpOfKernelAddr
	mov	ds,	ax
	mov	esi,	OffsetTmpOfKernelFile
 
Label_Mov_Kernel:	;------------------
	
	mov	al,	byte	[ds:esi]
	mov	byte	[fs:edi],	al
 
	inc	esi
	inc	edi
 
	loop	Label_Mov_Kernel
 
	mov	eax,	0x1000
	mov	ds,	eax
 
	mov	dword	[OffsetOfKernelFileCount],	edi
 
	pop	esi
	pop	ds
	pop	edi
	pop	fs
	pop	eax
	pop	cx
;;;;;;;;;;;;;;;;;;;;;;;	
 
	call	Func_GetFATEntry
	cmp	ax,	0FFFh
	jz	Label_File_Loaded
	push	ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance
 
	jmp	Label_Go_On_Loading_File
 

        这部分程序负责将内核程序读取到临时转存空间中,随后再将其移动至1 MB以上的物理内存空间。为了避免转存环节发生错误,还是一个字节一个字节的复制为妙,借助汇编指令LOOP可完成此项工作。由于内核体积庞大必须逐个簇地读取和转存,那么每次转存内核程序片段时必须保存目标偏移值,该值(EDI寄存器)保存于临时变量offsetofKernelFilecount中。


代码块6:


当内核程序被加载到1MB以上物理内存地址后,使用代码块6在屏幕的第0行第39列显示一个字符'G'。此举不仅可以隔离内核程序的加载过程,还引入了一种高效的字符显示方法。

Label_File_Loaded:
		
	mov	ax, 0B800h
	mov	gs, ax
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	mov	al, 'G'
	mov	[gs:((80 * 0 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。
 

代码块7:

KillMotor:
	
	push	dx
	mov	dx,	03F2h
	mov	al,	0	
	out	dx,	al
	pop	dx
 

关闭软驱马达是通过向IO端口3F2h写入控制命令实现的,此端口控制着软盘驱动器的不少硬件功能。下表罗列出了I/O端口3F2h可控制的软盘驱动器功能。
 

         既然已将内核程序从软盘加载到内存,便可放心地向此IO端口写入数值0关闭全部软盘驱动器。在使用OUT汇编指令操作IO端口时,需要特别注意8位端口与16位端口的使用区别。
以下是Intel官方白皮书对OUT指令的概括描述:
        OUT指令的源操作数根据端口位宽可以选用AL/AX/EAX寄存器;目的操作数可以是立即数或DX寄存器,其中立即数的取值范围只能是8位宽(0~FFh ),而DX寄存器允许的取值范围是16位宽(0~FFFFh ).

代码块8:

代码功能:物理地址空间信息由一个结构体数组构成,计算机平台的地址空间划分情况都能从这个结构体数组中反映出来,它记录的地址空间类型包括可用物理内存地址空间、设备寄存器地址空间、内存空洞等,详细内容将会在第7章中讲解。
        这段程序借助BIOS中断服务程序INT 15h来获取物理地址空间信息,并将其保存在0x7E00地址处的临时转存空间里,操作系统会在初始化内存管理单元时解析该结构体数组。

;=======	get memory address size type
 
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0400h		;row 4
	mov	cx,	24
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartGetMemStructMessage
	int	10h
 
	mov	ebx,	0
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	MemoryStructBufferAddr	
 
Label_Get_Mem_Struct:
 
	mov	eax,	0x0E820
	mov	ecx,	20
	mov	edx,	0x534D4150
	int	15h
	jc	Label_Get_Mem_Fail
	add	di,	20
 
	cmp	ebx,	0
	jne	Label_Get_Mem_Struct
	jmp	Label_Get_Mem_OK
 
Label_Get_Mem_Fail:
 
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0500h		;row 5
	mov	cx,	23
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructErrMessage
	int	10h
	jmp	$
 
Label_Get_Mem_OK:
	
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0600h		;row 6
	mov	cx,	29
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructOKMessage
	int	10h	

代码块9:

;=======	display num in al
 
Label_DispAL:
 
	push	ecx
	push	edx
	push	edi
	
	mov	edi,	[DisplayPosition]
	mov	ah,	0Fh
	mov	dl,	al
	shr	al,	4
	mov	ecx,	2
.begin:
 
	and	al,	0Fh
	cmp	al,	9
	ja	.1
	add	al,	'0'
	jmp	.2
.1:
 
	sub	al,	0Ah
	add	al,	'A'
.2:
 
	mov	[gs:edi],	ax
	add	edi,	2
	
	mov	al,	dl
	loop	.begin
 
	mov	[DisplayPosition],	edi
 
	pop	edi
	pop	edx
	pop	ecx
	
	ret

        通过这个程序模块可将十六进制数值显示在屏幕上,执行Label_DispAL模块需要提供的参数说明如下。
                模块Label_DispAL功能:显示十六进制数字。AL=要显示的十六进制数。
        Label_DispAL模块首先会保存即将变更的寄存器值到栈中,然后把变量DisplayPosition保存的屏幕偏移值(字符游标索引值)载入到EDI寄存器中,并向AH寄存器存入字体的颜色属性值。为了先显示AL寄存器的高四位数据,暂且先把AL寄存器的低四位数据保存在DL寄存器。接着将AL寄存器的高四位数值与9比较,如果大于9,则减去oAh并与字符'A'相加,否则,直接将其与字符'o'相加。然后将AX寄存器(AL与AH寄存器组合而成)的值,保存至以GS段寄存器为基址、DisplayPosition变量为偏移的显示字符内存空间中。最后再按上述执行步骤将AL寄存器的低四位数值显示出来。

代码块10:

;=======	set the SVGA mode(VESA VBE)
 
	mov	ax,	4F02h
	mov	bx,	4180h	;========================mode : 0x180 or 0x143
	int 	10h
 
	cmp	ax,	004Fh
	jnz	Label_SET_SVGA_Mode_VESA_VBE_FAIL

        这段程序设置了SVGA芯片的显示模式,代码中的0x180和0x143是显示模式号,下表是这两种显示模式号的属性信息。

        此部分内容是关于VBE ( VESABIOSEXTENSION)的显示模式,通过设置不同的显示模式号,可配置出不同的屏幕分辨率、每个像素点的数据位宽、颜色格式等。这些信息皆是从Bochs虚拟平台的SVGA芯片中获得。

实模式----->保护模式

代码块11:

[SECTION gdt]
 
LABEL_GDT:		dd	0,0
LABEL_DESC_CODE32:	dd	0x0000FFFF,0x00CF9A00
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200
 
GdtLen	equ	$ - LABEL_GDT
GdtPtr	dw	GdtLen - 1
	dd	LABEL_GDT
 
SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT

        本段程序创建了一个临时GDT表。为了避免保护模式段结构的复杂性,此处将代码段和数据段的段基地址都设置在0x00000000地址处,段限长为0xffffffff,即段可以索引0~4GB内存地址空间。

        因为GDT表的基地址和长度必须借助LGDT汇编指令才能加载到GDTR寄存器,而GDTR寄存器是一个6 B的结构,结构中的低2 B保存GDT表的长度,高4B保存GDT表的基地址,标识符cdtPtr是此结构的起始地址。这个GDT表曾经用于开启Big Real Mode模式,由于其数据段被设置成平坦地址空间( 0~4GB地址空间),故此FS段寄存器可以寻址整个4 GB内存地址空间。
        代码中的标识符selectorCode32和selectorData32是两个段选择子( Selector ),它们是段描述符在GDT表中的索引号。

代码块12:

;=======	tmp IDT
 
IDT:
	times	0x50	dq	0
IDT_END:
 
IDT_POINTER:
		dw	IDT_END - IDT - 1
		dd	IDT
 

;=======	init IDT GDT goto protect mode 
 
	cli			;======close interrupt
 
	db	0x66
	lgdt	[GdtPtr]
 
;	db	0x66
;	lidt	[IDT_POINTER]
 
	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax	
 
	jmp	dword SelectorCode32:GO_TO_TMP_Protect

        在处理器切换至保护模式前,引导加载程序已使用cLr指令禁止外部中断,所以在切换到保护模式的过程中不会产生中断和异常,进而不必完整地初始化IDT,只要有相应的结构体即可。如果能够保证处理器在模式切换的过程中不会产生异常,即使没有IDT也可以。
当保护模式的系统数据结构准备就绪后,便可着手编写模式切换程序。处理器从实模式进入保护模式的契机是,执行Mov汇编指令置位CRO控制寄存器的PE标志位(可同时置位CRO寄存器的PG标志位以开启分页机制)。进入保护模式后,处理器将从0特权级(CPL=0)开始执行。为了保证代码在不同种Intel处理器中的前后兼容性

建议遵循以下步骤执行模式切换操作:

  • (1)执行CLI汇编指令禁止可屏蔽硬件中断,对于不可屏蔽中断NMI只能借助外部电路才能禁止。(模式切换程序必须保证在切换过程中不能产生异常和中断。)
  • (2)执行LGDT汇编指令将GDT的基地址和长度加载到GDTR寄存器。
  • (3)执行Mov CRo汇编指令位置CRO控制寄存器的PE标志位。(可同时置位CRO控制寄存器的PG标志位。)
  • (4)一旦Mov CRO汇编指令执行结束,紧随其后必须执行一条远跳转( far JMP)或远调用(farCALL)指令,以切换到保护模式的代码段去执行。(这是一个典型的保护模式切换方法。)
  • (5)通过执行JMP或cALL指令,可改变处理器的执行流水线,进而使处理器加载执行保护模式的代码段。
  • (6)如果开启分页机制,那么MOV CRo指令和JMP/CALL(跳转/调用)指令必须位于同一性地址映射的页面内。(因为保护模式和分页机制使能后的物理地址,与执行JMP/CALL指令前的线性地址相同。)至于JMP或cALL指令的目标地址,则无需进行同一性地址映射(线性地址与物理地址重合)。
  • (7)如需使用LDT,则必须借助LLDT汇编指令将GDT内的LDT段选择子加载到LDTR寄存器中。(8)执行LTR汇编指令将一个TSS段描述符的段选择子加载到TR任务寄存器。处理器对TSS段结构无特殊要求,凡是可写的内存空间均可。
  • (9)进入保护模式后,数据段寄存器仍旧保留着实模式的段数据,必须重新加载数据段选择子或使用JMP/CALL指令执行新任务,便可将其更新为保护模式。(执行步骤(4)的uMP或cALL指令已将代码段寄存器更新为保护模式。)对于不使用的数据段寄存器(DS和SS寄存器除外),可将NOLL段选择子加载到其中。
  • (10)执行rIDT指令,将保护模式下的IDT表的基地址和长度加载到IDTR寄存器。
  • (11)执行sTr指令使能可屏蔽硬件中断,并执行必要的硬件操作使能NMI不可屏蔽中断。

从保护模式---->IA-32e模式(64位模式)

代码块13:

[SECTION gdt64]
 
LABEL_GDT64:		dq	0x0000000000000000
LABEL_DESC_CODE64:	dq	0x0020980000000000
LABEL_DESC_DATA64:	dq	0x0000920000000000
 
GdtLen64	equ	$ - LABEL_GDT64
GdtPtr64	dw	GdtLen64 - 1
		dd	LABEL_GDT64
 
SelectorCode64	equ	LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64	equ	LABEL_DESC_DATA64 - LABEL_GDT64
 
[SECTION .s32]
[BITS 32]
 
GO_TO_TMP_Protect:
 
;=======	go to tmp long mode
 
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	ss,	ax
	mov	esp,	7E00h
 
	call	support_long_mode
	test	eax,	eax
 
	jz	no_support

        一旦进入保护模式首要任务是初始化各个段寄存器以及栈指针,然后检测处理器是否支持IA-32e模式(或称长模式)。如果不支持IA-32e模式就进入待机状态,不做任何操作。如果支持IA-32e模式,则开始向IA-32e模式切换。

代码块14:

通过此模块可检测出处理器是否支持IA-32e模式。

;=======	test support long mode or not
 
support_long_mode:
 
	mov	eax,	0x80000000
	cpuid
	cmp	eax,	0x80000001
	setnb	al	
	jb	support_long_mode_done
	mov	eax,	0x80000001
	cpuid
	bt	edx,	29
	setc	al
support_long_mode_done:
	
	movzx	eax,	al
	ret
 
;=======	no support
 
no_support:
	jmp	$

        由于CPUID汇编指令的扩展功能项0x80000001的第29位,指示处理器是否支持IA-32e模式,故此本段程序首先检测当前处理器对cPUID汇编指令的支持情况,判断该指令的最大扩展功能号是否超过0x8000000。只有当cPUID指令的扩展功能号大于等于0x80000001时,才有可能支持64位的长模式,因此要先检测cPUID指令支持的扩展功能号,再读取相应的标志位。最后将读取的结果存入EAX寄存器供模块调用者判断。以下是对CPUID指令的概括描述。

  •  EFLAGS标志寄存器的ID标志位(第21位)表明处理器是否支持cPUID指令。如果程序可以操作(置位和复位)此标志位,则说明处理器支持cPUID指令,CPUID指令在64位模式和32位模式的执行效果相同。
  • CPUID指令会根据EAX寄存器传入的基础功能号(有时还需要向ECX寄存器传入扩展功能号),查询处理器的鉴定信息和机能信息,其返回结果将保存在EAX、EBX、ECX和EDX寄存器中。
     

代码块15:

;=======	load GDTR
 
	db	0x66
	lgdt	[GdtPtr64]
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	gs,	ax
	mov	ss,	ax
 
	mov	esp,	7E00h

        使用LGDT汇编指令,加载IA-32e模式的临时GDT表到GDTR寄存器中,并将临时GDT表的数据段初始化到各个数据段寄存器(除CS段寄存器外)中。由于代码段寄存器CS不能采用直接赋值的方式来改变,所以必须借助跨段跳转指令( far JMP )或跨段调用指令( far CALL)才能实现改变。

代码块16:

;=======	open PAE        开启物理地址扩展功能(PAE)。
 
	mov	eax,	cr4
	bts	eax,	5
	mov	cr4,	eax
 
;=======	load	cr3        将临时页目录的首地址设置到CR3控制寄存器中。

 
	mov	eax,	0x90000
	mov	cr3,	eax
 
;=======	enable long-mode   当页目录基地址已加载到CR3控制寄存器,通过置位IA32_EFER寄存器的LME ;标志位激活IA-32e模式。

 
	mov	ecx,	0C0000080h		;IA32_EFER
	rdmsr
 
	bts	eax,	8
	wrmsr
 
;=======	open PE and paging
 
	mov	eax,	cr0
	bts	eax,	0
	bts	eax,	31
	mov	cr0,	eax
 
	jmp	SelectorCode64:OffsetOfKernelFile

代码段功能介绍 :   摘自《一个64位操作系统的设计与实现》

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

What’smean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值