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

10-07 10:44