3.BootLoader引导启动程序

家电维修 2023-07-16 19:17www.caominkang.com家电维修技术

        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 0xaa55
 3.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意思是写入后不截断输出文件尺寸

bochs -f ./bootsrc 

3.1.5.加载Loader到内存

        本操作系统将选用逻辑简单的FAT12文件系统来装载Loader程序和内核程序。

        将软盘格式化成FAT12文件系统过程中,FAT类文件系统会对软盘里的扇区结构化处理,进而把软盘扇区划分为引导扇区,FAT表,根目录区和数据区。

  • 引导扇区                                                                                                                                    不仅含有引导程序,还有FAT12文件系统的整个组成结构信息。这些信息描述了FAT12文件系统对磁盘扇区的管理情况,它相当于是EXT类文件系统的superblock结构。 
名称偏移长度内容本系统引导程序数据BS_jmpBoot03跳转指令jmp short Label_Start; nopBS_OEMName38生产厂商名'MINEboot'BPB_BytesPerSec112每扇区字节数512BPB_SecPerClus131每簇扇区数1BPB_RsvdSecCnt142保留扇区数1BPB_NumFATs161FAT表的份数2BPB_RootEntCnt172根目录可容纳的目录项数224BPB_TotSec16192总扇区数2880BPB_Media211介质描述符0xF0BPB_FATSz16222每FAT扇区数9BPB_SecPerTrk242每磁道扇区数18BPB_NumHeads262磁头数2BPB_Hiddsec284隐藏扇区数0BPB_TotSec32324如BPB_TotSec16为0,则由此值记录扇区数0BS_DrvNum361int 13h的驱动器号0BS_Reserved1371未使用0BS_BootSig381扩展引导标记(29h)0x29BS_VolID394卷序列号0BS_VolLab4311卷标'boot loader'BS_FileSysType548文件系统类型'FAT12'引导代码62448引导代码,数据及其他信息结束标记5102结束标记0xAA550xAA55
  •   
    • 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", 0
3.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/

        当虚拟软盘镜像文件挂载成功后便可对其进行访问了。

        其实格式化文件系统,就是把文件系统的所有结构数据写入到磁盘扇区的过程。

Copyright © 2016-2025 www.jianfeikang.com 建飞家电维修 版权所有 Power by