BootLoader引导启动程序


BootLoader引导启动程序原本由Boot引导程序和Loader引导加载程序两部分构成。Boot引导程序主要负责开机启动和加载Loader程序;Loader引导加载程序则用于完成配置和硬件工作环境、引导加载内核等任务。

Boot引导程序

计算机上电后,首先经过BIOS上电自检,这个过程会检测硬件设备是否有问题。如果检测没有问题的话,将根据BIOS的启动项配置选择引导设备,目前支持从硬盘,软盘,U盘,网络启动,通常情况下会选择硬盘作为默认启动项。

BIOS引导原理

选择了启动设备后,会检测相应启动设备的第0磁头第0磁道第一个扇区是否以数值0x55和0xaa两个字节作为结尾。如果是,BIOS就认为这个扇区是一个引导扇区,会把此扇区的数据复制到物理内存0x7c00处,然后将处理器的执行权移交给这段程序。
在这里插入图片描述
由于磁盘的一个扇区只有512B所以是无法容纳一个操作系统的,所以Boot引导程序将功能更强大的引导加载程序Loader装载内存中,一旦Loader引导加载程序开始执行,一切都会交给我们编写的软件来控制,可以看作是硬件设备向软件移交控制权。

写一个Boot引导程序

寄存器初始化部分。

org	0x7c00;org指令将程序的起始地址设置为0x7c00,因为BIOS会加载引导程序至此处
BaseOfStack	equ	0x7c00
Label_Start:

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ss,	ax
	mov	sp,	BaseOfStack;初始化代码段,数据段,扩展段,堆栈段以及栈指针

通过BIOS中断服务程序INT 10h实现屏幕信息显示相关操作。

;=======	clear screen

	mov	ax,	0600h;int 10h,ah=06h,al=0实现清屏功能
	mov	bx,	0700h;al=0,bx的值不生效
	mov	cx,	0;al=0,cx的值不生效
	mov	dx,	0184fh;al=0,dx的值不生效
	int	10h

;=======	set focus

	mov	ax,	0200h;int 10h,ah=02h,设置屏幕光标位置
	mov	bx,	0000h;页码
	mov	dx,	0000h;将光标设置到屏幕左上角(0,0)处
	int	10h

;=======	display on screen : Start Booting......

	mov	ax,	1301h;int 10h,ah=13h,al=01h,显示字符串,光标移动至字符串尾端位置
	mov	bx,	000fh;设置字符显示属性,白字,黑底,高亮,不闪烁
	mov	dx,	0000h;从(0,0)处开始显示
	mov	cx,	10;要显示的字符串的长度
	push	ax;保存ax寄存器
	mov	ax,	ds
	mov	es,	ax;设置要显示的字符串的段基址
	pop	ax
	mov	bp,	StartBootMessage;设置要显示的字符串的偏移地址
	int	10h

定义一个字符串和引导程序的结束标识数据。

StartBootMessage:	db	"Start Boot"
;=======	fill zero until whole sector

	times	510 - ($ - $$)	db	0
	dw	0xaa55;引导扇区标识

创建虚拟机软盘镜像

安装好Bochs虚拟机源码后,会生成磁盘镜像创建工具bximage。可以使用bximage命令创建虚拟软盘镜像文件。使用bximage根据提示创建一个名为boot.img,大小为1.44MB的软盘。

在Bochs上运行Boot程序

使用nasm命令将boot.asm源文件编译成二进制文件。

nasm boot.asm -o boot.bin

将编译好的二进制文件写入虚拟软盘镜像文件内。使用dd命令将引导程序强制写入虚拟软盘的固定扇区,这种方式可以绕过文件系统的管理和控制,直接操作磁盘扇区。

# 书上原本没有第1条命令,如果没有第一条命令后面会因为磁盘空间不足而复制loder程序失败
dd if=/dev/zero of=./bochs-2.6.8/boot.img count=204
dd if=boot.bin of=./bochs-2.6.8/boot.img count=1 bs=512 conv=notrunc

使用bochs -f ./bochsrc启动Bochs虚拟机,执行选项6。
在这里插入图片描述

加载Loader到内存

加载Loader程序最直接的想法是从文件系统中把Loader程序加载到内存。
本操作系统使用最简单的FAT12文件系统来装载Loader程序和内核程序。在将软盘格式化成FAT12文件系统的过程中,FAT类文件系统会对软盘里的扇区进行结构化处理,把软盘分成引导扇区,FAT表,根目录区和数据区4个部分。

  • 引导扇区
    在这里插入图片描述
    在这里插入图片描述
  • FAT表
    数据区的簇号与FAT的表项是一一对应的。
    在这里插入图片描述
  • 根目录和数据区
    根目录区和数据区都保存着与文件相关的数据,根目录区只能保存目录项信息,数据区不但可以保存目录项信息,还可以保存文件内的数据。
    在这里插入图片描述
    为虚拟机软盘创建FAT12文件系统引导扇区数据。
	org	0x7c00;org指令将程序的起始地址设置为0x7c00,因为BIOS会加载引导程序至此处

BaseOfStack	equ	0x7c00

BaseOfLoader	equ	0x1000;Loader程序的起始基地址
OffsetOfLoader	equ	0x00;Loader程序的起始偏移地址,两者组成了Loader程序的起始物理地址

RootDirSectors	equ	14;根目录占用扇区数
SectorNumOfRootDirStart	equ	19;根目录的起始扇扇区号
SectorNumOfFAT1Start	equ	1;FAT1表的起始扇区号
SectorBalance	equ	17;用于平衡文件的起始簇号和数据区起始簇号的差值
    ;初始化文件系统
	jmp	short Label_Start
	nop
	BS_OEMName	db	'MINEboot';生产厂商名
	BPB_BytesPerSec	dw	512;每扇区字节数
	BPB_SecPerClus	db	1;每簇扇区数
	BPB_RsvdSecCnt	dw	1;保留扇区数
	BPB_NumFATs	db	2;FAT表的份数
	BPB_RootEntCnt	dw	224;根目录可容纳的目录项数
	BPB_TotSec16	dw	2880;总扇区数
	BPB_Media	db	0xf0;介质描述符
	BPB_FATSz16	dw	9;每FAT扇区数
	BPB_SecPerTrk	dw	18;每磁道扇区数
	BPB_NumHeads	dw	2;磁头数
	BPB_HiddSec	dd	0;隐藏扇区数
	BPB_TotSec32	dd	0;记录扇区数
	BS_DrvNum	db	0;int 13h的驱动器号
	BS_Reserved1	db	0;未使用
	BS_BootSig	db	0x29;扩展引导标记
	BS_VolID	dd	0;卷序列号
	BS_VolLab	db	'boot loader';卷标
	BS_FileSysType	db	'FAT12   ';文件系统类型

实现软盘读取功能。

;=======	read one sector from floppy

Func_ReadOneSector:
	
	push	bp;保存栈帧寄存器
	mov	bp,	sp;保存栈寄存器
	sub	esp,	2;在栈上开辟两个字节存储单元
	mov	byte	[bp - 2],	cl;将cl存储在刚开辟的地址中
	push	bx;保存bx寄存器
	mov	bl,	[BPB_SecPerTrk];将每磁道扇区数送至bl
	div	bl;使用ax中存储的待读取的磁盘起始扇区号除以bl寄存器
	inc	ah;ah存储目标磁道内的起始扇区数,因为从1开始计数,所以加1
	mov	cl,	ah;将ah送入cl
	mov	dh,	al;将al寄存器保留一份
	shr	al,	1;目标磁道号右移一位计算磁道号
	mov	ch,	al;ch存储磁道号
	and	dh,	1;目标磁道号和1与计算出磁头号
	pop	bx;恢复bx寄存器
	mov	dl,	[BS_DrvNum];将驱动器号送入dl
Label_Go_On_Reading:
	mov	ah,	2;int 13h ah=02h实现软盘读取操作
	mov	al,	byte	[bp - 2];将读入的扇区数送入al
	int	13h
	jc	Label_Go_On_Reading
	add	esp,	2;释放开始申请的2个字节的栈存储空间
	pop	bp;恢复栈帧寄存器
	ret

在软盘扇区读取功能的基础上,实现目标文件搜索功能。

;=======	search loader.bin
	mov	word	[SectorNo],	SectorNumOfRootDirStart;从第一个根目录所在扇区开始搜索

Lable_Search_In_Root_Dir_Begin:

	cmp	word	[RootDirSizeForLoop],	0;判断是否所有的根目录都搜索完了
	jz	Label_No_LoaderBin;是的话,跳转到没找到loader.bin的处理分支
	dec	word	[RootDirSizeForLoop];递减循环次数
	mov	ax,	00h
	mov	es,	ax
	mov	bx,	8000h;构建目标缓冲区起始地址es:bx
	mov	ax,	[SectorNo];第一个根目录所在扇区作为待读取的磁盘起始扇区号
	mov	cl,	1;读入一个扇区
	call	Func_ReadOneSector
	mov	si,	LoaderFileName
	mov	di,	8000h
	cld;清除DF标志位
	mov	dx,	10h;每个扇区可以容纳的目录项个数送入dx
	
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;是的话则找到了loader.bin文件
	dec	cx;比较下一个字符
	lodsb;将ds:si指定的数据读入al
	cmp	al,	byte	[es:di];比较al中的字节是否和es:di处的字节相同
	jz	Label_Go_On;相同则跳转到下一个字节比较处理分支
	jmp	Label_Different;不同则跳转到不同的处理分支

Label_Go_On:
	
	inc	di;增加di
	jmp	Label_Cmp_FileName;比较下一个字节

Label_Different:

	and	di,	0ffe0h
	add	di,	20h;下一个目录项
	mov	si,	LoaderFileName
	jmp	Label_Search_For_LoaderBin;比较下一个目录项

Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1;将扇区数加1
	jmp	Lable_Search_In_Root_Dir_Begin;在下一个扇区中寻找

搜索不到loader.bin程序时,显示提示信息。

;=======	display on screen : ERROR:No LOADER Found

Label_No_LoaderBin:

	mov	ax,	1301h;int 10h,ah=13h,al=01h,显示字符串,光标移动至字符串尾端位置
	mov	bx,	008ch;黑色背景,红色字体,高亮,字体闪烁
	mov	dx,	0100h;1行0列显示
	mov	cx,	21;字符串长度
	push	ax;保存ax寄存器
	mov	ax,	ds
	mov	es,	ax;将ds送入es
	pop	ax;恢复ax寄存器
	mov	bp,	NoLoaderMessage;es:bp要显示的字符串地址
	int	10h
	jmp	$

搜索到loader.bin后,根据FAT表项提供的簇号顺序依次加载扇区数据到内存中,解析FAT表项。

;=======	get FAT Entry 根据当前FAT表项索引出下一个FAT表项

Func_GetFATEntry:

	push	es;保存es寄存器
	push	bx;保存bx寄存器
	push	ax;保存ax寄存器
	mov	ax,	00
	mov	es,	ax;将00送入es寄存器
	pop	ax;恢复ax寄存器
	mov	byte	[Odd],	0;将奇偶标志置0
	mov	bx,	3
	mul	bx;被乘数在ax寄存器,乘数在bx寄存器,乘积在dx:ax寄存器
	mov	bx,	2
	div	bx;每个FAT表项占用12bit,每三个字节存储两个FAT表项,被除数在dx:ax寄存器,除数在bx寄存器,商在ax寄存器,余数在dx寄存器
	cmp	dx,	0
	jz	Label_Even;余数为偶数跳转到偶数处理分支
	mov	byte	[Odd],	1;余数为奇数

Label_Even:

	xor	dx,	dx;将dx寄存器淸0
	mov	bx,	[BPB_BytesPerSec]
	div	bx;再除以每扇区字节数,商在ax寄存器为FAT表项的偏移扇区号,余数在dx寄存器为FAT表项在扇区中的偏移位置
	push	dx;保存dx寄存器
	mov	bx,	8000h;目标缓冲区偏移
	add	ax,	SectorNumOfFAT1Start;从FAT的第一个扇区号开始
	mov	cl,	2;读出2个扇区
	call	Func_ReadOneSector
	
	pop	dx;恢复dx寄存器
	add	bx,	dx;将bx加上偏移位置
	mov	ax,	[es:bx];将下一个FAT表项送入ax寄存器
	cmp	byte	[Odd],	1;判断奇偶标志
	jnz	Label_Even_2;偶数,跳转套偶数处理分支
	shr	ax,	4;奇数,右移4位

Label_Even_2:
	and	ax,	0fffh;高位淸0
	pop	bx;恢复bx寄存器
	pop	es;恢复es寄存器
	ret

加载loader.bin文件到内存。

;=======	found loader.bin name in root director struct

Label_FileName_Found:

	mov	ax,	RootDirSectors
	and	di,	0ffe0h;一个目录项是32B,低5位淸0
	add	di,	01ah;起始簇号偏移
	mov	cx,	word	[es:di];获取起始簇号
	push	cx;保存cx寄存器
	add	cx,	ax
	add	cx,	SectorBalance;计算数据区扇区号
	mov	ax,	BaseOfLoader
	mov	es,	ax
	mov	bx,	OffsetOfLoader;配置loader.bin在内存中的起始地址
	mov	ax,	cx;将数据区起始扇区号送入ax

Label_Go_On_Loading_File:
	push	ax;保存ax寄存器
	push	bx;保存bx寄存器
	mov	ah,	0eh
	mov	al,	'.';在屏幕上显示字符.
	mov	bl,	0fh;黑色背景,白色字体,高亮,不闪烁
	int	10h
	pop	bx;恢复bx寄存器
	pop	ax;恢复ax寄存器

	mov	cl,	1
	call	Func_ReadOneSector;读取一个扇区
	pop	ax;恢复ax寄存器
	call	Func_GetFATEntry;获取下一个FAT表项
	cmp	ax,	0fffh;判断是否是最后一个簇
	jz	Label_File_Loaded;如果是则跳转到loader程序起始地址
	push	ax;保存ax寄存器
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance;计算下一个FAT表项对应的数据扇区号
	add	bx,	[BPB_BytesPerSec];指向下一个512字节
	jmp	Label_Go_On_Loading_File;加载下一个FAT表项对应的数据区的数据

Label_File_Loaded:
	
	jmp	BaseOfLoader:OffsetOfLoader

程序运行时临时数据和屏幕显示的日志信息。

;=======	tmp variable

RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0

;=======	display messages

StartBootMessage:	db	"Start Boot"
NoLoaderMessage:	db	"ERROR:No LOADER Found"
LoaderFileName:		db	"LOADER  BIN",0

;=======	fill zero until whole sector

	times	510 - ($ - $$)	db	0
	dw	0xaa55;引导扇区标识

从Boot跳转到Loader程序

实现Boot引导程序的最后一步就是跳转至Loader引导程序加载程序处,向其移交处理器的控制权。

Label_File_Loaded:
	
	jmp	BaseOfLoader:OffsetOfLoader

编写一个简单的Loader引导加载程序来检验这个跳转过程。

org	10000h

	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

	jmp	$

;=======	display messages

StartLoaderMessage:	db	"Start Loader"

编译loader.asm,并将生成的二进制程序loader.bin复制到虚拟软盘镜像文件boot.img中。

nasm loder.asm -o loader.bin
mount ./bochs-2.6.8/boot.img /media/ -t vfat -o loop
cp loader.bin /media/
sync
umount /media/

运行效果如下。
在这里插入图片描述

Loader引导加载程序

Loader原理

Loader引导加载程序负责检测硬件信息,处理器模式切换,向内核传递数据三部分工作。

  • 检测硬件信息
    Loader引导加载程序主要是通过BIOS中断服务程序来获取和检测硬件信息。由于BIOS在上电自检处的大部分信息只能在实模式下获取,而且内核运行于非实模式下,那么就必须在进入内核程序前将这些信息检测出来,再作为参数提供给内核程序使用。
  • 处理器模式切换
    从起初BIOS运行的实模式,到32位操作系统使用的保护模式,再到64位操作系统使用的IA-32e模式,Loader引导加载程序必须经过这三种模式,才能使处理器运行于64位的IA-32e模式。
  • 向内核传递数据
    Loader引导加载程序可向内核程序传递两类数据,一类是控制信息,另一类是硬件数据信息。控制信息一般用于控制内核执行流程或限制内核的某些功能。硬件数据信息通常指Loader引导加载程序检测出的硬件数据信息。

写一个Loader程序

定义内核程序起始地址,临时内存空间起始地址。

org	10000h
	jmp	Label_Start

%include	"fat12.inc"

BaseOfKernelFile	equ	0x00
OffsetOfKernelFile	equ	0x100000;内核程序起始地址位于1MB处,因为1MB以下的地址不全是可用内存地址空间

BaseTmpOfKernelAddr	equ	0x00
OffsetTmpOfKernelFile	equ	0x7E00;内核程序临时转存空间,因为内核程序的读取是通过BIOS中断服务程序int 13h实现的,BIOS在
;实模式下只支持上限为1MB的物理地址空间寻址

MemoryStructBufferAddr	equ	0x7E00

内存地址0x7E00是内核程序的临时转存空间,由于内核程序的读取操作是通过BIOS中断服务程序INT 13h实现的,BIOS在实模式下只支持上限位1MB的物理地址空间寻址,所以必须先将内核程序读入到临时转存空间,然后再通过特殊方式搬运到1MB以上的内存空间中。
开始执行Loader引导加载程序。

[SECTION .s16]
[BITS 16]

Label_Start:

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ax,	0x00
	mov	ss,	ax
	mov	sp,	0x7c00;初始化ds,es,ss,sp寄存器

;=======	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;在屏幕上显示es:bp指定的字符串

开启实模式下的4GB寻址功能。

;=======	open address A20 让fs可以在实模式下的寻址能力超过1MB
	push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al;通过置位0x92端口的第一位来开启A20功能
	pop	ax

	cli;关闭外部中断

	db	0x66
	lgdt	[GdtPtr];加载保护模式结构数据信息

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax;置cr0第0位开启保护模式

	mov	ax,	SelectorData32
	mov	fs,	ax;给fs寄存器加载新的数据段值
	mov	eax,	cr0
	and	al,	11111110b
	mov	cr0,	eax;关闭保护模式

	sti;开启外部中断

从FAT12文件系统中搜索出内核程序文件kernel.bin。

;=======	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;清除DF标志位
	mov	dx,	10h;每个扇区可以容纳的目录项个数送入dx
	
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;低5位清0
	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	$

如果搜索到内核程序文件kernel.bin,则将kernel.bin文件内的数据读取至物理内存中。

;=======	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 将kernel.bin加载到es:bx缓冲区
	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];fs:edi指向目标kernel地址

	mov	ax,	BaseTmpOfKernelAddr
	mov	ds,	ax
	mov	esi,	OffsetTmpOfKernelFile;ds:esi指向源kernel地址

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;获取下一个FAT表项
	cmp	ax,	0FFFh
	jz	Label_File_Loaded
	push	ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance;计算下一个FAT表项对应的数据区扇区号

	jmp	Label_Go_On_Loading_File
```![在这里插入图片描述](https://img-blog.csdnimg.cn/fffbd201e89a408385cc13c8b7a598b1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAamF2YXpjdw==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)

将内核程序加载到1MB以上物理内存地址后,显示一个'G'字。
```asm
Label_File_Loaded:
		
	mov	ax, 0B800h
	mov	gs, ax;设置gs段基址
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	mov	al, 'G'
	mov	[gs:((80 * 0 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。

运行效果如下。
在这里插入图片描述
当Loader引导加载程序完成内核程序的加载工作后,软盘驱动器将不再使用,可以通过如下的方式关闭软驱马达。

illMotor:
	
	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;显示Start Get Memory Struct.字符串

	mov	ebx,	0
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	MemoryStructBufferAddr
	;es:di指向一个地址范围描述结构 ARDS(Address Range Descriptor Structure), BIOS将会填充此结构

Label_Get_Mem_Struct:

	mov	eax,	0x0E820;指定获取内存信息
	mov	ecx,	20;es:di所指向的地址范围描述结构的大小,以字节为单位
	;无论es:di所指向的结构如何设置,BIOS最多将会填充ecx字节。
	;不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节
	mov	edx,	0x534D4150
	;0534D4150h('SMAP')——BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息被BIOS放置到es:di所指向的结构中
	int	15h;调用中断服务程序int 15h来获取物理地址空间信息
	jc	Label_Get_Mem_Fail
	add	di,	20

	cmp	ebx,	0;放置着“后续值(continuation value)”,第一次调用时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;显示Get Memory Struct ERROR字符串
	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 Memory Struct SUCCESSFUL!字符串

物理地址空间信息由一个结构体数组构成,它记录的地址空间类型包括可用物理内存地址空间,设备寄存器地址空间,内存空洞等。上面的程序借助借助BIOS中断服务程序INT15来获取物理地址空间信息,并将其保存在0x7E00地址处的临时转存空间里,操作系统会在初始化内存管理单元时解析该结构体数组。
显示16进制数值。Label_DispAL模块的主要作用是显示视频图像芯片的查询信息。

;=======	display num in al

Label_DispAL:

	push	ecx;
	push	edx
	push	edi
	
	mov	edi,	[DisplayPosition];将屏幕偏移值送入edi寄存器
	mov	ah,	0Fh;将字体的颜色属性值送入ah
	mov	dl,	al;将al送入dl,暂时把al寄存器的低四位保存到dl寄存器
	shr	al,	4;将al右移4位
	mov	ecx,	2;al分两次显示
.begin:

	and	al,	0Fh;将高4位清0
	cmp	al,	9;和9比较
	ja	.1;比9大,跳转到标号1处
	add	al,	'0';直接与'0'相加
	jmp	.2;比9小跳转到标号2处
.1:

	sub	al,	0Ah;减去0Ah
	add	al,	'A';加上字符'A'
.2:

	mov	[gs:edi],	ax;保存至gs寄存器位段基址,DisplayPosition为偏移的显示字符内存空间中
	add	edi,	2;偏移位置增加2
	
	mov	al,	dl;再显示低4位
	loop	.begin

	mov	[DisplayPosition],	edi

	pop	edi
	pop	edx
	pop	ecx
	
	ret

设置SVGA芯片显示模式。

;=======	set the SVGA mode(VESA VBE)

	mov	ax,	4F02h;对于超级VGA显示卡,我们可用AX=4F02H和下列BX 的值来设置其显示模式
	mov	bx,	4180h	;========================mode : 0x180 or 0x143
	int 	10h;设置SVGA显示模式

	cmp	ax,	004Fh
	jnz	Label_SET_SVGA_Mode_VESA_VBE_FAIL

在这里插入图片描述

从实模式进入保护模式再到IA-32e模式

在实模式下,程序可以操作任何地址空间,而且无法限制程序的执行权限。在保护模式下,处理器按程序的执行级别分为0,1,2,3,4四个等级。最高等级0由系统内核使用,最低等级3由应用程序使用,等级1和等级2通常由系统服务来使用。

  1. 从实模式进入保护模式
    系统数据结构,系统在进入保护模式之前,必须创建一个拥有代码段描述符和数据段描述符的GDT,并且一定要使用LGDT汇编指令将其加载到GDTR寄存器。
    中断和异常,在保护模式下,中断/异常处理程序皆由IDT来管理。
    分页机制,CR0控制寄存器的PG标志位用于控制分页机制的开启和关闭。
    多任务机制,如果希望使用多任务机制或允许改变特权级,则必须在首次执行任务切换前,创建至少一个任务状态段TSS结构和附加的TSS段描述符。
    创建GDT表。
[SECTION gdt];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;是此结构的起始地址,GDT表的长度
	dd	LABEL_GDT;GDT表的基地址

SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT;32位段选择子,是段描述符在GDT表中的索引
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT;32位段选择子,是段描述符在GDT表中的索引

为IDT开辟内存空间。

;=======	tmp IDT;为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;用于修饰当前操作数是32位宽
	lgdt	[GdtPtr];将GDT的基地址和长度加载到GDTR寄存器

;	db	0x66
;	lidt	[IDT_POINTER]

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax;置位CR0控制寄存器的PE标志位

	jmp	dword SelectorCode32:GO_TO_TMP_Protect;切换到保护模式的代码段去执行
  1. 从保护模式进入IA-32e模式
    系统数据结构,既然已经开启IA-32e模式,那么系统各描述表寄存器理应重新加载(借助LGDT,LLDT,LIDT和LTR指令)为IA-32e模式的64位描述符表。
    中断和异常。
    为切换到IA-32e模式而准备临时GDT表结构数据。
[SECTION gdt64];64位段结构

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;64位段选择子
SelectorData64	equ	LABEL_DESC_DATA64 - LABEL_GDT64;64位段选择子

当准备好段结构的初始化信息后,方可从GO_TO_TMP_Protect地址处开始执行IA-32e模式的切换程序。

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;检测是否支持IA-32e模式
	test	eax,	eax

	jz	no_support

检测处理器是否支持IA-32e模式。

;=======	test support long mode or not

support_long_mode:

	mov	eax,	0x80000000
	cpuid;获取cpu支持的最大功能号
	cmp	eax,	0x80000001
	setnb	al;最大功能号不低于	0x80000001时置位al
	jb	support_long_mode_done;小于0x80000001跳转
	mov	eax,	0x80000001
	cpuid;调用cpuid的0x80000001扩展功能项,看是否支持IA-32e模式
	bt	edx,	29;将edx的第29位复制到进位标志位,CPUID的扩展功能项的0x80000001的第29位,指示
	;处理器是否支持IA-32e模式
	setc	al;当进位标志位为1时置al为1,当进位标志位为0时置al为0
support_long_mode_done:
	
	movzx	eax,	al;将al的值送入eax寄存器,并且高位清零
	ret

;=======	no support

no_support:;不支持就进入待机状态,不做任何操作
	jmp	$

为IA-32e模式配置临时页目录和页表项。

;=======	init temporary page table 0x90000

	mov	dword	[0x90000],	0x91007;将IA-32e模式的页目录首地址设置在0x90000地址处,并配置各级页表项的值
	;该值由页表起始地址和页属性组成
	mov	dword	[0x90004],	0x00000
	mov	dword	[0x90800],	0x91007
	mov	dword	[0x90804],	0x00000

	mov	dword	[0x91000],	0x92007
	mov	dword	[0x91004],	0x00000

	mov	dword	[0x92000],	0x000083
	mov	dword	[0x92004],	0x000000

	mov	dword	[0x92008],	0x200083
	mov	dword	[0x9200c],	0x000000

	mov	dword	[0x92010],	0x400083
	mov	dword	[0x92014],	0x000000

	mov	dword	[0x92018],	0x600083
	mov	dword	[0x9201c],	0x000000

	mov	dword	[0x92020],	0x800083
	mov	dword	[0x92024],	0x000000

	mov	dword	[0x92028],	0xa00083
	mov	dword	[0x9202c],	0x000000

重新加载全局描述符表GDT,并初始化大部分寄存器。

;=======	load GDTR

	db	0x66
	lgdt	[GdtPtr64];重新加载全局描述符表GDT,初始化大部分段寄存器
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	gs,	ax
	mov	ss,	ax

	mov	esp,	7E00h

由于代码段寄存器CS不能采用直接赋值的方式来改变,所以必须借助跨段跳转指令(far JMP)或跨段调用指令(far CALL)才能实现改变。
开启地址扩展功能。

;=======	open PAE

	mov	eax,	cr4
	bts	eax,	5;第5位是PAE功能的标志位
	mov	cr4,	eax;通过置位CR4控制寄存器的PAE标志位,开启物理地址扩展功能

完成CR3控制寄存器的设置工作。

;=======	load	cr3

	mov	eax,	0x90000
	mov	cr3,	eax;将临时页目录首地址设置到CR3控制寄存器中

置位LME标志位,激活IA-32e模式。

;=======	enable long-mode

	mov	ecx,	0C0000080h		;IA32_EFER置位LME标志位,激活IA-32e模式
	rdmsr;读取msr寄存器

	bts	eax,	8;第8位是LME标志位
	wrmsr;送入msr寄存器

再次使能保护模式,置位PE标志位。

;=======	open PE and paging

	mov	eax,	cr0
	bts	eax,	0;将第0位复制到进位标志位,将eax的第0位置1,置位PG标志位,使能分页机制
	bts	eax,	31;将第31位复制到进位标志位,将eax的第31位置1,置位PE标志位,使能保护模式
	mov	cr0,	eax

但是处理器目前正在执行保护模式的程序,这种状态叫作兼容模式,即运行在IA-32e模式下的32位模式程序。若想真正运行在IA-32e模式,还需要一条跨段跳转/调用指令将CS段寄存器的值更新为IA-32e模式的代码段描述符。

从Loader跳转到内核程序

执行一条远跳转/调用指令,可切换到IA-32e模式。

	jmp	SelectorCode64:OffsetOfKernelFile;执行一条远跳转指令,进入64位IA-32e模式

当前系统虽然已进入IA-32e模式,但这只是临时中转模式,接下来的内核程序将会为系统重新创建IA-32e模式的段结构和页表结构。
附boot.asm和loader.asm完整代码。
boot.asm

;/***************************************************
;		版权声明
;
;	本操作系统名为:MINE
;	该操作系统未经授权不得以盈利或非盈利为目的进行开发,
;	只允许个人学习以及公开交流使用
;
;	代码最终所有权及解释权归田宇所有;
;
;	本模块作者:	田宇
;	EMail:		345538255@qq.com
;
;
;***************************************************/

	org	0x7c00;org指令将程序的起始地址设置为0x7c00,因为BIOS会加载引导程序至此处

BaseOfStack	equ	0x7c00

BaseOfLoader	equ	0x1000;Loader程序的起始基地址
OffsetOfLoader	equ	0x00;Loader程序的起始偏移地址,两者组成了Loader程序的起始物理地址

RootDirSectors	equ	14;根目录占用扇区数
SectorNumOfRootDirStart	equ	19;根目录的起始扇扇区号
SectorNumOfFAT1Start	equ	1;FAT1表的起始扇区号
SectorBalance	equ	17;用于平衡文件的起始簇号和数据区起始簇号的差值
    ;初始化文件系统
	jmp	short Label_Start
	nop
	BS_OEMName	db	'MINEboot';生产厂商名
	BPB_BytesPerSec	dw	512;每扇区字节数
	BPB_SecPerClus	db	1;每簇扇区数
	BPB_RsvdSecCnt	dw	1;保留扇区数
	BPB_NumFATs	db	2;FAT表的份数
	BPB_RootEntCnt	dw	224;根目录可容纳的目录项数
	BPB_TotSec16	dw	2880;总扇区数
	BPB_Media	db	0xf0;介质描述符
	BPB_FATSz16	dw	9;每FAT扇区数
	BPB_SecPerTrk	dw	18;每磁道扇区数
	BPB_NumHeads	dw	2;磁头数
	BPB_HiddSec	dd	0;隐藏扇区数
	BPB_TotSec32	dd	0;记录扇区数
	BS_DrvNum	db	0;int 13h的驱动器号
	BS_Reserved1	db	0;未使用
	BS_BootSig	db	0x29;扩展引导标记
	BS_VolID	dd	0;卷序列号
	BS_VolLab	db	'boot loader';卷标
	BS_FileSysType	db	'FAT12   ';文件系统类型

Label_Start:

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ss,	ax
	mov	sp,	BaseOfStack;初始化代码段,数据段,扩展段,堆栈段以及栈指针

;=======	clear screen

	mov	ax,	0600h;int 10h,ah=06h,al=0实现清屏功能
	mov	bx,	0700h;al=0,bx的值不生效
	mov	cx,	0;al=0,cx的值不生效
	mov	dx,	0184fh;al=0,dx的值不生效
	int	10h

;=======	set focus

	mov	ax,	0200h;int 10h,ah=02h,设置屏幕光标位置
	mov	bx,	0000h;页码
	mov	dx,	0000h;将光标设置到屏幕左上角(0,0)处
	int	10h

;=======	display on screen : Start Booting......

	mov	ax,	1301h;int 10h,ah=13h,al=01h,显示字符串,光标移动至字符串尾端位置
	mov	bx,	000fh;设置字符显示属性,白字,黑底,高亮,不闪烁
	mov	dx,	0000h;从(0,0)处开始显示
	mov	cx,	10;要显示的字符串的长度
	push	ax;保存ax寄存器
	mov	ax,	ds
	mov	es,	ax;设置要显示的字符串的段基址
	pop	ax
	mov	bp,	StartBootMessage;设置要显示的字符串的偏移地址
	int	10h

;=======	reset floppy 重新初始化软盘驱动器

	xor	ah,	ah;ah清零
	xor	dl,	dl;dl清零,代表第一个软盘驱动器
	int	13h

;=======	search loader.bin
	mov	word	[SectorNo],	SectorNumOfRootDirStart;从第一个根目录所在扇区开始搜索

Lable_Search_In_Root_Dir_Begin:

	cmp	word	[RootDirSizeForLoop],	0;判断是否所有的根目录都搜索完了
	jz	Label_No_LoaderBin;是的话,跳转到没找到loader.bin的处理分支
	dec	word	[RootDirSizeForLoop];递减循环次数
	mov	ax,	00h
	mov	es,	ax
	mov	bx,	8000h;构建目标缓冲区起始地址es:bx
	mov	ax,	[SectorNo];第一个根目录所在扇区作为待读取的磁盘起始扇区号
	mov	cl,	1;读入一个扇区
	call	Func_ReadOneSector
	mov	si,	LoaderFileName
	mov	di,	8000h
	cld;清除DF标志位
	mov	dx,	10h;每个扇区可以容纳的目录项个数送入dx
	
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;是的话则找到了loader.bin文件
	dec	cx;比较下一个字符
	lodsb;将ds:si指定的数据读入al
	cmp	al,	byte	[es:di];比较al中的字节是否和es:di处的字节相同
	jz	Label_Go_On;相同则跳转到下一个字节比较处理分支
	jmp	Label_Different;不同则跳转到不同的处理分支

Label_Go_On:
	
	inc	di;增加di
	jmp	Label_Cmp_FileName;比较下一个字节

Label_Different:

	and	di,	0ffe0h
	add	di,	20h;下一个目录项
	mov	si,	LoaderFileName
	jmp	Label_Search_For_LoaderBin;比较下一个目录项

Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1;将扇区数加1
	jmp	Lable_Search_In_Root_Dir_Begin;在下一个扇区中寻找
	
;=======	display on screen : ERROR:No LOADER Found

Label_No_LoaderBin:

	mov	ax,	1301h;int 10h,ah=13h,al=01h,显示字符串,光标移动至字符串尾端位置
	mov	bx,	008ch;黑色背景,红色字体,高亮,字体闪烁
	mov	dx,	0100h;1行0列显示
	mov	cx,	21;字符串长度
	push	ax;保存ax寄存器
	mov	ax,	ds
	mov	es,	ax;将ds送入es
	pop	ax;恢复ax寄存器
	mov	bp,	NoLoaderMessage;es:bp要显示的字符串地址
	int	10h
	jmp	$

;=======	found loader.bin name in root director struct

Label_FileName_Found:

	mov	ax,	RootDirSectors
	and	di,	0ffe0h;一个目录项是32B,低5位淸0
	add	di,	01ah;起始簇号偏移
	mov	cx,	word	[es:di];获取起始簇号
	push	cx;保存cx寄存器
	add	cx,	ax
	add	cx,	SectorBalance;计算数据区扇区号
	mov	ax,	BaseOfLoader
	mov	es,	ax
	mov	bx,	OffsetOfLoader;配置loader.bin在内存中的起始地址
	mov	ax,	cx;将数据区起始扇区号送入ax

Label_Go_On_Loading_File:
	push	ax;保存ax寄存器
	push	bx;保存bx寄存器
	mov	ah,	0eh
	mov	al,	'.';在屏幕上显示字符.
	mov	bl,	0fh;黑色背景,白色字体,高亮,不闪烁
	int	10h
	pop	bx;恢复bx寄存器
	pop	ax;恢复ax寄存器

	mov	cl,	1
	call	Func_ReadOneSector;读取一个扇区
	pop	ax;恢复ax寄存器
	call	Func_GetFATEntry;获取下一个FAT表项
	cmp	ax,	0fffh;判断是否是最后一个簇
	jz	Label_File_Loaded;如果是则跳转到loader程序起始地址
	push	ax;保存ax寄存器
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance;计算下一个FAT表项对应的数据扇区号
	add	bx,	[BPB_BytesPerSec];指向下一个512字节
	jmp	Label_Go_On_Loading_File;加载下一个FAT表项对应的数据区的数据

Label_File_Loaded:
	
	jmp	BaseOfLoader:OffsetOfLoader

;=======	read one sector from floppy

Func_ReadOneSector:
	
	push	bp;保存栈帧寄存器
	mov	bp,	sp;保存栈寄存器
	sub	esp,	2;在栈上开辟两个字节存储单元
	mov	byte	[bp - 2],	cl;将cl存储在刚开辟的地址中
	push	bx;保存bx寄存器
	mov	bl,	[BPB_SecPerTrk];将每磁道扇区数送至bl
	div	bl;使用ax中存储的待读取的磁盘起始扇区号除以bl寄存器
	inc	ah;ah存储目标磁道内的起始扇区数,因为从1开始计数,所以加1
	mov	cl,	ah;将ah送入cl
	mov	dh,	al;将al寄存器保留一份
	shr	al,	1;目标磁道号右移一位计算磁道号
	mov	ch,	al;ch存储磁道号
	and	dh,	1;目标磁道号和1与计算出磁头号
	pop	bx;恢复bx寄存器
	mov	dl,	[BS_DrvNum];将驱动器号送入dl
Label_Go_On_Reading:
	mov	ah,	2;int 13h ah=02h实现软盘读取操作
	mov	al,	byte	[bp - 2];将读入的扇区数送入al
	int	13h
	jc	Label_Go_On_Reading
	add	esp,	2;释放开始申请的2个字节的栈存储空间
	pop	bp;恢复栈帧寄存器
	ret

;=======	get FAT Entry 根据当前FAT表项索引出下一个FAT表项

Func_GetFATEntry:

	push	es;保存es寄存器
	push	bx;保存bx寄存器
	push	ax;保存ax寄存器
	mov	ax,	00
	mov	es,	ax;将00送入es寄存器
	pop	ax;恢复ax寄存器
	mov	byte	[Odd],	0;将奇偶标志置0
	mov	bx,	3
	mul	bx;被乘数在ax寄存器,乘数在bx寄存器,乘积在dx:ax寄存器
	mov	bx,	2
	div	bx;每个FAT表项占用12bit,每三个字节存储两个FAT表项,被除数在dx:ax寄存器,除数在bx寄存器,商在ax寄存器,余数在dx寄存器
	cmp	dx,	0
	jz	Label_Even;余数为偶数跳转到偶数处理分支
	mov	byte	[Odd],	1;余数为奇数

Label_Even:

	xor	dx,	dx;将dx寄存器淸0
	mov	bx,	[BPB_BytesPerSec]
	div	bx;再除以每扇区字节数,商在ax寄存器为FAT表项的偏移扇区号,余数在dx寄存器为FAT表项在扇区中的偏移位置
	push	dx;保存dx寄存器
	mov	bx,	8000h;目标缓冲区偏移
	add	ax,	SectorNumOfFAT1Start;从FAT的第一个扇区号开始
	mov	cl,	2;读出2个扇区
	call	Func_ReadOneSector
	
	pop	dx;恢复dx寄存器
	add	bx,	dx;将bx加上偏移位置
	mov	ax,	[es:bx];将下一个FAT表项送入ax寄存器
	cmp	byte	[Odd],	1;判断奇偶标志
	jnz	Label_Even_2;偶数,跳转套偶数处理分支
	shr	ax,	4;奇数,右移4位

Label_Even_2:
	and	ax,	0fffh;高位淸0
	pop	bx;恢复bx寄存器
	pop	es;恢复es寄存器
	ret

;=======	tmp variable

RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0

;=======	display messages

StartBootMessage:	db	"Start Boot"
NoLoaderMessage:	db	"ERROR:No LOADER Found"
LoaderFileName:		db	"LOADER  BIN",0

;=======	fill zero until whole sector

	times	510 - ($ - $$)	db	0
	dw	0xaa55;引导扇区标识
```
loader.asm
```asm
;/***************************************************
;		版权声明
;
;	本操作系统名为:MINE
;	该操作系统未经授权不得以盈利或非盈利为目的进行开发,
;	只允许个人学习以及公开交流使用
;
;	代码最终所有权及解释权归田宇所有;
;
;	本模块作者:	田宇
;	EMail:		345538255@qq.com
;
;搞错了,应该是先dd if=/dev/zero of=./bochs-2.6.8/boot.img count=2048
;再写入 dd if=boot.bin of=./bochs-2.6.8/boot.img count=1 bs=512 conv=notrunc
;***************************************************/

org	10000h
	jmp	Label_Start

%include	"fat12.inc"

BaseOfKernelFile	equ	0x00
OffsetOfKernelFile	equ	0x100000;内核程序起始地址位于1MB处,因为1MB以下的地址不全是可用内存地址空间

BaseTmpOfKernelAddr	equ	0x00
OffsetTmpOfKernelFile	equ	0x7E00;内核程序临时转存空间,因为内核程序的读取是通过BIOS中断服务程序int 13h实现的,BIOS在
;实模式下只支持上限为1MB的物理地址空间寻址

MemoryStructBufferAddr	equ	0x7E00

[SECTION gdt];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;是此结构的起始地址,GDT表的长度
	dd	LABEL_GDT;GDT表的基地址

SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT;32位段选择子,是段描述符在GDT表中的索引
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT;32位段选择子,是段描述符在GDT表中的索引

[SECTION gdt64];64位段结构

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;64位段选择子
SelectorData64	equ	LABEL_DESC_DATA64 - LABEL_GDT64;64位段选择子

[SECTION .s16]
[BITS 16]

Label_Start:

	mov	ax,	cs
	mov	ds,	ax
	mov	es,	ax
	mov	ax,	0x00
	mov	ss,	ax
	mov	sp,	0x7c00;初始化ds,es,ss,sp寄存器

;=======	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;在屏幕上显示es:bp指定的字符串

;=======	open address A20 让fs可以在实模式下的寻址能力超过1MB
	push	ax
	in	al,	92h
	or	al,	00000010b
	out	92h,	al;通过置位0x92端口的第一位来开启A20功能
	pop	ax

	cli;关闭外部中断

	db	0x66
	lgdt	[GdtPtr];加载保护模式结构数据信息

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax;置cr0第0位开启保护模式

	mov	ax,	SelectorData32
	mov	fs,	ax;给fs寄存器加载新的数据段值
	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;清除DF标志位
	mov	dx,	10h;每个扇区可以容纳的目录项个数送入dx
	
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;低5位清0
	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 将kernel.bin加载到es:bx缓冲区
	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];fs:edi指向目标kernel地址

	mov	ax,	BaseTmpOfKernelAddr
	mov	ds,	ax
	mov	esi,	OffsetTmpOfKernelFile;ds:esi指向源kernel地址

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;获取下一个FAT表项
	cmp	ax,	0FFFh
	jz	Label_File_Loaded
	push	ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance;计算下一个FAT表项对应的数据区扇区号

	jmp	Label_Go_On_Loading_File

Label_File_Loaded:
		
	mov	ax, 0B800h
	mov	gs, ax;设置gs段基址
	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;显示Start Get Memory Struct.字符串

	mov	ebx,	0
	mov	ax,	0x00
	mov	es,	ax
	mov	di,	MemoryStructBufferAddr
	;es:di指向一个地址范围描述结构 ARDS(Address Range Descriptor Structure), BIOS将会填充此结构

Label_Get_Mem_Struct:

	mov	eax,	0x0E820;指定获取内存信息
	mov	ecx,	20;es:di所指向的地址范围描述结构的大小,以字节为单位
	;无论es:di所指向的结构如何设置,BIOS最多将会填充ecx字节。
	;不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节
	mov	edx,	0x534D4150
	;0534D4150h('SMAP')——BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息被BIOS放置到es:di所指向的结构中
	int	15h;调用中断服务程序int 15h来获取物理地址空间信息
	jc	Label_Get_Mem_Fail
	add	di,	20

	cmp	ebx,	0;放置着“后续值(continuation value)”,第一次调用时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;显示Get Memory Struct ERROR字符串
	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 Memory Struct SUCCESSFUL!字符串

;=======	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;显示Start Get SVGA VBE Info字符串

	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;显示Get SVGA VBE Info ERROR字符串

	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 VBE Info SUCCESSFUL!字符串

;=======	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;显示Start Get SVGA Mode Info字符串


	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;显示Get SVGA Mode Info ERROR字符串

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;显示Get SVGA Mode Info SUCCESSFUL!字符串

;=======	set the SVGA mode(VESA VBE)

	mov	ax,	4F02h;对于超级VGA显示卡,我们可用AX=4F02H和下列BX 的值来设置其显示模式
	mov	bx,	4180h	;========================mode : 0x180 or 0x143
	int 	10h;设置SVGA显示模式

	cmp	ax,	004Fh
	jnz	Label_SET_SVGA_Mode_VESA_VBE_FAIL

;=======	init IDT GDT goto protect mode 

	cli			;======close interrupt关闭中断,保证在切换过程中不会出现异常和中断

	db	0x66;用于修饰当前操作数是32位宽
	lgdt	[GdtPtr];将GDT的基地址和长度加载到GDTR寄存器

;	db	0x66
;	lidt	[IDT_POINTER]

	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax;置位CR0控制寄存器的PE标志位

	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;检测是否支持IA-32e模式
	test	eax,	eax

	jz	no_support

;=======	init temporary page table 0x90000

	mov	dword	[0x90000],	0x91007;将IA-32e模式的页目录首地址设置在0x90000地址处,并配置各级页表项的值
	;该值由页表起始地址和页属性组成
	mov	dword	[0x90004],	0x00000
	mov	dword	[0x90800],	0x91007
	mov	dword	[0x90804],	0x00000

	mov	dword	[0x91000],	0x92007
	mov	dword	[0x91004],	0x00000

	mov	dword	[0x92000],	0x000083
	mov	dword	[0x92004],	0x000000

	mov	dword	[0x92008],	0x200083
	mov	dword	[0x9200c],	0x000000

	mov	dword	[0x92010],	0x400083
	mov	dword	[0x92014],	0x000000

	mov	dword	[0x92018],	0x600083
	mov	dword	[0x9201c],	0x000000

	mov	dword	[0x92020],	0x800083
	mov	dword	[0x92024],	0x000000

	mov	dword	[0x92028],	0xa00083
	mov	dword	[0x9202c],	0x000000

;=======	load GDTR

	db	0x66
	lgdt	[GdtPtr64];重新加载全局描述符表GDT,初始化大部分段寄存器
	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;第5位是PAE功能的标志位
	mov	cr4,	eax;通过置位CR4控制寄存器的PAE标志位,开启物理地址扩展功能

;=======	load	cr3

	mov	eax,	0x90000
	mov	cr3,	eax;将临时页目录首地址设置到CR3控制寄存器中

;=======	enable long-mode

	mov	ecx,	0C0000080h		;IA32_EFER置位LME标志位,激活IA-32e模式
	rdmsr;读取msr寄存器

	bts	eax,	8;第8位是LME标志位
	wrmsr;送入msr寄存器

;=======	open PE and paging

	mov	eax,	cr0
	bts	eax,	0;将第0位复制到进位标志位,将eax的第0位置1,置位PG标志位,使能分页机制
	bts	eax,	31;将第31位复制到进位标志位,将eax的第31位置1,置位PE标志位,使能保护模式
	mov	cr0,	eax

	jmp	SelectorCode64:OffsetOfKernelFile;执行一条远跳转指令,进入64位IA-32e模式

;=======	test support long mode or not

support_long_mode:

	mov	eax,	0x80000000
	cpuid;获取cpu支持的最大功能号
	cmp	eax,	0x80000001
	setnb	al;最大功能号不低于	0x80000001时置位al
	jb	support_long_mode_done;小于0x80000001跳转
	mov	eax,	0x80000001
	cpuid;调用cpuid的0x80000001扩展功能项,看是否支持IA-32e模式
	bt	edx,	29;将edx的第29位复制到进位标志位,CPUID的扩展功能项的0x80000001的第29位,指示
	;处理器是否支持IA-32e模式
	setc	al;当进位标志位为1时置al为1,当进位标志位为0时置al为0
support_long_mode_done:
	
	movzx	eax,	al;将al的值送入eax寄存器,并且高位清零
	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];将屏幕偏移值送入edi寄存器
	mov	ah,	0Fh;将字体的颜色属性值送入ah
	mov	dl,	al;将al送入dl,暂时把al寄存器的低四位保存到dl寄存器
	shr	al,	4;将al右移4位
	mov	ecx,	2;al分两次显示
.begin:

	and	al,	0Fh;将高4位清0
	cmp	al,	9;和9比较
	ja	.1;比9大,跳转到标号1处
	add	al,	'0';直接与'0'相加
	jmp	.2;比9小跳转到标号2处
.1:

	sub	al,	0Ah;减去0Ah
	add	al,	'A';加上字符'A'
.2:

	mov	[gs:edi],	ax;保存至gs寄存器位段基址,DisplayPosition为偏移的显示字符内存空间中
	add	edi,	2;偏移位置增加2
	
	mov	al,	dl;再显示低4位
	loop	.begin

	mov	[DisplayPosition],	edi

	pop	edi
	pop	edx
	pop	ecx
	
	ret


;=======	tmp IDT;为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!"
```

版权声明:本文为qq_22866497原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。