1.首先大概说一下KolibriOS这个系统引导的过程,然后是对其bootloder.asm文件的注释分析
引导程序被加载到0x7c00后,首先向屏幕输出“Starting system”字样,然后从fat12结构中获取需要的信息,将位于软盘镜像的根目录表(第20-第33扇区)加载至0x700处,之后寻找字符串为"KERNEL MNT"的表项(KERNEL.MNT为内核文件),找到后根据该表项的信息将内核文件(KERNEL.MNT)加载至0x10000处,最后利用retf指令跳转至0x10000处。
2.bootloder.asm源文件解读:
include "lang.inc" ;lang.inc的内容是对于语言的设定,这个是在编译时用luc脚本创建出来的,也可以自己设定
;lang="en" ;这里将语言设定为英语,主要是在开机时显示
lf equ 0ah
cr equ 0dh
pos_read_tmp equ 0700h ;根目录表和fat被加载的地址
boot_program equ 07c00h ;boot被加载到的地址
seg_read_kernel equ 01000h ;内核被加载到的段地址
jmp start_program ;跳转到程序开始的地方
nop
include 'floppy1440.inc' ;在floppy1440.inc文件中包含的是1.44m软盘格式的设置,作者还提供了一些其他大小软盘的设置文件,可以根据需要修改
;include 'floppy2880.inc'
;include 'floppy1680.inc'
;include 'floppy1743.inc'
;下面是fat12文件系统的结构,这是floppy1440.inc文件的内容:
; BS_OEMName db 'KOLIBRI ' ; 启动扇区名称(必须为8字节)
; BPB_BytsPerSec dw 512 ; 每个扇区(sector)大小(必须为512字节)
; BPB_SecPerClus db 1 ; 簇(cluster)大小(必须为1个扇区)
; BPB_RsvdSecCnt dw 1 ; FAT起始位置(一般为第一个扇区)
; BPB_NumFATs db 2 ; FAT个数(必须为2)
; BPB_RootEntCnt dw 224 ; 根目录文件数最大值(一般为224项)
; BPB_TotSec16 dw 2880 ; 该磁盘大小(为2880扇区,即1440*1024/512)
; BPB_Media db 0f0h ; 磁盘类型(可移动介质,设置为0xf0)
; BPB_FATSz16 dw 9 ; 每个FAT的扇区数(设为9扇区)
; BPB_SecPerTrk dw 18 ; 一个磁道(track)的扇区数(最大为18)
; BPB_NumHeads dw 2 ; 磁头数(软盘有两个面,所以设置2)
; BPB_HiddSec dd 0 ; 隐藏扇区数,0
; BPB_TotSec32 dd 0 ; 如果BPB_ToSec16是0,由这里记录扇区数
; BS_DrvNum db 0 ; 中断13的驱动器号
; BS_Reserved db 0 ; 未使用
; BS_BootSig db 29h ; 扩展引导标签29h
; BS_VolID dd 0 ; 卷标序列号
; BS_VolLab db 'KOLIBRI ' ; 卷标(磁盘的名称)
; BS_FilSysType db 'FAT12 ' ; 文件系统类型
start_program:
xor ax, ax ;将ax清零
mov ss, ax ;设置栈底寄存器ss指向0x0
mov sp, boot_program ;设置栈顶寄存器sp指向0x7c00,即boot被夹在的地方
push ss
pop ds ;令数据段寄存器ds=0;
; print loading string
mov si, loading+boot_program
;将loading的内容在屏幕上显示出来
loop_loading:
lodsb ;lodsb指令的作用是将ds:si指向的值存入al,此外还有lodsw指令,将ds:si指向的值存入ax
or al, al ;or al,al用于判断al的值进行或运算,当为0时,置标志位zf=1,
jz read_root_directory ;zf=1后,jz就会跳转到read_root_directory
mov ah, 0eh ;调用bios中断号0x10,ah=0xe0,bx=为前景色,al=要显示的字符
mov bx, 7
int 10h
jmp loop_loading
read_root_directory:
push ss
pop es
mov ax, word [BPB_FATSz16+boot_program] ;将每个fat表的扇区数9存入ax
xor cx, cx
mov cl, byte [BPB_NumFATs+boot_program] ;fat表数2存入cl
mul cx
add ax, word [BPB_RsvdSecCnt+boot_program] ;此时ax=18,再加上boot记录占用的扇区数1,ax=19
mov word [FirstRootDirSecNum+boot_program], ax ; 将boot及文件表占用的长度存入FirstRootDirSecNum,其实就是根目录表的起始位置
mov si, ax
; - count of sectors in RootDir
mov bx, word [BPB_BytsPerSec+boot_program] ;每扇区字节数512存入bx
mov cl, 5 ; 将bx=512除以32(32=2^5),就是将ax的值右移5位,每条目录占用32字节,所以一个扇区(512 byte)有16条目录
shr bx, cl ; bx = 每个扇区的目录条数 =16
mov ax, word [BPB_RootEntCnt+boot_program] ;将根目录文件数最大值224存入ax
xor dx, dx
div bx ;被除数是ax=224,除数是bx=16,商为14,存入ax
mov word [RootDirSecs+boot_program], ax ;ax=14,存入RootDirSecs指向的内存,也就是说根目录表的长度为14扇区,从19-32
; - data start
add si, ax ; si=19+14=33,结果指向的是文件数据的起始扇区(即33*512=0x4200)
mov word [data_start+boot_program], si ;将位置存入data_start
; reading root directory
; al=count root dir sectrors !!!! TODO: al, max 255 sectors !!!!
mov ah, 2 ;ax=14,现在令ah=2,ax=0x020e
push ax
mov ax, word [FirstRootDirSecNum+boot_program] ;[FirstRootDirSecNum]=19,存入ax,作为conv_abs_to_THS的参数,ax=19
call conv_abs_to_THS ; 将扇区数(ax)转换为bios的参数(磁道号:磁头号:扇区),ch=00,cl=2,dh=,1,dl=0
pop ax ;ax=0x20e
mov bx, pos_read_tmp ; pos_read_tmp=700h,es:bx=0x0:0x700=0x700
call read_sector ;将根目录表的14个扇区读到0x700处
mov si, bx ;bx=0x700
mov ax, [RootDirSecs+boot_program] ;将根目录表长度14存入ax
mul word [BPB_BytsPerSec+boot_program] ;[BPB_BytsPerSec]=512(扇区大小512字节),ax=ax*512=14*512=0x1c00
add ax, si ; ax=ax+si=0x1c00+0x700=0x2300,ax指向根目录表的结束位置
; 在根目录表中寻找内核文件
loop_find_dir_entry:
push si
mov cx, 11
mov di, kernel_name+boot_program ;[kernel_name]='KERNEL MNT'
rep cmpsb ;cmpsb是字符串比较指令,将es:si指向的数据与es:di指向的数据进行比较,rep用来重复后面的cmpsb指令,每进行一次,cx-1,直到cx=0
;si=0x700
pop si
je found_kernel_file ;如果找到'KERNEL MNT'则跳转到found_kernel_file
add si, 32 ; 否则令si+32,即偏移到下一条目录
cmp si, ax ; 判断是否到目录表文件尾。当si>=ax,cf=0,jb的跳转条件是cf=1,所以会退出循环
jb loop_find_dir_entry
;下面是提示错误信息
file_error_message:
mov si, error_message+boot_program
loop_error_message:
lodsb
or al, al
jz freeze_pc
mov ah, 0eh
mov bx, 7
int 10h
jmp loop_error_message
freeze_pc:
jmp $ ; 打印错误字符串后,陷入死循环
; === KERNEL FOUND. LOADING... ===
;kernel内核文件
found_kernel_file:
mov bp, [si+01ah] ;si=700h,si+01ah=71ah,bp=[71ah],0x1a=26
;在目录表项中,偏移量=0x1a的位置是内核文件在磁盘中的储存位置,(单位是簇,簇的值从2开始,就是说
;如果这个地方等于2,说明内核文件在文件数据区的第一个簇(33*512=0x4200处),在这里一个簇和一个扇区长度相等)
; <diamond>
mov [cluster1st+boot_program], bp ; [cluster1st]指向文件内核的第一个簇
; <\diamond>
; reading first FAT table
mov ax, word [BPB_RsvdSecCnt+boot_program] ; fat1的位置存入ax,ax=1
call conv_abs_to_THS ; 返回ch=0,cl=2,ax=0,bh=0,bl=0
mov bx, pos_read_tmp ; bx=700
mov ah, 2 ; ah=2 (read)
mov al, byte [BPB_FATSz16+boot_program] ; al=9=一个根目录表占的扇区数
call read_sector ; 把第一个fat表读到0x700
jc file_error_message ; 出错则跳到错误提示
mov ax, seg_read_kernel ;seg_read_kernel=1000h,ax=1000h
mov es, ax ;es=1000h
xor bx, bx ; es:bx = 1000h:0000h
; reading kernel file
loop_obtains_kernel_data:
; read one cluster of file
call obtain_cluster ;将内核文件的第一个扇区读取到es:bx = 1000h:0000h=0x10000
jc file_error_message ; 当cf=1,说明读取失败
; add one cluster length to segment:offset
push bx
mov bx, es ;bx=1000h
mov ax, word [BPB_BytsPerSec+boot_program] ; ax=512=一个扇区的大小=512字节
movsx cx, byte [BPB_SecPerClus+boot_program] ; cx=1=一个簇的大小=1扇区
mul cx ; ax=cx*cx=512*1=512
shr ax, 4 ; ax=ax/16=32=20h
add bx, ax ;bx=bx+ax=1000h+20h=1020h
mov es, bx ;es=1020h
pop bx ;bx=0
mov di, bp ;di=bp=2=内核文件的簇号
shr di, 1 ;di=1,cf=0
pushf
add di, bp ; di = bp * 1.5==3
add di, pos_read_tmp ; di=700h+di=700h+3=703h
mov ax, [di] ; 将fat1的第4-5个字节读入,fat表的前3个字节没用
popf
jc move_4_right ;有进位跳转到move_4_right
and ax, 0fffh ;清掉ax前4位
jmp verify_end_sector
move_4_right:
mov cl, 4
shr ax, cl
verify_end_sector:
cmp ax, 0ff8h ;当值大于等于ff8h时,说明这是这个文件的最后一个簇,ff7h代表该簇损坏
jae execute_kernel ;判断这是内核文件的最后一个簇时,跳转到execute_kernel
mov bp, ax ;簇号是连续的,如果一个文件占了4个簇,第一个簇号为2,则它对应的fat表项为03h 40h 00h f5h f8h(12位一项,每项指向该文件下一个正确的簇)
jmp loop_obtains_kernel_data ;继续读取内核文件
execute_kernel:
; <diamond>
mov ax, 'KL' ;ax='KL'
push 0
pop ds ;ds=0(数据段寄存器)
mov si, loader_block+boot_program
; </diamond>
push word seg_read_kernel ;seg_read_kernel=1000h
push word 0
retf ; jmp far 1000:0000
;retf相当于pop ip ;pop cs;利用retf实现段间跳转
;------------------------------------------
; loading cluster from file to es:bx
obtain_cluster:
; bp=簇号(假设kernel文件的位置在文件数据区第一个扇区,则bp=2)
; cf = 0 -> 读取成功
; cf = 1 -> 读取失败
; print one dot
push bx
mov ax, 0e2eh ; ah=0eh (teletype), al='.'
xor bh, bh
int 10h
;功能描述:在Teletype模式下显示字符
;入口参数:AH=0EH
;AL=字符
;BH=页码
;BL=前景色(图形模式)
;出口参数:无
pop bx
writesec:
;将簇号变成扇区号
mov ax, bp ; ax=bp=2
sub ax, 2 ;ax=0
xor dx, dx ;dx=0
mov dl, byte [BPB_SecPerClus+boot_program] ;dl=簇的大小=1(扇区)
mul dx ;ax=0
add ax, word [data_start+boot_program] ;ax=ax+文件数据区起始位置=0x4200
call conv_abs_to_THS ; 将位置转换为bios可以处理的参数
patchhere:
mov ah, 2 ; ah=2 (read)
mov al, byte [BPB_SecPerClus+boot_program] ; 读取一个扇区
call read_sector
retn ;retn是段内跳转,近返回
;------------------------------------------
;------------------------------------------
; read sector from disk
read_sector:
push bp
mov bp, 20 ;尝试20次,因为软盘读取时可能会发生错误,所以会多尝试几次
newread:
dec bp ;dec指令将bp减1,
jz file_error_message ;等于0则跳转到file_error_message
push ax bx cx dx ;ax=0x020e,dx=0x0100,cx=0x0002,bx=0x0700;es=0;ch=00,cl=2,dh=,1,dl=0
int 13h
;参数:ah=2;作用:读扇区
;al=扇区数
;ch=柱面
;cl=扇区
;dh=磁头
;dl=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
;es:bx=缓冲区的地址
;出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,AH=状态代码,其定义如下:
;00H — 无错 01H — 非法命令
;02H — 地址目标未发现 03H — 磁盘写保护(软盘)
;04H — 扇区未发现 05H — 复位失败(硬盘)
;06H — 软盘取出(软盘) 07H — 错误的参数表(硬盘)
;08H — DMA越界(软盘) 09H — DMA超过64K界限
;0AH — 错误的扇区标志(硬盘) 0BH — 错误的磁道标志(硬盘)
;0CH — 介质类型未发现(软盘) 0DH — 格式化时非法扇区号(硬盘)
;0EH — 控制数据地址目标被发现(硬盘) 0FH — DMA仲裁越界(硬盘)
;10H — 不正确的CRC或ECC编码 11H — ECC校正数据错(硬盘)
pop dx cx bx ax
jc newread
pop bp
retn
;------------------------------------------
; convert abs. sector number (AX) to BIOS T:H:S
; sector number = (abs.sector%BPB_SecPerTrk)+1
; pre.track number = (abs.sector/BPB_SecPerTrk)
; head number = pre.track number%BPB_NumHeads
; track number = pre.track number/BPB_NumHeads
; Return: cl - sector number
; ch - track number
; dl - drive number (0 = a:)
; dh - head number
conv_abs_to_THS:
push bx ;传入ax=19
mov bx, word [BPB_SecPerTrk+boot_program] ;将磁道扇区数18存入bx,bx=18
xor dx, dx
div bx ;令ax/bx,19/18=1-----1,ax=1,dx=1
inc dx ;dx=2;
mov cl, dl ; cl=2 =扇区数
mov bx, word [BPB_NumHeads+boot_program] ;将磁头数2存入bx
xor dx, dx
div bx ;1/2=0-----1,ax=0,dx=1
; !!!!!!! ax = 磁道号, dx = 磁头号
mov ch, al ; ch=磁道号=0
xchg dh, dl ; xchg交换两个寄存器的值,交换前dh=0,dl=1,交换后:dh=1,dl=0
mov dl, 0 ; dl=0 (drive 0 (a:)),即bios中断调用中驱动器0
pop bx
retn
;------------------------------------------
if lang eq sp
loading db cr,lf,'Iniciando el sistema ',00h
else
loading db cr,lf,'Starting system ',00h
end if
error_message db 13,10
kernel_name db 'KERNEL MNT ?',cr,lf,00h
FirstRootDirSecNum dw ?
RootDirSecs dw ?
data_start dw ?
; <diamond>
write1st:
push cs
pop ds
mov byte [patchhere+1+boot_program], 3 ; change ah=2 to ah=3
mov bp, [cluster1st+boot_program]
push 1000h
pop es
xor bx, bx
call writesec
mov byte [patchhere+1+boot_program], 2 ; change back ah=3 to ah=2
retf
cluster1st dw ?
loader_block:
db 1
dw 0
dw write1st+boot_program
dw 0
; <\diamond>
times 0x1fe-$ db 00h
db 55h,0aah ;boot signature
关于fat12文件系统的参考(侵删):https://yq.aliyun.com/wenji/275247