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
```
将内核程序加载到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通常由系统服务来使用。
- 从实模式进入保护模式
系统数据结构,系统在进入保护模式之前,必须创建一个拥有代码段描述符和数据段描述符的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;切换到保护模式的代码段去执行
- 从保护模式进入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!"
```