loader.asm源码分析

作用:通过int 15h中断获取内存信息,调用的结果是BIOS会填充es:di指向的一块内存,此结构成为ARDS(地址范围描述符结构):

    mov ebx, 0          ; ebx = 后续值, 开始时需为 0
    mov di, _MemChkBuf      ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure)
.MemChkLoop:
    mov eax, 0E820h     ; eax = 0000E820h 查询系统地址映射
    mov ecx, 20         ; ecx = 地址范围描述符结构的大小,20字节
    mov edx, 0534D4150h     ; edx = 'SMAP'
    int 15h         ; int 15h,通过这个中断获取内存信息
    jc  .MemChkFail
    add di, 20             ;因为每个ARDS占20字节,所以自增20指向下一个空白位置
    inc dword [_dwMCRNumber]    ; dwMCRNumber = ARDS 的个数
    cmp ebx, 0  ;int 15h将上次调用的计数值填充到ebx中,如果为0表示探测结束,否则继续探测。
    jne .MemChkLoop
    jmp .MemChkOK
.MemChkFail:
    mov dword [_dwMCRNumber], 0
.MemChkOK:

ReaderSector:

作用:从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中。

顾名思义,ReaderSector函数的作用就是读扇区。这个函数最最重要的一条语句就是int 13h,这是一个中断服务程序,这里仅仅介绍当ah=2时服务程序的功能。

功能描述:读扇区
入口参数:AH=02H
AL=扇区数
CH=柱面
CL=扇区
DH=磁头
DL=驱动器,00H ~ 7FH:软盘;80H ~ 0FFH:硬盘
ES:BX=缓冲区的地址
出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明

    push    bp
    mov bp, sp
    sub esp, 2          ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

    mov byte [bp-2], cl
    push    bx          ; 保存 bx
    mov bl, [BPB_SecPerTrk] ; bl: 除数
    div bl          ; y 在 al 中, z 在 ah 中
    inc ah          ; z ++
    mov cl, ah          ; cl <- 起始扇区号
    mov dh, al          ; dh <- y
    shr al, 1           ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov ch, al          ; ch <- 柱面号
    and dh, 1           ; dh & 1 = 磁头号
    pop bx          ; 恢复 bx
    ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
    mov dl, [BS_DrvNum]     ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov ah, 2           ; 读
    mov al, byte [bp-2]     ; 读 al 个扇区
    int 13h
    jc  .GoOnReading        ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    add esp, 2
    pop bp

    ret

在根目录区寻找KERNEL.BIN

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp word [wRootDirSizeForLoop], 0   ; ┓
    jz  LABEL_NO_KERNELBIN      ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN
    dec word [wRootDirSizeForLoop]  ; ┛
    mov ax, BaseOfKernelFile
    mov es, ax          ; es <- BaseOfKernelFile
    mov bx, OffsetOfKernelFile  ; bx <- OffsetOfKernelFile  于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
    mov ax, [wSectorNo]     ; ax <- Root Directory 中的某 Sector 号
    mov cl, 1
    call    ReadSector ;把一个扇区读到了BaseOfKernelFile:OffsetOfKernelFile

    mov si, KernelFileName  ; ds:si -> "KERNEL  BIN"
    mov di, OffsetOfKernelFile  ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+????
    cld
    mov dx, 10h  ;dx作为循环控制,控制一个扇区读的最大次数,因为一个扇区512字节,一个条目占32字节,所以最多读16次!
LABEL_SEARCH_FOR_KERNELBIN:
    cmp dx, 0                   ; ┓
    jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR  ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector
    dec dx                  ; ┛
    mov cx, 11 ;"KERNEL  BIN"共11个字节
LABEL_CMP_FILENAME:
    cmp cx, 0           ; ┓
    jz  LABEL_FILENAME_FOUND    ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到
    dec cx          ; ┛
    lodsb               ; ds:si -> al
    cmp al, byte [es:di]    ; if al == es:di
    jz  LABEL_GO_ON
    jmp LABEL_DIFFERENT
LABEL_GO_ON:
    inc di
    jmp LABEL_CMP_FILENAME  ;   继续循环

LABEL_DIFFERENT:
    and di, 0FFE0h      ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数
    add di, 20h         ;     ┃
    mov si, KernelFileName  ;     ┣ di += 20h  下一个目录条目(一个目录条目32字节)
    jmp LABEL_SEARCH_FOR_KERNELBIN;   ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add word [wSectorNo], 1
    jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_KERNELBIN:
    mov dh, 2           ; "No KERNEL."
    call    DispStrRealMode     ; 显示字符串
    jmp $           ; 没有找到 KERNEL.BIN, 死循环在这里

LABEL_FILENAME_FOUND:
FAT12:
loader.asm源码分析_第1张图片
  • 引导扇区里放了一个短跳转指令(jmp LABEL_START)和一些与FAT设置有关的参数(如每扇区的字节数、每簇扇区数...)
  • 根目录区:由若干目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt(在引导扇区定义)个。一个条目占32字节,主要定义了文件的属性、名称、大小、日期以及在磁盘中的位置。
loader.asm源码分析_第2张图片
条目结构
  • FAT表:存放FAT项(FAT Entry),每个FAT项12位,值代表文件的下一个簇号,但如果值大于或等于**0xFF8,则表示当前簇是本文件的最后一个簇。FAT1和FAT2完全一样,多一个备份。

这里说明一下代码中几个常数设置的原因:

  • mov dx, 10h :一个扇区512字节,一个条目占32字节,所以一个扇区里最多存放16个条目。dx中的值用作循环控制,控制读一个扇区时读条目的最大值。
  • mov cx, 11:条目中存放文件名,"KERNEL BIN",占11字节。

加载KERNEL:

调用ReadSector函数把Kernel加载到BaseOfKernelFile:OffsetOfKernelFile

LABEL_FILENAME_FOUND:           ; 找到 KERNEL.BIN 后便来到这里继续
    mov ax, RootDirSectors    ;RootDirSectors:根目录占用空间
    and di, 0FFF0h      ; di -> 当前条目的开始

    push    eax
    mov eax, [es : di + 01Ch]       ; ┓条目[01Ch]处保存着文件大小信息
    mov dword [dwKernelSize], eax   ; ┛保存 KERNEL.BIN 文件大小
    pop eax

    add di, 01Ah        ; 条目[01Ah]处保存此条目对应的开始簇号,因为这里设置一簇一扇区,故开始扇区号等于开始簇号。
    mov cx, word [es:di]
    push    cx          ; 保存此 Sector 在 FAT 中的序号
    add cx, ax
    add cx, DeltaSectorNo   ;文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo,这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
    mov ax, BaseOfKernelFile
    mov es, ax          ; es <- BaseOfKernelFile
    mov bx, OffsetOfKernelFile  ; bx <- OffsetOfKernelFile  于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
    mov ax, cx          ; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
    push    ax          ; ┓
    push    bx          ; ┃
    mov ah, 0Eh         ; ┃ 每读一个扇区就在 "Loading  " 后面打一个点, 形成这样的效果:
    mov al, '.'         ; ┃
    mov bl, 0Fh         ; ┃ Loading ......
    int 10h         ; ┃
    pop bx          ; ┃
    pop ax          ; ┛

    mov cl, 1
    call    ReadSector    
    pop ax          ; 取出此 Sector 在 FAT 中的序号
    call    GetFATEntry    ;找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
    cmp ax, 0FFFh      ;到文件的最后一个簇
    jz  LABEL_FILE_LOADED
    push    ax          ; 保存 Sector 在 FAT 中的序号
    mov dx, RootDirSectors
    add ax, dx
    add ax, DeltaSectorNo
    add bx, [BPB_BytsPerSec]
    jmp LABEL_GOON_LOADING_FILE

LABEL_FILE_LOADED:
    call    KillMotor       ; 关闭软驱马达
    mov dh, 1           ; "Ready."
    call    DispStrRealMode     ; 显示字符串

跳入保护模式:

; 加载 GDTR
    lgdt    [GdtPtr]

; 关中断,保护模式下中断处理的机制是不同的,不关中断会出现错误
    cli

; 打开地址线A20,8086有20根地址线,开机时置A20=0可以保证即使现代计算机有很多根地址线,在实模式下寻址范围还是和8086的结果一样。
    in  al, 92h
    or  al, 00000010b
    out 92h, al

; 准备切换到保护模式,寄存器cr0第一位是PE位,置0,CPU运行在实模式下。置1,CPU运行在保护模式下。
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

; 真正进入保护模式
    jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START)

以下代码运行在保护模式下


由实模式跳到此处:

初始化段寄存器,调用DispMemInfo、SetupPaging、InitKernel

LABEL_PM_START:
    mov ax, SelectorVideo
    mov gs, ax              ;gs指向显存 
    mov ax, SelectorFlatRW
    mov ds, ax              
    mov es, ax
    mov fs, ax
    mov ss, ax              ;ds,es,fs,ss指向SelectorFlatRW
    mov esp, TopOfStack     ;esp指向栈顶

    push    szMemChkTitle    ;szMemChkTitle:"BaseAddrL BaseAddrH LengthLow LengthHigh   Type"
    call    DispStr
    add esp, 4

    call    DispMemInfo
    call    SetupPaging

    mov ah, 0Fh             ; 0000: 黑底    1111: 白字
    mov al, 'P'
    mov [gs:((80 * 0 + 39) * 2)], ax    ; 屏幕第 0 行, 第 39 列。

    call    InitKernel

显示内存信息:

DispMemInfo:
    push    esi
    push    edi
    push    ecx

    mov esi, MemChkBuf
    mov ecx, [dwMCRNumber]  ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop:                  ;{
    mov edx, 5          ;   for(int j=0;j<5;j++)    // 每次得到一个ARDS中的成员,共5个成员
    mov edi, ARDStruct      ;   {           // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1:                 ;
    push    dword [esi]     ;
    call    DispInt         ;       DispInt(MemChkBuf[j*4]); // 显示一个成员
    pop eax         ;
    stosd               ;       ARDStruct[j*4] = MemChkBuf[j*4];
    add esi, 4          ;
    dec edx         ;
    cmp edx, 0          ;
    jnz .1          ;   }
    call    DispReturn      ;   printf("\n");
    cmp dword [dwType], 1   ;   if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
    jne .2          ;   {
    mov eax, [dwBaseAddrLow]    ;
    add eax, [dwLengthLow]  ;
    cmp eax, [dwMemSize]    ;       if(BaseAddrLow + LengthLow > MemSize)
    jb  .2          ;
    mov [dwMemSize], eax    ;           MemSize = BaseAddrLow + LengthLow;
.2:                 ;   }
    loop    .loop           ;}
                    ;
    call    DispReturn      ;printf("\n");
    push    szRAMSize       ;
    call    DispStr         ;printf("RAM size:");
    add esp, 4          ;
                    ;
    push    dword [dwMemSize]   ;
    call    DispInt         ;DispInt(MemSize);
    add esp, 4          ;

    pop ecx
    pop edi
    pop esi
    ret

启动分页机制:

SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor edx, edx
    mov eax, [dwMemSize]
    mov ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
    div ebx
    mov ecx, eax    ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
    test    edx, edx
    jz  .no_remainder
    inc ecx     ; 如果余数不为 0 就需增加一个页表
.no_remainder:
    push    ecx     ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 首先初始化页目录
    mov ax, SelectorFlatRW
    mov es, ax
    mov edi, PageDirBase    ; 此段首地址为 PageDirBase:equ    200000h ,页目录开始地址
    xor eax, eax
    mov eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
    stosd                   ;将 EAX 存储到地址 ES:EDI
    add eax, 4096       ; 为了简化, 所有页表在内存中是连续的.一个页表大小4KB,4096字节
    loop    .1

    ; 再初始化所有页表
    pop eax         ; 页表个数
    mov ebx, 1024       ; 每个页表 1024 个 PTE
    mul ebx              ;PTE个数 = 页表个数 * 1024
    mov ecx, eax        
    mov edi, PageTblBase    ; 此段首地址为 PageTblBase:equ    201000h ,页表开始地址:
    xor eax, eax
    mov eax, PG_P  | PG_USU | PG_RWW
.2:
    stosd
    add eax, 4096       ; 每一页指向 4K 的空间
    loop    .2

    mov eax, PageDirBase
    mov cr3, eax
    mov eax, cr0
    or  eax, 80000000h
    mov cr0, eax
    jmp short .3
.3:
    nop

    ret
; 分页机制启动完毕 ----------------------------------------------------------

分页机制:

逻辑地址-->分段机制-->线性地址-->分页机制-->物理地址

  • 页:就是一块内存,大小可以是4K、1M等等
  • 开关位于寄存器cr0的PG位,PG=1分页机制开启

寻址方式:


loader.asm源码分析_第3张图片
  • 页目录表:大小为4KB,储存在一个物理页中,每个表项4字节长,共1024个表项,每个表项对应第二级的一个页表。
  • 页表:1024项,每项对应一个物理页。

InitKernel:

InitKernel:
        xor   esi, esi
        mov   cx, word [BaseOfKernelFilePhyAddr+2Ch];`. ecx <- pELFHdr->e_phnum
        movzx ecx, cx                               ;/
        mov   esi, [BaseOfKernelFilePhyAddr + 1Ch]  ; esi <- pELFHdr->e_phoff
        add   esi, BaseOfKernelFilePhyAddr;esi<-OffsetOfKernel+pELFHdr->e_phoff
.Begin:
        mov   eax, [esi + 0]
        cmp   eax, 0                      ; PT_NULL
        jz    .NoAction
        push  dword [esi + 010h]    ;size ;`.
        mov   eax, [esi + 04h]            ; |
        add   eax, BaseOfKernelFilePhyAddr; | memcpy((void*)(pPHdr->p_vaddr),
        push  eax           ;src  ; |      uchCode + pPHdr->p_offset,
        push  dword [esi + 08h]     ;dst  ; |      pPHdr->p_filesz;
        call  MemCpy                      ; |
        add   esp, 12                     ;/
.NoAction:
        add   esi, 020h                   ; esi += pELFHdr->e_phentsize
        dec   ecx
        jnz   .Begin

        ret

loader.asm源码分析_第4张图片

你可能感兴趣的:(loader.asm源码分析)