- org 0x7c00
-
- BaseOfStack equ 0x7c00
- BaseOfLoader equ 0x1000
- OffsetOfLoader equ 0x00
- RootDirSectors equ 14
- SectorNumOfRootDirStart equ 19
- SectorNumOfFAT1Start equ 1
- 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
- BPB_RootEntCnt dw 224
- BPB_TotSec16 dw 2880
- BPB_Media db 0xf0
- BPB_FATSz16 dw 9
- BPB_SecPerTrk dw 18
- BPB_NumHeads dw 2
- BPB_HiddSec dd 0
- BPB_TotSec32 dd 0
- BS_DrvNum db 0
- BS_Reserved1 db 0
- BS_BootSig db 0x29
- BS_VolID dd 0
- BS_VolLab db 'boot loader'
- BS_FileSysType db 'FAT12 '
这段程序中的代码BaseOfLoader equ 0x1000和offsetofLoader equ 0x00组合成了Loader程序的起始物理地址,这个组合必须经过实模式的地址变换公式才能生成物理地址,即BaseofLoader<<4 +offsetofLoader = 0x10000。
代码RootDirSectors equ 14定义了根目录占用的扇区数,这个数值是根据FAT12文件系统提供的信息经过计算而得,即(BPB_RootEntCnt * 32 + BPB_BytesPerSec - 1)/BPB_BytesPersec = ( 224×32 + 512 - 1)/ 512 = 14。
等价语句sectorNumofRootDirstart equ 19定义了根目录的起始扇区号,这个数值也是通过计算而得,即保留扇区数+FAT表扇区数*FAT表份数=1 + 9 * 2= 19,因为扇区编号的计数值从O开始,故根目录的起始扇区号为19。
程序SectorNumOfFAT1Start equ 1代表了FAT1表的起始扇区号,在FAT1表前面只有一个保留扇区(引导扇区),而且它的扇区编号是0,那么FAT1表的起始扇区号理应为1。
汇编代码sectorBalance equ 17用于平衡文件(或者目录)的起始簇号与数据区起始簇号的差值。更通俗点说,因为数据区对应的有效簇号是2(FAT[2]),为了正确计算出FAT表项对应的数据区起始扇区号,则必须将FAT表项值减2,或者将数据区的起始簇号/扇区号减2(仅在每簇由一个扇区组成时可用)。本程序暂时采用一种更取巧的方法是,将根目录起始扇区号减2(19-2=17),进而间接把数据区的起始扇区号(数据区起始扇区号=根目录起始扇区号+根目录所占扇区数)减2。
- ;======= 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 功能:读取磁盘扇区。
模块Func_ReadOnesector仅仅是对BIOS中断服务程序的再次封装,以简化读取磁盘扇区的操作过程,进而在调用Func_ReadOnesector模块时,只需传递下列参数到对应的寄存器中,即可实现磁盘扇区的读取操作。模块Func_ReadOnesector详细参数说明如下。
模块Func_Readonesector功能:读取磁盘扇区。
因为Func_ReadOnesector模块传入的磁盘扇区号是LBA (Logical Block Address,逻辑块寻址)格式的,而INT 13h,AH=O2h中断服务程序只能受理CHS ( Cylinder /Head/Sector,柱面/磁头/扇区)格式的磁盘扇区号,那么必须将LBA格式转换为CHS格式,通过下列公式可将LBA格式转换为CHS格式。

模块Func_ReadOnesector在读取软盘之前,会先保存栈帧寄存器和栈寄存器的数值,从栈中开辟两个字节的存储空间(将栈指针向下移动两个字节),由于此时代码bp - 2与ESP寄存器均指向同一内存地址,所以CL寄存器的值就保存在刚开辟的栈空间里。而后,使用AX寄存器(待读取的磁盘起始扇区号)除以BL寄存器(每磁道扇区数),计算出目标磁道号(商:AL寄存器)和目标磁道内的起始扇区号(余数:AH寄存器),考虑到磁道内的起始扇区号从1开始计数,故此将余数值加1,即inc ah。紧接着,再按照上公式计算出磁道号(也叫柱面号)与磁头号,将计算结果保存在对应寄存器内。最后,执行INT 13h中断服务程序从软盘扇区读取数据到内存中,当数据读取成功(CF标志位被复位)后恢复调用现场。
- ;======= search loader.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, 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:
-
- 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
- jmp Lable_Search_In_Root_Dir_Begin
通过这段代码能够从根目录中搜索出引导加载程序(文件名为loader.bin )。在程序执行初期,程序会先保存根目录的起始扇区号,并依据根目录占用磁盘扇区数来确定需要搜索的扇区数,并从根目录中读入一个扇区的数据到缓冲区;接下来,遍历读入缓冲区中的每个目录项,寻找与目标文件名字符串( “LOADER BIN" , 0 )相匹配的目录项,其中DX寄存器记录着每个扇区可容纳的目录项个数(512/32= 16=0x10 ),CX寄存器记录着目录项的文件名长度(文件名长度为11B,包括文件名和扩展名,但不包含分隔符“.”)。在比对每个目录项文件名的过程中,使用了汇编指令LODSB,该命令的加载方向与DF标志位有关,因此在使用此命令时需用CLD指令清DF标志位。
以下是Intel官方白皮书对LODSB/LODSW/LODSD/LODSQ指令的概括描述。
1.该命令可从DS:(R|E)SI寄存器指定的内存地址中读取数据到AL/AX/EAX/RAX寄存器。
2.当数据载入到AL/AX/EAX/RAX寄存器后,(RE)SI寄存器将会依据R|EFLAGs标志寄存器的DF标志位自动增加或减少载人的数据长度(1/2/4/8字节)。当DF=0时,(RE)SI寄存器将会自动增加;反之,(R|E)SI寄存器将会自动减少。
一旦发现完全匹配的字符串,则跳转到Label_FileName_Found处执行;如果没有找到,那么就执行其后的Label_No_LoaderBin模块,进而在屏幕上显示提示信息,通知用户引导加载程序不存在。
特别注意,因为FAT12文件系统的文件名是不区分大小写字母的,即使将小写字母命名的文件复制到FAT12文件系统内,文件系统也会为其创建大写字母的文件名和目录项。而小写字母文件名只作为其显示名,真正的数据内容皆保存在大写字母对应的目录项。所以这里应该搜索大写字母的文件名字符串。
- ;======= 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 $
这段代码借助BIOS中断处理程序INT 10h ,将字符串ERROR :No LOADER Found显示到屏幕的第1行第0列上。
- ;======= 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表项占用12 bit,即每三个字节存储两个FAT表项,由此看来,FAT表项的存储位置是具有奇偶性的。使用Func_GetFATEntry模块可根据当前FAT表项索引出下一个FAT表项,该模块的寄存器参数说明如下。
模块Func_GetFATEntry 功能:根据当前FAT表项索引出下一个FAT表项。
这段程序首先会保存FAT表项号,并将奇偶标志变量(变量[odd])置0。因为每个FAT表项占1.5 B,所以将FAT表项乘以3除以2(扩大1.5倍)来判读余数的奇偶性并保存在[odd]中(奇数为1,偶数为0 ),再将计算结果除以每扇区字节数,商值为FAT表项的偏移扇区号,余数值为FAT表项在扇区中的偏移位置。接着,通过Func_Readonesector模块连续读入两个扇区的数据,此举的目的是为了解决FAT表项横跨两个扇区的问题。最后,根据奇偶标志变量进一步处理奇偶项错位问题,即奇数项向右移动4位。有能力的读者可自行将FAT12文件系统替换为FAT16文件系统,这样可以简化FAT表项的索引过程。
在完成Func_ReadOnesector和Func_GetFATEntry模块后,就可借助这两个模块把loader.bin文件内的数据从软盘扇区读取到指定地址中。代码清单3-10实现了从FAT12文件系统中加载loader.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 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程序的起始簇号计算出其对应的扇区号。为了增强人机交互效果,此处还使用BIOS中断服务程序INT 10h在屏幕上显示一个字符' .'。接着,每读入一个扇区的数据就通过Func_GetFATEntry模块取得下一个FAT表项,并跳转至Label_Go_on_Loading_File处继续读入下一个簇的数据,如此往复,直至Func_GetFATEntry模块返回的FAT表项值是offfh为止。当loader.bin文件的数据全部读取到内存后,跳转至Label_File_Loaded处准备执行loader.bin程序。
这段代码使用了BIOS中断服务程序INT10h的主功能号AH=0Eh在屏幕上显示一个字符。详细寄存器参数说明如下。
INT 10h,AH=OEh 功能:在屏幕上显示一个字符。
- ;======= 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
这三个变量用于保存程序运行时的临时数据,上文已经讲解了它们的使用过程,此处不再过多讲述。
上述字符串均是屏幕上显示的日志信息。值得说明的是,NASM编译器中的单引号与双引号作用相同,并非如标准C语言中规定的:双引号会在字符串结尾处自动添加字符· \0',而在NASM编译器中必须自行添加。不过,本程序使用的BIOS中断服务程序必须明确提供显示的字符串的长度,不需要判读字符串结尾处的字符'\0 '。
目前,我们还未进入Loader引导加载程序的开发环节,所以在Label_File_Loaded处使用代码jmp s,让程序死循环在此处。
boot.am
- org 0x7c00
-
- BaseOfStack equ 0x7c00
- BaseOfLoader equ 0x1000
- OffsetOfLoader equ 0x00
- RootDirSectors equ 14
- SectorNumOfRootDirStart equ 19
- SectorNumOfFAT1Start equ 1
- 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
- BPB_RootEntCnt dw 224
- BPB_TotSec16 dw 2880
- BPB_Media db 0xf0
- BPB_FATSz16 dw 9
- BPB_SecPerTrk dw 18
- BPB_NumHeads dw 2
- BPB_HiddSec dd 0
- BPB_TotSec32 dd 0
- BS_DrvNum db 0
- 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
- 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
-
- ;======= reset floppy
-
- xor ah, ah
- xor 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
- dec word [RootDirSizeForLoop]
- mov ax, 00h
- mov es, ax
- mov bx, 8000h
- 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:
-
- 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
- jmp Lable_Search_In_Root_Dir_Begin
-
- ;======= 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 $
-
- ;======= 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 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 $
-
- ;======= 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
-
- ;======= 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
-
- ;======= 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
-
- ;=========================
-
- Label_File_Loaded:
- jmp BaseOfLoader:OffsetOfLoader
-
- ;======= fill zero until whole sector
-
- times 510 - ($ - $$) db 0
- dw 0xaa55
-
-
简单的loader.asm代码
- org 10000h
- mov ax,cs
- mov ds,ax
- mov es,ax
- mov ax,0x00
- mov ss,ax
- mov sp,0x7c00
- ;========== 显示 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 $
- ;=============
- StartLoaderMessage : db "Start Loader"
摘自:《一个64位操作系统的设计与实现》