3.BootLoader引导启动程序
Boot引导程序主要负责开启启动和加载Loader程序;Loader引导加载程序则用于完成配置硬件工作环境,引导加载内核等。
3.1.Boot引导程序目前BIOS支持的设备启动项有软盘启动,U盘启动,硬盘启动,网络启动。
3.1.1.BIOS引导原理当BIOS自检结束后会根据启动选项设置去选择启动设备,软盘启动下,检测软盘下是软盘的第0磁头第0磁道第1扇区,是否以数值0x55和0xaa作为结尾。如是,BIOS认为这个扇区是一个Boot Sector,进而把此扇区的数据复制到物理内存地址0x7c00处,随后将处理器执行权移交给这段程序(跳转到0x7c00地址处执行)。
鉴于引导扇区容量限制,Boot引导程序仅能作为一级助推器,将功能更强大的引导加载程序Loader装载到内存中。一旦Loader引导加载程序开始执行,一切由软件控制。
在BIOS向引导程序移交执行权前,BIOS会对处理器进行初始化,其中包括CS和IP。当BIOS跳转到引导程序时,CS和IP的值分别是0x0000和0x7c00。此时处理器处于实模式下。
FAT12文件系统作为软盘文件系统及后续的U盘文件系统是合适的,因为它简单。
3.1.2.写一个Boot引导程序以Intel汇编格式
0x7c00 BaseOfStack equ 0x7c00 Label_Start: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack
; ============= clear screen mov ax, 0600h mov bx, 0700h mov cx, 0 mov dx, 0184fh int 10h ; ============= set focus mov ax, 0200h mov bx, 0000h mov dx, 0000h int 10h ; ========== display on screen : Start Booting ...... mov ax, 1301h mov bx, 000fh mov dx, 0000h mov cx, 10 push ax mov ax, ds mov es, ax pop ax mov bp, StartBootMessage int 10h
- 设置屏幕光标位置 int 10h, ah=02设定光标位置
- DH=游标列数
- DL=游标行数
- BH=页码
- 上卷指定范围的窗口 INT 10h, AH=06h:按指定范围滚动屏幕
- AL=滚动列数,为0实现清空屏幕
- BH=滚动后空出位置放入的属性
- CH=滚动范围左上角坐标列号
- CL=滚动范围左上角坐标行号
- DH
- DL
- BH=颜色属性
- 显示字符串 INT 10h, AH=13h显示一行字符串
- AL=00h:字符串的属性由BL寄存器提供。CX寄存器提供长度。显示后光标位置不变
- AL=01h与00h区别是同步更新光标位置
- AL=02h显示后光标位置不变,字符串属性由每个字符后紧跟字节提供
- AL=03h与02h区别是光标会移动到字符串尾端
- CX=字符串长度
- DH=游标的坐标行号
- DL=游标的坐标列号
- ES:BP=>要显示字符串的内存地址
- BH=页码
- BL=字符属性/颜色属性
; =========== reset floppy xor ah, ah xor dl, dl int 13h jmp $
软盘驱动器的复位。
INT 13h, AH=00h重置磁盘驱动器,为下一次读写软盘做准备
- DL=驱动器号,00H~7FH软盘;80H~0FFH硬盘
- DL=00h代表第一个软盘驱动器
- DL=01h代表第二个软盘驱动器
- DL=80h代表第一个硬盘驱动器
- DL=81h代表第二个硬盘驱动器
StartBootMessage: db "Start Boot" ; ====== fill zero until hole sector times 510 - ($-$$) db 0 d 0xaa553.1.3.创建虚拟软盘镜像文件
软盘和软盘驱动器已经退出历史舞台。但大多数虚拟机软件都提供虚拟软盘驱动器和创建虚拟软盘镜像功能。如何用Bochs虚拟机自带工具创建虚拟软盘镜像。
虚拟磁盘镜像创建工具bximage。
3.1.4.在Bochs上运行我们的Boot程序nasm boot.asm -o boot.bin
用dd命令将引导程序强制写入到虚拟软盘的固定扇区。
dd if=boot.bin of=../../bochs-2.6.8/boot.img bs=512 count=1 conv=notrunc
conv=notrunc意思是写入后不截断输出文件尺寸
3.1.5.加载Loader到内存bochs -f ./bootsrc
本操作系统将选用逻辑简单的FAT12文件系统来装载Loader程序和内核程序。
将软盘格式化成FAT12文件系统过程中,FAT类文件系统会对软盘里的扇区结构化处理,进而把软盘扇区划分为引导扇区,FAT表,根目录区和数据区。
- 引导扇区 不仅含有引导程序,还有FAT12文件系统的整个组成结构信息。这些信息描述了FAT12文件系统对磁盘扇区的管理情况,它相当于是EXT类文件系统的superblock结构。
-
- BS_OEMName。记录制造商名字。
- BPB_SecPerClus。描述了每簇扇区数。
- BPB_RsvdSecCnt。指定保留扇区数量。为1,意味着引导扇区在保留扇区内,故FAT表从软盘第二个扇区开始。
- BPB_NumFATS。指定FAT12文件系统中FAT表的份数。
- BPB_RootEntCnt。指定根目录可容纳的目录项数。
- BPB_TotSec16。记录总扇区数,包括保留扇区,FAT表,根目录区,数据区。
- BPB_Media。描述存储介质类型。对不可移动的存储介质是0xF8,对可移动的存储介质,是0xF0。无论该字段写入什么数值,也需向FAT[0]的低字节写入相同值。
- BPB_FATSz16。记录着FAT表占用的扇区数。
- BS_VolLab。指定卷标。就是Windos或Linux中显示的磁盘名。
- BS_FileSysType。
- FAT表 FAT12文件系统以簇为单位来分配数据区的存储空间,每个簇长度为BPB_BytesPerSecBPB_SecPerClus字节,数据区的簇号与FAT表的表项是一一对应关系。文件在FAT类文件系统的存储单位是簇。 FAT表的表项位宽与FAT类型有关。当一个文件的体积增大时,其所需的磁盘存储空间也会增加,随着时间推移,文件系统将无法确保文件中的数据存储在连续的磁盘扇区内,文件往往被分成若干片段。借助FAT表项,可将这些不连续的文件片段按簇号链接起来。
FAT项实例值描述0FF0h磁盘标示字,低字节与BPB_Media数值保持一致1FFFh第一个簇已经被占用2003h000h:可用簇3004h002h~FEFh已用簇,标识下一个簇的簇号......FF0h~FF6h保留簇NFFFhFF7h坏簇N+1000hFF8h~FFFh文件一个簇......
FAT[0]的低8位在数值上与BPB_Media一致,剩余位全部设置为1。文件系统初始化期间,已明确将FAT[1]赋值为FFFh。
-
根目录区和数据区 根目录区只保存目录项信息,数据区不但可保存目录项信息,还可保存文件内数据。 此处的目录项是一个由32B组成的结构体,它既可表示成一个目录,又可表示成一个文件,其中记录着名字,长度,数据起始簇号等信息。表3-3-是目录项的完整结构。
名称偏移长度描述DIR_Name0x0011文件名8B,扩展名3BDIR_Attr0x0B1文件属性保留0x0C10保留位DIR_WrtTime0x162一次写入时间DIR_WrtDate0x182一次写入日期DIR_FstClus0x1A2起始簇号DIR_FileSize0x1C4文件大小FAT[0]和FAT[1]是保留项,不能用于数据区的簇索引,数据区的第一个有效簇号是2。
0x7c00 BaseOfStack equ 0x7c00 BaseOfLoader equ 0x1000 OffsetOfLoader equ 0x00 RootDirSectors equ 14 SectorNumOfRootDirStat equ 19 SectorNumOfFAT1Start equ 1 SectorBalance equ 17 jmp short Label_Start nop BS_OEMName db 'MINEboot' BPB_BytesPerSec d 512 BPB_SecPerClus db 1 BPB_RsvdSecCnt d 1 BPB_NumFATs db 2 BPB_RootEntCnt d 224 BPB_TotSec16 d 2880 BPB_Media db 0xf0 BPB_FATSz16 d 9 BPB_SecPerTrk d 18 BPB_NumHeads d 2 BPB_hiddSec dd 0 BPB_TotSec32 dd 0 BS_DrvNum db 0 BS_Reserved1 db 0 BS_BootSig db 29h BS_VolID dd 0 BS_VolLab db 'boot loader' BS_FileSysType db 'FAT12'
计算根目录扇区数,
-
(BPB_RootEntCnt32+BPB_BytesPerSec-1)/BPB_BytesPerSec
计算根目录的起始扇区号,即保留扇区数+FAT表扇区数FAT表份数=1+92=19,因为扇区标号从0开始,故根目录起始扇区号为19。
数据区对应的有效簇号是FAT[2],为正确计算FAT表项对应的数据区起始扇区号,需将FAT表项值减2。本程序暂用一种更取巧方法,将根目录起始扇区号减2(19-2=17),进而间接把数据区的起始扇区号(数据区起始扇区号=根目录起始扇区号+根目录所占扇区数)减2。
准备好FAT12文件系统引导扇区数据后,还需为引导程序准备软盘读取功能。
; ========= read one sector from floppy 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
代码中Func_ReadOneSector模块负责实现软盘读取,它借助BIOS中断服务程序INT 13h的主功能号AH=02h实现软盘扇区的读取操作,该中断服务程序的各寄存器参数说明如下。
INT 13h, AH=02h功能读取磁盘扇区
- AL=读入的扇区数
- CH=磁道号(柱面号)的低8位
- CL=扇区号1~63(bit0~5),磁道号(柱面号)的高2位(bit6~7,只对硬盘有效)
- DH=磁头号
- DL=驱动器号(如操作的是硬盘驱动器,bit7需置位)
- ES:BX=>数据缓冲区
模块Func_ReadOneSector详细参数说明如下
- AX=待读取的磁盘起始扇区号
- CL=读入的扇区数量
- ES:BX=>目标缓冲区起始地址
因为Func_ReadOneSector模块传入的磁盘扇区号是LBA格式的,而INT 13h, AH=02h中断服务程序只能受理CHS(柱面、磁头、扇区)格式的磁盘扇区号。需将LBA格式转换为CHS格式
LBA扇区号 / 每磁道扇区数 =
商Q
柱面号=Q>=1
磁头号=Q
余数R
起始扇区号=R+1
模块Func_ReadOneSector读取软盘前,会先保存栈帧寄存器和栈寄存器的数值,从栈中开辟两个字节的存储空间。
有了软盘扇区读取功能,便可在其基础商实现文件系统访问功能。
; ======================= search loader.bin mov ord [SectorNo], SectorNumOfRootDirStart Label_Search_In_Root_Dir_Begin: cmp ord [RootDirSizeForLoop], 0 jz Label_No_LoaderBin dec ord [RootDirSizeForLoop] mov ax, 00h mov es, ax mov bx, 800h mov ax, [SectorNo] mov cl, 1 call Func_ReadOneSector mov si, LoaderFileName mov di, 8000h cld mov dx, 10h Label_Search_For_LoaderBin: cmp dx, 0 jz Label_Goto_Next_Sector_In_Root_Dir dec dx mov cx, 11 Label_Cmp_FileName: cmp cx, 0 jz Label_FileName_Found dec cx lodsb cmp al, byte [es:di] jz Label_Go_On jmp Label_Different Label_Go_On: inc di jmp Label_Cmp_FileName Label_Different: add di, 0ffe0h add di, 20h mov si, LoaderFileName jmp Label_Search_For_LoaderBin Label_GoTo_Next_Sector_In_Root_Dir: add ord [SectorNo], 1 jmp Label_Search_In_Root_Dir_Begin
上述代码能从根目录搜索出引导加载程序(文件名loader.bin)。程序执行初期,程序会先保存根目录的起始扇区号,并依据根目录占用磁盘扇区数来确定要搜索的扇区数,并从根目录中读入一个扇区的数据到缓冲区;接下来,遍历读入缓冲区中的每个目录项,寻找与目标文件名字匹配的目录项,其中DX寄存器记录着每个扇区可容纳的目录项个数,CX寄存器记录着目录项的文件名长度(11B,包括文件名,扩展名,不包含分隔符".")。在比对每个目录项文件名过程中,使用了汇编指令LODSB,该命令的加载方向与DF标志位有关,使用此命令时需用CLD清DF标志位。
以下是Intel官方白皮书对LODSB/LODSW/LODSD/LODSQ的概况描述
- 命令可从DS:(R|E)SI寄存器指定的内存地址中读取数据到AL/AX/EAX/RAX寄存器
- 当数据载入AL/AX/EAX/RAX寄存器后,(R|E)SI寄存器会依据R|EFLAGS的DF标志位自动增加或减少载入的数据长度(1、2、4、8字节)。DF=0时,(R|E)SI寄存器自动增加。反之,自动减少。
; =========== display on screen: ERROR : No LOADER Found Label_No_LoaderBin: mov ax, 1301h mov bx, 008ch mov dx, 0100h mov cx, 21 push ax mov ax, ds mov es, ax pop ax mov bp, NoLoaderMessage int 10h jmp $
; ========== 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
此前已经提及FAT12文件系统的每个FAT表项占用12bit,即每三个字节存储两个FAT表项,由此看来,FAT表项的存储位置有奇偶性。
模块Func_GetFATEntry功能根据当前FAT表项索引出下一个FAT表项。
- AH=FAT表项号(输入参数、输出参数)
这段程序会保存FAT表项号,并将奇偶标志变量(变量[odd])置0。因为每个FAT表项占1.5B,所以将FAT表项乘以3除以2(扩大1.5倍),来判读余数的奇偶性并保存在[odd]中(奇数为1,偶数为0),再将计算结果除以每扇区字节数,商值为FAT表项的偏移扇区号,余数值为FAT表项在扇区中的偏移位置。接着,通过Func_ReadOneSector模块连续读入两个扇区的数据。,根据奇偶标志变量进一步处理奇偶项错位问题,即奇数项向右移动4位。
在完成Func_ReadOneSector和Func_GetFATEntry模块后,就可借助这两个模块把loader.bin文件内的数据从软盘扇区读取到指定的地址中。下述实现了从FAT12文件系统加载loader.bin文件到内存的过程。
Label_FileName_Found: mov ax, RootDirSectors and di, 0ffe0h add di, 01ah mov cx, ord [es:di] push cx add cx, ax add cx, SectorBalance mov ax, BaseOfLoader mov es, ax mov bx, OffsetOfLoader 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 call Func_GetFATEntry cmp ax, 0fffh jz Label_File_Loaded push ax mov dx, RootDirSectors add ax, dx add ax, SectorBalance add bx, [BPB_BytesPerSec] jmp Label_Go_On_Loading_File Label_File_Loaded: jmp $
在Label_FileName_Found模块中,程序会先取得目录项DIR_FstClus字段的数值,并通过配置ES寄存器和BX寄存器来指定loader.bin程序在内存中的起始地址,再根据loader.bin程序的起始簇号计算出其对应的扇区号。
每读入一个扇区的数据就通过Func_GetFATEntry模块取得下一个FAT表项,并跳转至Label_Go_On_Loading_File处继续读入下一个簇的数据,如此往复,直到Func_GetFATEntry模块返回的FAT表项值是0fffh为止。当loader.bin文件的数据全部读取到内存后,跳转至Label_File_Loaded处准备执行loader.bin程序。
INT 10h, AH=0Eh功能在屏幕上显示一个字符。
- AL=待显示字符
- BL=前景色
; ========= tmp variable RootDirSizeForLoop d RootDirSectors SectorNo d 0 Odd db 0 ; ========== display messages StartBootMessage: db "Start Boot" NoLoaderMessage: db "ERROR:No LOADER Found" LoaderFileName: db "LOADER BIN", 03.1.6.从Boot跳转到Loader程序
跳转至Loader引导加载程序处,向其移交处理器的控制权。
Label_File_Loaded: jmp BaseOfLoader:OffsetOfLoader
编写一个简单的Loader引导加载程序来检测这个跳转过程
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 mov cx, 12 push ax mov ax, ds mov es, ax pop ax mov bp, StartLoaderMessage int 10h jmp $ ; =============== display message StartLoaderMessage:db "Start Loader"
当loader.asm程序编译结束后,需将生成的二进制程序loader.bin复制到虚拟软盘镜像文件boot.img中。此处的复制过程与boot.bin程序的写入过程采用了完全不同的方法。
当boot.bin程序写入到boot.img虚拟软盘镜像文件后,boot.img虚拟软盘已经拥有了FAT12文件系统,那么应该借助挂载命令mount和复制命令cp,把引导加载程序loader.bin复制到文件系统中。复制过程需执行以下命令
mount ../bochs-2.6.8/boot.img /media/ -t vfat -o loop
cp loader.bin /media/
sync
umount /media/
当虚拟软盘镜像文件挂载成功后便可对其进行访问了。
其实格式化文件系统,就是把文件系统的所有结构数据写入到磁盘扇区的过程。