007_Loader引导加载程序

Loader主要负责检测硬件信息、处理器模式切换和向内核传递数据。在实模式与保护模式之间切换时,Loader会创建GDT(全局描述符表),并通过LGDT指令加载到GDTR寄存器。开启A20信号线允许访问超过1MB的地址空间。Loader还会搜索并加载kernel.bin文件到内存特定位置。
摘要由CSDN通过智能技术生成

Loader原理

Loader主要负责检测硬件信息,处理器模式切换和向内核传递数据三项工作

检测硬件信息

Loader主要通过BIOS的中断服务程序来获取硬件信息,这些硬件信息大部分都只能在实模式下获取,而系统工作于非实模式下,那么就需要在进入内核程序前将这些信息检测出来,作为参数传递给内核程序
在这些信息中有物理地址空间信息,显示器信息等等。

处理器模式切换

从BIOS运行的实模式,到32位的保护模式,再到64位操作系统使用的IA-32e模式,在各模式的切换中,Loader都必须手动创建各模式要使用的临时数据,并按照标准进行模式之间的跳转。

向内核传递数据

Loader向内核主要转递两种数据,一种是控制信息,另一种则是之前说的硬件信息
控制信息一般用于控制内核执行流程或限制内核的某些功能,如启动模式(图形还是字符)、启动方式(网络还是本地)、终端重定向(串口或显示器)

代码部分

由于这部分涉及到很多我没学过的知识,所以我也不确定能不能讲清楚,只能试着解释一下,没懂的地方就暂且存疑留到后面解释

设置GDT数据结构

org 0x1000

jmp Label_Start

%include "fat16.inc"

[SECTION gdt]      ; SECTION在这里的作用就是起一个修饰的作用,并不会在编译时产生什么作用

LABEL_GDT: dd 0, 0                  ; GDT第一个描述符定义为null
; BASE: 0
; limit: 0xfffff
; G: 1
; D: 1
; P: 1
; DPL: 0
; type: 1010 , 代码段,表示权限可执行,可读
LABEL_DESC_CODE32: dd 0x0000ffff, 0x00cf9a00    ; 代码段描述符
; type: 0010,数据段,表示权限可读可写
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200	; 数据段描述符

GdtLen	equ	$ - LABEL_GDT       ; GDT长度
GdtPtr	dw	GdtLen - 1          ; GDT段界限
	dd	LABEL_GDT               ; GDT基地址

SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT   ; 代码段选择子,一个16位数据,用来指向代码段描述符的起始地址
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT

[SECTION gdt64]					; IA-32e的GDP和保护模式是一样的位数,但是其内存是强制平坦型的,我没有查到强制平坦的含义,理解应该是不需要划分段了,所以取消了段基址和段限长两个参数的意义

LABEL_GDT64:		dq	0x0000000000000000
LABEL_DESC_CODE64:	dq	0x00209800 00000000
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

寻址方式

GDT(globel description table)全局描述符表,再次之前的实模式下,通过[段基址:段偏移]可以访问所有的内存空间,在变换到保护模式后,访问内存空间的过程变成:

  1. 访问GDTR寄存器(里面存放的是GDT的首地址)
  2. 通过一个段选择子找到要访问的那个数据的段描述符
  3. 表项中存放的就是要访问的那个地址的真实地址
    还有一点要注意的时,在实模式下,段寄存器是16位的,但是,随着地址线的变多,段寄存器位数没有变,所以为了访问更大的地址空间,段偏移量就要够大,如何存储每个段的段偏移量就产生了问题,这也就产生了使用GDT来解决这个问题的办法
GDT结构

image.png
base:32位段基地址,之所以这么分散,一句话就是历史原因
limit:20位段限长,用来鉴定访问的地址是否超出界限,也就是“保护”的地址
G:=0,最大段限长为 2 20 = 1 M B 2^{20}=1MB 220=1MB,段限长单位为1字节
=1,最大段限长为 2 12 × 2 20 = 4 G B 2^{12} \times 2^{20}=4GB 212×220=4GB,段限长的单位是4KB
D/B:如果描述符指向代码段,这就是D位,D=1使用32位地址和32/8操作数
D=0使用16位地址和16/8操作数
如果描述符指向数据段,这就是B位,B=1段上限为4GB
B=0段上限为64KB
如果描述符指向堆栈段,这就是B位,B=1时使用32位操作数,堆栈指针为ESP
B=0时使用16位操作数,堆栈指针为SP
AVL:保留位
P:=1段在内存中存在,=0不存在
DPL:特权级,0是最高,3是最低
type:<8,数据段
image.png
>=8,代码段
image.png

段寄存器数据结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4K7NuuV-1686823496586)(https://zuos-tec-1311460166.cos.ap-chengdu.myqcloud.com/test/20230613220539.png)]

突破1MB寻址能力

在此前的实模式中,一直使用的是20位地址线寻址,最大也超不过1MB的寻址能力,为了实现这个功能,需要先开启地址A20功能,不用去管A20的开启怎么就实现访问1MB以上空间,只需要知道,它就是1MB以上和以下之前的一个开关,打开后就可以进入以上的空间。
关于如何打开A20有很多办法,这里用的是将0x92端口的第一位置1,就可以开启A20

[SECTION .s16]
[BITS 16]		; [BITS 16/32]是告诉编译器将会在16位还是32位的cpu上运行,其编译的方式是按16位还是32位进行编译

Label_Start:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ax, 0x00
    mov ss, ax
    mov sp, 0x7c00

	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0201h		;row 2
	mov	cx,	12
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartLoaderMessage
	int	10h						; 显示开始loader字符串
; 开启A20信号线,将92h端口第1位置1
    push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al
	pop	ax

第一次进入和退出保护模式

CR0

cr开头的控制寄存器,32位,下面是每一位的意义
image.png
PG:Paging(分页),=1时表示启用分页机制;=0时表示禁用分页机制,这时线性地址就是物理地址。【什么是线性地址?】。如果PE标志位未设置,PG位无意义;在PE被清除时设置PG会导致通用保护异常
CD:Cache Disable,缓存禁用。当CD和NW都被清除时,处理器的内部或外部缓存可以对整个物理内存的存储位置进行缓存。当CD被设定时,缓存将受到限制【不懂为什么限制缓存,暂且就不设置】
NW:Not Write-through:好像是和CD搭配使用的,不懂
AM:Alignment Mask(对齐掩码)。设置AM位,启用自动对齐检查;清除AM位,禁用对齐检查。对齐检查的进行需要满足以下几个条件,1. AM标志被设置;2. EFLAGS寄存器中的AC为被设置;3. CPL(当前特权级)等于3;4. CPU处于保护模式或虚拟8086模式下。
WP:Write Protect写保护。当WP被设置时,阻止特权程序对只读页面进行写操作;当被清除时允许。WP和cr4.CET有关,设置CET之前必须设置WP,当CET是1时,不能清除WP。
NE:Numeric Error(数值错误)是CR0寄存器的第5位(bit 5)。当设置了NE标志时,启用内部的机制来报告x87 FPU错误;当清除NE标志时,启用PC风格的x87 FPU错误报告机制。当NE标志被清除并且IGNNE#输入被触发时,x87 FPU错误会被忽略。当NE标志被清除并且IGNNE#输入未触发时,未屏蔽的x87 FPU错误会导致处理器断言FERR#引脚以生成外部中断,并在执行下一个等待的浮点指令或WAIT/FWAIT指令之前立即停止指令执行。
FERR#引脚用于驱动外部中断控制器的输入(FERR#引脚模拟Intel 287和Intel 387 DX数学协处理器的ERROR#引脚)。NE标志、IGNNE#引脚和FERR#引脚与外部逻辑一起用于实现PC风格的错误报告。使用FERR#和IGNNE#来处理浮点异常在现代操作系统中已不推荐使用;这种非本机的方法还限制了新型处理器仅能在一个逻辑处理器处于活动状态。
ET:Extension Type扩展类型,对于Pentium 4、Intel Xeon、P6系列和Pentium处理器,该标志位被保留且固定为1。而对于Intel386和Intel486处理器,当设置了该标志位时,表示支持Intel 387 DX数学协处理器指令。
MP:monitor coprocessor监视协处理器,它控制WAIT(或FWAIT)指令与TS标志(CR0的第3位)的交互作用。如果设置了MP标志,并且TS标志也被设置,那么WAIT指令将引发设备不可用异常(#NM)。如果清除了MP标志,则WAIT指令将忽略TS标志的设置。
PE:Protection Enable保护使能,这也是在这里要用到的最重要的一个标志位,设置PE,启用保护模式;清除PE,启用实模式。如要启动分页机制,要同时启动PE和PG标志。
CR0有用的也差不多只有PE位,在这里列出来,是因为即使知道它没用,也想在看的时候能看到全貌

; 开启和关闭保护模式
	cli							; 关中断
	db	0x66					; 在16位编址的段中使用32位的命令时前面要加一个0x66
	lgdt	[GdtPtr]			; 使用该命令可以将GDT的基地址和长度加载到GDTR寄存器中

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax				; 将PE位置1

	mov	ax,	SelectorData32		
	mov	fs,	ax					; 使用fs存储保护模式下设置的临时数据段选择子
	mov	eax,	cr0
	and	al,	11111110b			; 将PE位置0关闭保护模式
	mov	cr0,	eax
	sti							; 开中断

搜索kernel.bin文件

没有什么特别的,和之前搜索loader.bin是一样的过程

; ------------- search kernel.bin ---------------
    mov word [SectorNo], SectorNumOfRootDirStart
; 准备对某一个扇区的第一个目录项进行对比的一些前期工作,包括读入该扇区的全部目录项,和赋一些初值
; 输入参数 [SectorNo]
Label_Search_In_Root_Dir_Begin:
    cmp word [RootDirSizeForLoop], 0
    jz Label_No_Kernel_File             ; 循环找到最后一个扇区还是没有,当RootDirSizeForLoop为0时表示没有扇区可以比较了
    dec word [RootDirSizeForLoop]     
    xor esi,esi  
    mov si, [SectorNo]
    mov ax, 0x00
    mov es, ax   ; 由于后面的lodsb要使用es:di指向目录项中文件名,所以这里也将其归一下0
    mov ax, 0x8000			
    mov di, ax
    call Func_Read_One_Sector		; 读入根目录扇区,缓存地址是0x8000
    mov si, KernelFileName
    mov ax, RootDirBuffer
    mov di, ax
    cld
    mov bx, DirEntryPerSector       ; bx存储每个扇区存储的目录项数目

; 判断当前扇区的目录项是否都已对比完
Label_Search_For_Kernel_File:
    cmp bx, 0                       ; 如果bx变为0,就是在当前扇区目录项中没有该文件
    jz Label_Goto_Next_Sector_In_Root_Dir
    dec bx

    mov cx, FileNameLen             ; cx存储文件名的长度
; 对比文件名其中的一个字符
Label_Cmp_FileName:
    cmp cx, 0                       ; 当cx为0,表示对比完了文件名的每一个字符,和目录项中文件名是相同的
    jz Label_FileName_Found         
    dec cx
    lodsb       ; 将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1,这里的esi指向的是kernelfilename的那个位置
    cmp al, byte [es:di]
    jz Label_Go_On                  ; 如果当前字符相同,就继续对比下一个字符
    jmp Label_Different             ; 如果字符不相同,就调到字符不同的下一个函数
; 为对比文件名下一个字符做准备
Label_Go_On:
	inc	di                          ; 由于lodsb中esi已经自动加一,所以对比下一个字符只需要对di加一就可以跳转
	jmp	Label_Cmp_FileName
; 文件名不同时的处理
Label_Different:
	and	di,	0xffe0  ; 将前5位归0,di变成了当前目录项的起始位置
	add	di,	20h     ; di加32,变成了下一个目录项的起始位置
	mov	si,	KernelFileName
	jmp	Label_Search_For_Kernel_File
; 对读取下一个扇区做准备
Label_Goto_Next_Sector_In_Root_Dir:
	add	word	[SectorNo],	1           ; 对比的第几个扇区加1
	jmp	Label_Search_In_Root_Dir_Begin  
        
; 没有kernel.bbin文件的显示
Label_No_Kernel_File:
    mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0301h		;row 3
	mov	cx,	21
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	NoKernelMessage
	int	10h
	jmp	$

发现kernel文件并转存

; 发现了kernel.bin文件的处理
Label_FileName_Found:
    mov ax, RootDirSectors
    and di, 0xffe0
    add di, DirFstClus          ; 找到对应目录项中存放首簇簇号的地址
    mov cx, word [es:di]        ; 将首簇簇号赋给cx 
    push cx      
         
    add cx, SectorAndClusterBalance ; cx等于簇号对应的扇区号
    mov eax, BaseOfTmpKernelAddr
    mov es, eax
    mov bx, OffsetOfTmpKernelFile
    mov ax, cx
; 加载kernel文件到临时地址
Label_Go_On_Loading_Kernel_File:
    push ax         
    push bx                     ; 现在栈中依次存放的是,kernel临时文件偏移量,文件扇区号,文件簇号
    mov ah, 0x0e
    mov al, '.'
    mov bl, 0x0f
    int 10h                     ; 显示....
    pop bx
    xor eax, eax
    pop ax
    mov esi, eax
    mov di, bx
    call Func_Read_One_Sector
    ; 保护环境
    pop ax          
    push cx         ; 
    push eax        ; 文件簇号
    push fs         ;
    push edi        ; 
    push ds
    push esi
	; mov eax, 0x11111111
	; mov fs, ax
	; mov es, ax
	; jmp $					; 加上这几句代码,进过测试 ,在关闭保护模式后fs的表示范围又只有16位了
    mov cx, 0x200
    mov ax, BaseOfKernelFile
    mov fs, ax      
    mov edi, dword [OffsetOfKernelFileCount] 
    mov ax, BaseOfTmpKernelAddr
    mov ds, ax
    mov esi, OffsetOfTmpKernelFile  
	mov es, eax

; 将kernel文件从临时地址移到最后的1MB开始地址
Label_Mov_Kernel:
    mov al, byte [ds:esi]       ; al=临时地址文件一字节
    mov byte [fs:edi], al       ; 0x100000地址开始处,

    inc edi
    inc esi

    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_Get_FAT_Entry			; 调用获取FAT表的函数
    cmp ax, 0xfff8
    jl Label_Kernel_File_Loaded   ; 如果大于0xfff8,表示文件加载完成
    push ax ; 压入簇号
    add ax, SectorAndClusterBalance
    jmp Label_Go_On_Loading_Kernel_File

Label_Kernel_File_Loaded:
		
	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0301h		;row 2
	mov	cx,	18
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	Kernel_File_Loaded
	int	10h
	; jmp $			; 在这里停顿后查看0x100000处信息,kernel的确是move过去了,但是[fs:edi]这时的fs是16位的,难道这样也能组合成20位以上的地址吗

查看fs的位数
移动kernel后查看

剩余代码就是从保护模式进入IA-32e 模式,但是这段代码还没弄懂,就不做注释了,loader.bin的全部代码放在最下面

org 0x1000

jmp Label_Start

%include "fat16.inc"

[SECTION gdt]      ; SECTION在这里的作用就是起一个修饰的作用,并不会在编译时产生什么作用

LABEL_GDT: dd 0, 0                  ; GDT第一个描述符定义为null
; BASE: 0
; limit: 0xfffff
; G: 1
; D: 1
; P: 1
; DPL: 0
; type: 1010 , 代码段,表示权限可执行,可读
LABEL_DESC_CODE32: dd 0x0000ffff, 0x00cf9a00    ; 代码段描述符
; type: 0010,数据段,表示权限可读可写
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200	; 数据段描述符

GdtLen	equ	$ - LABEL_GDT       ; GDT长度
GdtPtr	dw	GdtLen - 1          ; GDT段界限
	dd	LABEL_GDT               ; GDT基地址

SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT   ; 代码段选择子,一个16位数据,用来指向代码段描述符的起始地址
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT

[SECTION gdt64]					; IA-32e的GDP和保护模式是一样的位数,但是其内存是强制平坦型的,我没有查到强制平坦的含义,理解应该是不需要划分段了,所以取消了段基址和段限长两个参数的意义

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]		; [BITS 16/32]是告诉编译器将会在16位还是32位的cpu上运行,其编译的方式是按16位还是32位进行编译

Label_Start:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ax, 0x00
    mov ss, ax
    mov sp, 0x7c00

	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0201h		;row 2
	mov	cx,	12
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	StartLoaderMessage
	int	10h						; 显示开始loader字符串
; 开启A20信号线,将92h端口第1位置1
    push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al
	pop	ax						
; 开启和关闭保护模式
	cli							; 关中断
	db	0x66					; 在16位编址的段中使用32位的命令时前面要加一个0x66
	lgdt	[GdtPtr]			; 使用该命令可以将GDT的基地址和长度加载到GDTR寄存器中

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax				; 将PE位置1

	mov	ax,	SelectorData32		
	mov	fs,	ax					; 使用fs存储保护模式下设置的临时数据段选择子
	mov	eax,	cr0
	and	al,	11111110b			; 将PE位置0关闭保护模式
	mov	cr0,	eax
	sti							; 开中断

; ------------- search kernel.bin ---------------
    mov word [SectorNo], SectorNumOfRootDirStart
; 准备对某一个扇区的第一个目录项进行对比的一些前期工作,包括读入该扇区的全部目录项,和赋一些初值
; 输入参数 [SectorNo]
Label_Search_In_Root_Dir_Begin:
    cmp word [RootDirSizeForLoop], 0
    jz Label_No_Kernel_File             ; 循环找到最后一个扇区还是没有,当RootDirSizeForLoop为0时表示没有扇区可以比较了
    dec word [RootDirSizeForLoop]     
    xor esi,esi  
    mov si, [SectorNo]
    mov ax, 0x00
    mov es, ax   ; 由于后面的lodsb要使用es:di指向目录项中文件名,所以这里也将其归一下0
    mov ax, 0x8000			
    mov di, ax
    call Func_Read_One_Sector		; 读入根目录扇区,缓存地址是0x8000
    mov si, KernelFileName
    mov ax, RootDirBuffer
    mov di, ax
    cld
    mov bx, DirEntryPerSector       ; bx存储每个扇区存储的目录项数目

; 判断当前扇区的目录项是否都已对比完
Label_Search_For_Kernel_File:
    cmp bx, 0                       ; 如果bx变为0,就是在当前扇区目录项中没有该文件
    jz Label_Goto_Next_Sector_In_Root_Dir
    dec bx

    mov cx, FileNameLen             ; cx存储文件名的长度
; 对比文件名其中的一个字符
Label_Cmp_FileName:
    cmp cx, 0                       ; 当cx为0,表示对比完了文件名的每一个字符,和目录项中文件名是相同的
    jz Label_FileName_Found         
    dec cx
    lodsb       ; 将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1,这里的esi指向的是kernelfilename的那个位置
    cmp al, byte [es:di]
    jz Label_Go_On                  ; 如果当前字符相同,就继续对比下一个字符
    jmp Label_Different             ; 如果字符不相同,就调到字符不同的下一个函数
; 为对比文件名下一个字符做准备
Label_Go_On:
	inc	di                          ; 由于lodsb中esi已经自动加一,所以对比下一个字符只需要对di加一就可以跳转
	jmp	Label_Cmp_FileName
; 文件名不同时的处理
Label_Different:
	and	di,	0xffe0  ; 将前5位归0,di变成了当前目录项的起始位置
	add	di,	20h     ; di加32,变成了下一个目录项的起始位置
	mov	si,	KernelFileName
	jmp	Label_Search_For_Kernel_File
; 对读取下一个扇区做准备
Label_Goto_Next_Sector_In_Root_Dir:
	add	word	[SectorNo],	1           ; 对比的第几个扇区加1
	jmp	Label_Search_In_Root_Dir_Begin  
        
; 没有kernel.bbin文件的显示
Label_No_Kernel_File:
    mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0301h		;row 3
	mov	cx,	21
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	NoKernelMessage
	int	10h
	jmp	$
; 发现了kernel.bin文件的处理
Label_FileName_Found:
    mov ax, RootDirSectors
    and di, 0xffe0
    add di, DirFstClus          ; 找到对应目录项中存放首簇簇号的地址
    mov cx, word [es:di]        ; 将首簇簇号赋给cx 
    push cx      
         
    add cx, SectorAndClusterBalance ; cx等于簇号对应的扇区号
    mov eax, BaseOfTmpKernelAddr
    mov es, eax
    mov bx, OffsetOfTmpKernelFile
    mov ax, cx
; 加载kernel文件到临时地址
Label_Go_On_Loading_Kernel_File:
    push ax         
    push bx                     ; 现在栈中依次存放的是,kernel临时文件偏移量,文件扇区号,文件簇号
    mov ah, 0x0e
    mov al, '.'
    mov bl, 0x0f
    int 10h                     ; 显示....
    pop bx
    xor eax, eax
    pop ax
    mov esi, eax
    mov di, bx
    call Func_Read_One_Sector
    ; 保护环境
    pop ax          
    push cx         ; 
    push eax        ; 文件簇号
    push fs         ;
    push edi        ; 
    push ds
    push esi
	; mov eax, 0x11111111
	; mov fs, ax
	; mov es, ax
	; jmp $					; 加上这几句代码,进过测试 ,在关闭保护模式后fs的表示范围又只有16位了
    mov cx, 0x200
    mov ax, BaseOfKernelFile
    mov fs, ax      
    mov edi, dword [OffsetOfKernelFileCount] 
    mov ax, BaseOfTmpKernelAddr
    mov ds, ax
    mov esi, OffsetOfTmpKernelFile  
	mov es, eax

; 将kernel文件从临时地址移到最后的1MB开始地址
Label_Mov_Kernel:
    mov al, byte [ds:esi]       ; al=临时地址文件一字节
    mov byte [fs:edi], al       ; 0x100000地址开始处,

    inc edi
    inc esi

    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_Get_FAT_Entry			; 调用获取FAT表的函数
    cmp ax, 0xfff8
    jl Label_Kernel_File_Loaded   ; 如果大于0xfff8,表示文件加载完成
    push ax ; 压入簇号
    add ax, SectorAndClusterBalance
    jmp Label_Go_On_Loading_Kernel_File

Label_Kernel_File_Loaded:
		
	mov	ax,	1301h
	mov	bx,	000fh
	mov	dx,	0301h		;row 2
	mov	cx,	18
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	Kernel_File_Loaded
	int	10h
	; jmp $			; 在这里停顿后查看0x100000处信息,kernel的确是move过去了,但是[fs:edi]这时的fs是16位的,难道这样也能组合成20位以上的地址吗
; ------------------- 获取内存地址信息 ---------------------
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0401h		;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, 0
    mov es, ax
    mov di, MemoryStructBufferAddr

Label_Get_Mem_Struct:
    mov eax, 0xe820
    mov ecx, 20
    mov edx, 0x534d4150
    int 15h
    jc Label_Get_Mem_Struct_Fail
    add di, 20

    cmp ebx, 0
    jne Label_Get_Mem_Struct
    jmp Label_Get_Mem_Struct_Ok

Label_Get_Mem_Struct_Fail:
	mov	ax,	1301h
	mov	bx,	008Ch
	mov	dx,	0501h		;row 5
	mov	cx,	23
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructErrMessage
	int	10h
	jmp	$

Label_Get_Mem_Struct_Ok:
	mov	ax,	1301h
	mov	bx,	000Fh
	mov	dx,	0501h		;row 6
	mov	cx,	29
	push	ax
	mov	ax,	ds
	mov	es,	ax
	pop	ax
	mov	bp,	GetMemStructOKMessage
	int	10h	

; ; 获取SVGA信息
; 	mov	ax,	1301h
; 	mov	bx,	000Fh
; 	mov	dx,	0601h		;row 8
; 	mov	cx,	23
; 	push	ax
; 	mov	ax,	ds
; 	mov	es,	ax
; 	pop	ax
; 	mov	bp,	StartGetSVGAVBEInfoMessage
; 	int	10h
; 开启保护模式
    cli
    db 0x66
    lgdt [GdtPtr]			; 初始化GDT
	db	0x66
	lidt	[IDT_POINTER]	; 初始化IDT
    mov eax, cr0
    or eax, 1   ; 将最后一位置1
    mov cr0, eax

    jmp dword SelectorCode32:GO_TO_TMP_Protect

[SECTION .s32]
[BITS 32]
GO_TO_TMP_Protect:
    mov ax, 0x10 
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov esp, 0x7e00

    call support_long_mode
    test eax, eax

    jz no_support

; 在地址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

; 加载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
; 打开PAE(物理地址扩展功能)
	mov	eax,	cr4
	bts	eax,	5
	mov	cr4,	eax
; 修改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	$

[SECTION .s16lib]
[BITS 16]
Func_Get_FAT_Entry:
    push es
    push esi
    push di
    push dx
    push ax ; 文件簇号
    shr ax, 7       ; 每个FAT扇区存放256个FAT表项,除以256就是对应的扇区号
    add ax, SectorNumOfFAT1Start
    mov esi, eax
    mov di, FATBuffer
    mov dx, BaseOfTmpKernelAddr
    mov es, dx
    call Func_Read_One_Sector
    pop ax
    and ax, 0x007F
    shl ax, 2
    mov di, ax
    mov ax, word [es:di]    ; 获取到了下一个簇号
    pop dx
    pop di
    pop esi
    pop es
    ret

; 读取一个扇区函数(主控制器主盘)
; 输入参数:esi,LBA ; ds:di,数据存放地址
; 输出参数:无
Func_Read_One_Sector:  
; 读取状态,看是否第7位是0,第6位是否是1,也就是表示硬盘空闲且就绪的状态 
	mov dx,0x1f7        
	.not_ready1:
	nop             ; 一个短暂的停顿
	in al,dx        ; 将端口数据读入al中
	and al,0xc0     ; 将al和1100 0000做与运算,其结果是将al的第6,7位保留下来,其他都是0
	cmp al,0x40     ; 将al和0100 0000做比较,如果相等,证明硬盘空闲且就绪,如果不是就循环再检测
	jne .not_ready1
	; 设置要读取扇区的数量
	mov dx,0x1f2
	mov al,1
	out dx,al
	; 将LBA的前14位设置到0x1f3~0x1f5端口上
	mov eax,esi
	mov dx,0x1f3
	out dx,al

	shr eax,8       ; 右移8位,这样al的值就是第9-16位的LBA值
	mov dx,0x1f4
	out dx,al

	shr eax,8
	mov dx,0x1f5
	out dx,al
	; 设置0x1f6端口,前四位是LBA的最后四位,后四位,依据通过LBA从主设备读取数据的要求,应该设置为1110
	shr eax,8
	mov dx,0x1f6
	and al,0x0f     ; 将al的前四位进行保留,也就是LBA的后四位
	or al,0xe0      ; 和1110进行或的结果,就是将al的后四位设置为1110
	out dx,al
	; 写入读硬盘命令
	mov dx,0x1f7
	mov al,0x20     ; 0x20是读命令
	out dx,al
	; 再次检查硬盘状态是否为空闲,且可以从硬盘中读数据的状态,也就是0x1f7应该为10001000
	.not_ready2:
	nop
	in al,dx
	and al,0x88     ; 将al的第7和第3位保留
	cmp al,0x08     ; 判断第7位是否为0,第三位是否为1
	jne .not_ready2

	mov cx,256      ; 由于每次只能接受两个字节,所以一个扇区需要读取256次
	mov dx,0x1f0
	.read_data:
	in ax,dx
	mov [es:di],ax     ; 将读出的数据存入es:di开始的地址
	add di,2
	loop .read_data
	ret


;=======	tmp IDT

IDT:
	times	0x50	dq	0
IDT_END:

IDT_POINTER:
		dw	IDT_END - IDT - 1
		dd	IDT

; ------------- 变量 -----------------------
SectorNo    dw   0              ; [SectorNo]表示SectorNo地址下的数,即初值为0
RootDirSizeForLoop  dw RootDirSectors
KernelFileName:		db	"KERNEL  BIN",0            
OffsetOfKernelFileCount	dd	OffsetOfKernelFile   ;4B,存放kernel偏移量


Kernel_File_Loaded: db "Kernel File Loaded"
StartLoaderMessage:	db	"Start Loader"
NoKernelMessage:	db	"ERROR:No KERNEL Found"
KernelFoundMessage: db "Kernel found"
StartGetMemStructMessage:	db	"Start Get Memory Struct."
GetMemStructErrMessage:	db	"Get Memory Struct ERROR"
GetMemStructOKMessage:	db	"Get Memory Struct SUCCESSFUL!"
; SVGA info 没获取
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!"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值