十5、轉入內核

   在 linux 下編譯 ELF 格式,和書上同樣,簡單多了!
linux

    首先是 ELF 格式的幾個結構:數組

; ELF.inc
; ELF 文件格式說明及結構
; 四彩
; 2015-12-08


%ifndef _ELF_INC
%define _ELF_INC


; ELF 格式可執行文件結構:
; ELF 文件頭(Elf header)
; 程序頭表(program header table)
; 第 1 段(segment)
; 第 2 段
; 其餘段
; 節頭表(section header table)


; ========================================================================================
; ----------------------------------------------------------------------------------------
; ELF 文件頭結構
; e_type 字段可取值及說明
ET_NONE             equ  0                  ; 未指明文件類型
ET_REL              equ  1                  ; 重定位文件
ET_EXEC             equ  2                  ; 可執行文件
ET_DYN              equ  3                  ; 動態鏈接庫

EI_NIDENT           equ     16
struc Tag_ELF_Header
;    字節名                                   說明
    .e_ident        resb EI_NIDENT          ; ELF 文件標識(魔數、字長、字節序、版本)
    .e_type         resw 1                  ; 文件類型
    .e_machine      resw 1                  ; CPU 屬性要求
    .e_version      resd 1                  ; 版本
    .e_entry        resd 1                  ; 程序入口地址
    .e_phoff        resd 1                  ; 程序頭表在文件中的偏移量
    .e_shoff        resd 1                  ; 節頭表在文件中的偏移量
    .e_flags        resd 1                  ; 處理器特定標誌,IA32 爲 0
    .e_ehsize       resw 1                  ; 文件頭長度
    .e_phentsize    resw 1                  ; 程序頭表中一個條目的長度
    .e_phnum        resw 1                  ; 程序頭表條目數目
    .e_shentsize    resw 1                  ; 節頭表中一個條目的長度
    .e_shnum        resw 1                  ; 節頭表條目個數
    .e_shstrndx     resw 1                  ; 節頭表字符索引
endstruc


; ----------------------------------------------------------------------------------------
; 程序頭結構
; 描述一個段在文件中的位置、大小以及它被放進內存後所在的位置和大小。
; p_type 字段可取值及說明
PT_NULL             equ  0                  ; 未使用段
PT_LOAD             equ  1                  ; 可裝載段
struc Tag_Program_Header
;    字節名                                   說明
    .p_type         resd 1                  ; 段的類型
    .p_offset       resd 1                  ; 段在文件中的偏移
    .p_vaddr        resd 1                  ; 段在內存中的虛擬地址
    .p_paddr        resd 1                  ; 段的物理地址(在物理內存定位的系統中使用)
    .p_filesz       resd 1                  ; 段在文件中的長度
    .p_memsz        resd 1                  ; 段在內存中的長度
    .p_flags        resd 1                  ; 段的標記
    .p_align        resd 1                  ; 段在文件及內存的對齊標記
endstruc


; ----------------------------------------------------------------------------------------
; 節頭結構
struc Tag_Section_Header
;    字節名                                   說明
    .sh_name        resd 1                  ; 小節名在字符表中的索引
    .sh_type        resd 1                  ; 小節的類型
    .sh_flags       resd 1                  ; 小節屬性
    .sh_addr        resd 1                  ; 小節在運行時的虛擬地址
    .sh_offset      resd 1                  ; 小節的文件偏移
    .sh_size        resd 1                  ; 小節的大小(字節)
    .sh_link        resd 1                  ; 連接的另一小節的索引
    .sh_info        resd 1                  ; 附加的小節信息
    .sh_addralign   resd 1                  ; 小節的對齊
    .sh_entsize     resd 1                  ; 一些sections保存着一張固定大小入口的表。就像符號表
endstruc
; ****************************************************************************************


%endif

    const.inc 里加上程序入口物理地址:數據結構

; const.inc
; 常量
; 四彩
; 2015-12-08

%ifndef _CONSTANT_INC
%define _CONSTANT_INC

; ========================================================================================
; 內存中 0x0500 ~ 0x7BFF 段(29.75 KB)和 0x7E00 ~ 0x9FBFF 段(607.5 KB)可自由使用。
; 引導扇區段在加載完 Loader 後也可以使用,即整個 0x500 ~ 0x9FBFF 段(637.25 KB)均可自由使用。
;
STACKSIZE           equ 0x1000          ; 堆棧大小(4K)

SEGMENTOFGPARAM     equ 0x50            ; 存放全局數據的段基址
SEGMENTOFTEMP       equ 0x7E            ; 臨時數據被加載到內存的段基址
SEGMENTOFLOADER     equ 0x9000          ; LOADER.SYS 被加載到內存的段基址
SEGMENTOFKERNEL     equ 0x8000          ; KERNEL.EXE 被加載到內存的段基址

KERNELENTRYPHYADDR  equ 0x30400         ; kernel 的程序入口的物理地址
                                        ; 必須與 Makefile 中 -Ttext 參數的值相等
; ****************************************************************************************

%endif

   FAT12.inc 裏把 SearchFile 函數中比較字符串部分單獨做爲一個函數提出來了—— SearchFile 實在太長了:ide

; FAT12.inc
; FAT12 文件系統常量、宏及子函數定義
; 四彩
; 2015-12-08


%ifndef _FAT12_INC
%define _FAT12_INC

; ========================================================================================
BYTESPERSECTOR              equ 512         ; 每扇區字節數

IFATFIRSTSECTOR             equ 1           ; FAT 表的起始邏輯扇區號
IROOTDIRECTORYFIRSTSECTOR   equ 19          ; 根目錄區的起始邏輯扇區號
IDATAFIRSTSECTOR            equ 33          ; 數據區的起始邏輯扇區號
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系統的引導扇區頭部格式宏
; 調用格式:FAT12Head  RealEntry, OEMName, VolLab
;           RealEntry   : 入口地址標籤
;           OEMName     : 廠商名稱(8 字節長,不夠的填空格)
;           VolLab      : 卷標(11 字節長,不夠的填空格)
%macro FAT12Head 3
;   名稱                              偏移 長度 說明                      3.5英寸軟盤內容
    jmp %1                          ; 0x00  3   跳轉指令,指向程序入口     jmp RealEntry
    nop
    BS_OEMName      db %2           ; 0x03  8   廠商名稱                   自行定義
    BPB_BytsPerSec  dw 512          ; 0x0B  2   每扇區字節數               512
    BPB_SecPerClus  db 1            ; 0x0D  1   每簇扇區數                 1
    BPB_RsvdSecCnt  dw 1            ; 0x0E  2   保留扇區數                 1
    BPB_NumFATs     db 2            ; 0x10  1   FAT表份數                  2
    BPB_RootEntCnt  dw 224          ; 0x11  2   根目錄中最多容納的文件數   224
    BPB_TotSec16    dw 2880         ; 0x13  2   扇區總數 (FAT十二、16)     2880
    BPB_Media       db 0xF0         ; 0x15  1   介質描述符                 0xF0
    BPB_FATSz16     dw 9            ; 0x16  2   每一個FAT表所佔的扇區數      9
    BPB_SecPerTrk   dw 18           ; 0x18  2   每磁道扇區數               18
    BPB_NumHeads    dw 2            ; 0x1A  2   磁頭數                     2
    BPB_HiddSec     dd 0            ; 0x1C  4   隱藏扇區數                 0
    BPB_TotSec32    dd 2880         ; 0x20  4   扇區總數(FAT32)          2880
    BS_DrvNum       db 0            ; 0x24  1   磁盤驅動器號               0
    BS_Reserved1    db 0            ; 0x25  1   保留(供NT使用)           0
    BS_BootSig      db 0x29         ; 0x26  1   擴展引導標記               0x29
    BS_VolD         dd 0            ; 0x27  4   卷標序列號                 0
    BS_VolLab       db %3           ; 0x2B  11  卷標                       自行定義
    BS_FileSysType  db 'FAT12'      ; 0x36  8   文件系統類型名             FAT12
                                    ; 0x3E  448 引導代碼及其餘填充字符
                                    ; 0x1FE 2   結束標誌                   0xAA55
;
; BPB:BIOS Parameter Block,BIOS 參數塊
; BS:Boot Sector,引導扇區
%endmacro
; ****************************************************************************************


; ========================================================================================
; 目錄表項結構
struc DirectoryItem
;    字段名                           偏移 長度 說明
    .DIR_Name       resb    11      ; 0x00  11  文件名 8 + 3(大寫,不夠長度末尾填空格)
    .DIR_Attr       resb    1       ; 0x0B  1   文件屬性
                    resb    10      ; 0x0C  10  保留
    .DIR_WrtTime    resw    1       ; 0x16  2   最後修改時間
    .DIR_WrtDate    resw    1       ; 0x18  2   最後修改日期
    .DIR_FstClus    resw    1       ; 0x1A  2   此條目對應的開始簇號(即 FAT 表項序號)
    .DIR_FileSize   resd    1       ; 0x1C  4   文件大小
endstruc
; ****************************************************************************************


; ========================================================================================
; 從軟盤A(FAT12 格式)裝載文件到內存用到的子函數
%macro IncludeFAT12Function 1
; 調用格式 :IncludeFAT12Function SEGMENTOFTEMP
;     參數 :SEGMENTOFTEMP = 暫存臨時數據的段基址
;
; ----------------------------------------------------------------------------------------
; 函數功能:查找文件位置
; 入口參數:ds : si = 文件名(字符串)地址
; 出口參數:ax = 起始 FAT 表項序號
SearchFile:
    push bp
    mov bp, sp
    sub sp , 2 * 2                          ; 爲局部變量分配空間

    push es
    push cx
    push bx

    push %1                                 ; 存放根目錄數據要用到 es
    pop es

    ; 待讀取的根目錄區邏輯扇區號
    mov word[bp - 2], IROOTDIRECTORYFIRSTSECTOR
    ; 待查找的根目錄區扇區數
    mov word[bp - 2 * 2], IDATAFIRSTSECTOR - IROOTDIRECTORYFIRSTSECTOR

    ; 逐個扇區尋找
.NextSector:
    mov ax, [bp - 2]
    xor bx, bx
    call Read1Sector

    ; 逐項匹配
    mov cx, 16                  ; 一個扇區的總項數 = [BPB_BytsPerSec] / DirectoryItem_size
.ThisSector:
    mov ax, 11                              ; 比較文件名
    call StrnCmp
    cmp ax, 0
    jz .Found

    add bx, DirectoryItem_size              ; 下一個表項
    loop .ThisSector

    ; 判斷是否讀完根目錄區全部扇區:讀完說明沒找到,沒讀完就繼續下一個
    dec word[bp - 2 * 2]
    jz .NotFound
    inc word[bp - 2]
    jmp .NextSector

.NotFound:
    xor ax, ax
    jmp .Return

.Found:
    mov ax, word[es : bx + DirectoryItem.DIR_FstClus]

.Return:
    pop bx
    pop cx
    pop es

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函數功能:複製文件到內存
; 入口參數:ax = 起始 FAT 表項序號
;           es : bx = 起始內存地址
; 出口參數:無
LoadFile:
    push bp
    mov bp, sp

    push bx
    push ax

.Load:
    push bx
    push ax

    add ax, IDATAFIRSTSECTOR - 2            ; FAT 表項序號轉換爲邏輯扇區號
    call Read1Sector

    pop ax
    call GetEntryValue
    pop bx
    cmp ax, 0xFF8                           ; FAT 表項的值大於等於 0xFF8,表示文件結束
    jae .Return                             ; 未檢查壞扇區 —— 虛擬的不會壞的

    add bx, BYTESPERSECTOR
    jmp .Load

.Return:
    POP ax
    pop bx

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函數功能:取得 FAT 表中指定序號表項的值(即下一個扇區的 FAT 表項序號)
; 入口參數:ax = FAT 表項序號
; 出口參數:ax = 對應的 FAT 表項值
GetEntryValue:
    push bp
    mov bp, sp

    push es                                 ; 讀取 FAT 表時要使用 es 暫存數據
    push dx
    push cx
    push bx

    ; 計算該表項序號所在的邏輯扇區號和在該扇區的偏移量
    xor dx, dx                              ; 字節號(ax * 12 / 8)
    mov bx, 3
    mul bx
    mov bx, 2
    div bx

    mov cx, dx                              ; 保存表項序號的奇偶性(0 = 偶數,1 = 奇數)

    xor dx, dx
    mov bx, BYTESPERSECTOR
    div bx
    add ax, IFATFIRSTSECTOR                 ; 邏輯扇區號
    push dx                                 ; 保存在該扇區的偏移量

    ; 讀取連續 2 個扇區(表項可能跨扇區)
    push cx                                 ; Read1Sector 函數改變了 cx、ax
    push ax

    push %1
    pop es
    xor bx, bx
    call Read1Sector
    pop ax
    inc ax
    mov bx, BYTESPERSECTOR
    call Read1Sector
    pop cx

    ; 讀出 16 位,奇數項取高 12 位、偶數項取低 12 位(低低高高存放原則),獲得項值
    pop bx                                  ; 偏移量(上面壓進去的 dx 值)
    mov ax, [es : bx]
    jcxz .Even
    shr ax, 4
.Even:
    and ax, 0b0000111111111111              ; 奇數項高 4 位已爲 0,執行此操做值也不變

    pop bx
    pop cx
    pop dx
    pop es

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函數功能:讀取一個邏輯扇區到內存
; 入口參數:ax = 邏輯扇區號
;           es : bx = 起始內存地址
; 出口參數:ax = 同 ah = 二、int 0x13
Read1Sector:
    push bp
    mov bp, sp

    push dx
    push cx

    ; 由 LBA 計算 CHS
    mov dl, 18
    div dl
    mov ch, al
    mov dh, al
    mov cl, ah
    shr ch, 1
    inc cl
    and dh, 1

    ; 讀一個扇區
    mov ax, 0x0201
    xor dl, dl
    int 0x13

    ; 未檢讀取失敗 —— 虛擬的不會失敗

    pop cx
    pop dx

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函數功能:字符串比較
; 入口參數:ds : si = 字符串1地址
;           es : bx = 字符串2地址
;           ax = 字符串長度
; 出口參數:ax = 0:字符串相等,非 0 :不等
StrnCmp:
    push bp
    mov bp, sp

    push si
    push cx
    push bx

    mov cx, ax
    jcxz .Return

.Compare:
    lodsb
    cmp al, [es : bx]
    jnz .Return
    inc bx
    loop .Compare

.Return:
    mov ax, cx

    pop bx
    pop cx
    pop si

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函數功能:在光標當前顯示字符串,光標跟隨移動
; 入口參數:ds : si = 字符串地址
; 出口參數:無
PrintStr:
    push bp
    mov bp, sp

    push si
    push ax

    mov ah, 0x0E                            ; 功能號,0x0E:顯示一個字符,光標跟隨字符移動
.Print:
    lodsb
    cmp al, 0                               ; 字符串以 0 結尾
    je .Return
    int 0x10
    jmp .Print

.Return:
    pop ax
    pop si

    mov sp, bp
    pop bp
    ret

%endmacro
; ****************************************************************************************

%endif

    PMode.inc 把段描述符結構中第四個字段(16 位)拆成兩個字段(各 8 位),這樣看起來更清晰:函數

; PMode.inc
; 保護模式。內存分段管理機制的說明、宏及常量定義
; 四彩
; 2015-12-08


%ifndef _PROTECTEDMODE_INC
%define _PROTECTEDMODE_INC


; ========================================================================================
; ----------------------------------------------------------------------------------------
; 存儲器(Storage):存放程序和數據的器件,是用於保存信息的記憶設備。
;   存儲元(Storage Unit):也稱存儲位、記憶單元,是存放一個二進制位的單元。
;     是存儲器內部儲存數據的最小單位。
;     任何具備雙穩態(兩個穩定狀態)的物理器件均可以來作存儲元。
;   存儲單元(Storage Cell):存儲器中有大量的存儲元,把它們按相同的位劃分爲組,組內全部的
;     存儲元同時進行讀出或寫入操做,這樣的一組存儲元稱爲一個存儲單元。
;     一個存儲單元一般能夠存放一個字節;存儲單元是 CPU 訪問存儲器的基本單位。
;   存儲單元地址(Storage Cell Address):存儲單元的惟一的固定編號。
;     物理存儲器(Physical Storage):實際存在的、具備實物形式的存儲器。
;         內存(Memory):即內部存儲器,也叫主存。是 CPU 的地址線能夠直接進行尋址的存儲器。
;           用於暫時存放 CPU 中的運算數據,以及與硬盤等外部存儲器交換的數據。
;           分爲兩種:
;             物理內存(Physical Memory):經過物理上真實存在的內存條得到的內存。
;             虛擬內存(Virtual Memory):在硬盤上開出一個區域或文件模擬的物理內存。
;
;
; 內存地址(Address):內存中每一個用於數據存取的基本單位(字節),都被賦予的一個惟一的序號。
;     邏輯地址(Logical Address):機器語言指令中,用來指定一個操做數或一條指令的相對地址。
;       也叫虛擬地址(Virtual Address),是與段相關的偏移地址部分。
;     線性地址(Linear Address):邏輯地址(段中的偏移地址)加上相應段基地址生成的地址。
;       是邏輯地址到物理地址變換之間的中間層。
;     物理地址(Physical Address):內存單元的真實地址。
;       實際是出如今 CPU 外部地址總線上尋址物理內存的地址信號。能夠理解成把插在機器上的
;       物理內存看作一個從 0 到最大容量、逐字節編號的大數組,這個數組的下標就叫作物理地址。
;
;
; 物理存儲空間:物理地址的集合,就是硬件的存儲空間。也稱爲物理空間。
; 地址空間:是指編碼地址(對每個存儲單元分配一個號碼)的範圍。
; 存儲器地址空間:對存儲器編碼地址的範圍。
; 內存地址空間(Address Space):CPU 在操控物理存儲器的時候,把物理存儲器都看成內存來對待,
;   把它們總的看做一個由若干存儲單元組成的邏輯存儲器,這個邏輯存儲器就是內存地址空間。
;   內存地址空間是爲了不物理地址暴露給進程帶來的嚴重問題,創造的一種內存抽象。
;   是一個進程可用於尋址所有內存的地址的集合,是一段表示內存位置的地址範圍。
;   內存地址空間的大小受 CPU 地址總線寬度的限制。32 位地址總線寬度的內存地址空間最大 4GB。
;     邏輯地址空間(Logical Address Space):也稱虛擬地址空間,是指程序中指令和數據所用的
;       全部相對地址的編碼的範圍。
;     線性地址空間(Linear Address Space):線性地址的編碼範圍。
;   CPU 將一個虛擬內存空間中的地址轉換爲物理地址,須要進行兩步:首先將給定一個邏輯地址(
;   段內偏移量),CPU 利用其段式內存管理單元,先將爲個邏輯地址轉換成一個線程地址,再利用其
;   頁式內存管理單元,轉換爲最終物理地址。
;
;
; 內核空間:操做系統內核運行的線性地址空間。
;     內核線性地址空間由全部進程共享,但只有運行在內核態的進程才能訪問,用戶進程能夠經過
;     系統調用切換到內核態訪問內核空間,進程運行在內核態時所產生的地址都屬於內核空間。
; 用戶空間:普通應用程序運行的線性地址空間。
;     每一個進程都有一個獨立的用戶空間,用戶空間由每一個進程獨有。
;     可是內核線程沒有用戶空間,由於它不產生用戶空間地址。另外子進程共享(繼承)父進程的
;     用戶空間,只是使用與父進程相同的用戶線性地址到物理內存地址的映射關係,而不是共享
;     父進程用戶空間。
;
;
; 尋址(Addressing):由地址尋找數據,從地址對應的存儲單元中訪存數據。
;   物理上就是磁頭在盤片上定位數據的過程。
; 尋址方式(Addressing Mode):在存儲器中,指令、操做數寫入或讀出的方式,分爲地址指定方式、
;   相聯存儲方式和堆棧存取方式。計算機內存都採用地址指定方式。當採用地址指定方式時,處理器
;   根據指令中給出的地址信息來尋找物理地址的方式稱爲尋址方式。
;       指令尋址方式:造成指令的有效地址的方法。分爲兩類:
;           順序尋址方式:指令地址在內存中按順序安排,執行程序時,指令一條一條地順序執行。
;           跳躍尋址方式:下條指令的地址碼不是由程序計數器給出,而是由本條指令給出,程序
;             轉移執行的順序的過程。
;       操做數尋址方式:造成操做數的有效地址的方法。分不少種,常見的有:
;           隱含尋址:不明顯地給出操做數的地址。而是在指令中隱含着操做數的地址。
;           當即尋址:指令的地址字段指出的不是操做數的地址,而是操做數自己。
;           直接尋址(Direct Addressing):在指令中直接給出參與運算的操做數及運算結果所
;             存放的有效地址、不須要通過某種變換的尋址方式。
;           間接尋址:指令地址字段中的形式地址不是操做數的真正地址,而是操做數地址的指示器,
;             或者說此形式地址單元的內容纔是操做數的有效地址。
;           相對尋址方式:把當前指令的地址加上指令格式中的形式地址而造成操做數的有效地址。
;           基址尋址方式:將基址寄存器的內容,加上變址寄存器的內容而造成操做數的有效地址。
;           變址尋址方式:把變址寄存器的內容與偏移量相加來造成操做數有效地址。
;           塊尋址方式(Block Addressing):在指令中指出數據塊的起始地址(首地址)和數據塊
;             的長度(字數或字節數)。
;
;
; 段(Segment):將用戶做業的邏輯地址空間依照相應的邏輯信息組的長度劃分紅若干個連續的段。
;   由三個參數定義:
;     段基地址(Segment Base Address):線性地址空間中段的起始地址。
;     段界限(Segment Limit):段的大小。
;     段屬性(Segment Attributes):段的主要特性。
;   分 2 類:
;     存儲段(Memory Segment):存放可由程序直接進行訪問的代碼和數據。分 2 類:
;         代碼段(Code Segment):
;         數據段(Data Segment):
;     系統段(System Segment):分 2 類:
;         任務狀態段(Task State Segment):保存任務的重要信息,經過它實現任務的掛起和恢復。
;             任務:能夠理解成線程,每一個線程須要一個描述符來描述。
;         局部描述符表段:保存局部描述符表的段。
;  !!用分段機制隔離 OS 核心和應用程序,用分頁機制隔離進程。只須要兩個代碼段和兩個數據段。
;  ———用分段把整個系統空間分爲系統空間和用戶空間,再用分頁將用戶空間劃分爲不一樣的進程空間。
;
; 描述符(Descriptor):描述一個段所須要的三個參數(B、L、A)組成的數據結構。分 3 類:
;     存儲段描述符:段寄存器使用的描述符。分 2 類:代碼段描述符、數據段描述符。
;     系統段描述符,分 2 類:TSS 段描述符、LDT 段描述符。
;     門描述符(Gate Descriptor):描述控制轉移的入口點。
;       經過門實現任務內特權級的變換和任務間的切換。
;       分 4 類:
;         調用門(Call Gate):描述子程序的人口。
;         任務門(Task Gate):指示任務。
;         中斷門(Interrupt Gate):描述中斷處理程序的入口。
;         陷阱門(Trap Gate):描述異常處理程序的入口。
;
; 描述符表(Descriptor Table):由描述符組成的線性表。分 3 類:
;     全局描述符表(Global Descriptor Table):
;     中斷描述符表(Interrupt Descriptor Table):
;     局部描述符表(Local Descriptor Table):。
;         LDT 只是一個可選的數據結構,徹底能夠不用它。使用它帶來方便性,也帶來複雜性。
;         若是你想讓你的 OS 內核保持簡潔性、可移植性,則最好不要使用它。
;
; ----------------------------------------------------------------------------------------
; 存儲段(代碼段和數據段)描述符格式(8 字節 64 位)
;
;  ------ ┏━━┳━━┓內存高地址
;         ┃ 7  ┃ 段 ┃
;         ┣━━┫ 基 ┃
;         ┆    ┆ 址 ┆
;  字節   ┆    ┆ 高 ┆
;   7     ┣━━┫ 8  ┃
;         ┃ 0  ┃ 位 ┃
;  ------ ┣━━╋━━┫
;         ┃ 7  ┃ G  ┃
;         ┣━━╉━━┨
;         ┃ 6  ┃D/B ┃
;         ┣━━╉━━┨
;         ┃ 5  ┃ 未 ┃
;         ┣━━┫ 定 ┃
;         ┃ 4  ┃ 義 ┃
;  字節   ┣━━╉━━┨
;   6     ┃ 3  ┃    ┃
;         ┣━━┫ 段 ┃
;         ┃ 2  ┃ 界 ┃
;         ┣━━┫ 限 ┃
;         ┃ 1  ┃ 高 ┃
;         ┣━━┫ 4  ┃
;         ┃ 0  ┃ 位 ┃
;  ------ ┣━━╋━━┫
;         ┃ 7  ┃ P  ┃
;         ┣━━╉━━┨
;         ┃ 6  ┃ D  ┃
;         ┣━━┫ P  ┃
;         ┃ 5  ┃ L  ┃
;         ┣━━╉━━┨
;         ┃ 4  ┃ S  ┃
;  字節   ┣━━╉━━┨
;   5     ┃ 3  ┃    ┃
;         ┣━━┫ T  ┃
;         ┃ 2  ┃ Y  ┃
;         ┣━━┫ P  ┃
;         ┃ 1  ┃ E  ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 23 ┃    ┃
;         ┣━━┫    ┃
;         ┃ 22 ┃ 段 ┃
;         ┣━━┫ 基 ┃
;         ┆    ┆ 址 ┆
;  字節   ┆    ┆ 低 ┆
; 2,3,4 ┣━━┫ 24 ┃
;         ┃ 1  ┃ 位 ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 15 ┃    ┃
;         ┣━━┫    ┃
;         ┃ 14 ┃ 段 ┃
;         ┣━━┫ 界 ┃
;         ┆    ┆ 限 ┆
;  字節   ┆    ┆ 低 ┆
;  0,1   ┣━━┫ 16 ┃
;         ┃ 1  ┃ 位 ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┗━━┻━━┛內存低地址
;
; 存儲段描述符定義宏:(看不明白的話,把 16 進制換成 2 進制就很清楚了)
; 調用格式:Descriptor Base, Limit, Attribute
;         Base      : dd               ; 基址,32 位
;         Limit     : dd               ; 界限,32 位,低 20 位有效,高 12 位無效
;         Attribute : dw               ; 屬性,16 位,高 4 位和低 8 位有效,中間 4 位無效。
%macro Descriptor 3
    dw %2 & 0xFFFF                     ; 界限低 16 位
    dw %1 & 0xFFFF                     ; 基址低 16 位
    db (%1 >> 16) & 0xFF               ; 基址中間 8 位
    db %3 & 0xFF                       ; 屬性低 8 位
    db ((%2 >> 12) & 0xF0) | (%3 >> 12); 界限高 4 位 + 屬性高 4 位
    db (%1 >> 24) & 0xFF               ; 基址高 8 位
%endmacro
; ****************************************************************************************
;
;
; ========================================================================================
; 描述符屬性:
; 描述符屬性是一個字型數值,可是隻有高 4 位和低 8 位有效,中間 4 位無效。
;  ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓
;  ┃15┃14┃13┃12┃11┃10┃09┃08┃07┃06┃05┃04┃03┃02┃01┃ 0┃
;  ┣━┻━┻━┻━╋━┻━┻━┻━╋━╋━┻━╋━╋━┻━┻━┻━┫
;  ┃G ┃DB┃R  AVL┃    無效位    ┃P ┃  DPL ┃S ┃     TYPE     ┃
;  ┗━┻━┻━┻━┻━━━━━━━┻━┻━━━┻━┻━━━━━━━┛
;  十一、G:Granularity,界限粒度位
;    G = 0 界限粒度爲 1 字節;
;    G = 1 界限粒度爲 4K 字節。
;    注意,界限粒度只對段界限有效,對段基地址無效,段基地址老是以字節爲單位。
;
;  十、DB:Default operation size / default stack pointer size and/or upper bound
;      默認操做大小/默認棧指針大小和/或上界限位,根據描述的段不一樣,功能不一樣。
;      對於 32 位代碼和數據段,應該老是設置爲 1;對於 16 位代碼和數據段,應該老是設置爲 0。
;    ⑴ 可執行代碼段(D):指明指令引用有效地址和操做數的默認長度。
;      ① D = 1 默認爲 32 位代碼段,指令使用 32 位地址及 32 或 8 位操做數;
;      ② D = 0 默認爲 16 位代碼段,指令使用 16 位地址及 16 或 8 位操做數。
;      可使用指令前綴 0x66 來選擇非默認值的操做數大小、0x67 來選擇非默認值的地址大小。
;    ⑵由 SS 寄存器指向的數據段,一般爲堆棧段(B):指明堆棧操做指令默認棧指針大小。
;      ① D = 1 使用 32 位堆棧指針寄存器 ESP;
;      ② D = 0 使用 16 位堆棧指針寄存器 SP。
;    ⑶ 向下擴展數據段(B):指明段的上界限。
;      ① D = 1 段的上界限爲 4G;
;      ② D = 0 段的上界限爲 64K。
;
;  0九、R:Reserved,保留位
;    未定義,應該老是設置爲 0。
;
;  0八、AVL:Available,可用位
;    未定義,可供系統軟件使用。
;
;  0七、P:Present,段存在位
;    P = 1 該段在內存中,即該段存在,或者說描述符對地址轉換是有效的;
;    P = 0 該段不在內存中,即該段不存在,或者說描述符對地址轉換無效。
;          把指向這個段描述符的選擇符加載進段寄存器將致使產生一個段不存在異常。
;          內存管理軟件可使用這個標誌來控制在某一給定時間實際須要把那個段加載進內存中。
;          這個功能爲虛擬存儲提供了除分頁機制之外的控制。
;          操做系統可使用該描述符來保存其餘數據,如不存在段實際在什麼地方。
;
; 06 0五、DPL:Descriptor Privilege level,特權級位
;    規定了所描述段的特權級,用於特權檢查,以決定對該段可否訪問。
;    特權級範圍從 0 到 3,0 級特權級最高,3 級最低。
;
;  0四、S:Descriptor type flag,描述符類型位
;    S = 1 存儲段
;    S = 0 系統段和門
;
; 03 02 01 00、TYPE:說明存儲段描述符所描述的存儲段的具體屬性。
;      值     說明
; ------------------------------------------
;  系  0    未定義
;      1    可用 286TSS
;      2    局部描述符表
;      3    忙的 286TSS
;      4    286 調用門
;      5    任務門
;      6    286 中斷門
;  統  7    286 陷阱門
;      8    未定義
;      9    可用 386TSS
;      A    未定義
;      B    忙的 386TSS
;      C    386 調用門
;      D    未定義
;      E    386 中斷門
;  段  F    386 陷阱門
; ------------------------------------------
; 數據段均可讀、非一致
;  數  0    只讀
;      1    只讀、已訪問
;      2    讀/寫
;  據  3    讀/寫、已訪問
;      4    只讀、向下擴展
;      5    只讀、向下擴展、已訪問
;      6    讀/寫、向下擴展
;  段  7    讀/寫、向下擴展、已訪問
; ------------------------------------------
; 代碼段均可執行
;  代  8    只執行
;      9    只執行、已訪問
;      A    執行/讀
;  碼  B    執行/讀、已訪問
;      C    只執行、一致
;      D    只執行、一致、已訪問
;      E    執行/讀、一致
;  段  F    執行/讀、一致、已訪問
;
; 關於一致(Conforming)、非一致(Non-conforming):
;     同級間代碼、數據均可互相訪問。
;     特權級高的不容許訪問特權級低的代碼:系統不會調用用戶代碼。
;     特權級高的能夠訪問特權級低的數據,特權級低的不容許訪問特權級高的數據:
;       系統能夠訪問用戶數據,用戶不能訪問系統數據。
;     一致代碼段,特權級低的能夠訪問特權級高的代碼(特權級不會改變):
;       用戶能夠調用系統共享的代碼。
;     非一致代段(普通的代碼段)不一樣級間不能訪問:
;       防止用戶調用受保護的系統代碼。
;
; ----------------------------------------------------------------------------------------
; 描述符屬性常量定義:
; G 位,默認 1 字節粒度
DA_4K           equ 0x8000  ; 4K 字節粒度,0b 1 000 0000 0000 0000
;
; DB 位,默認 16 位
DA_32           equ 0x4000  ; 32 位,0b 1 00 0000 0000 0000
;
; DPL 位,默認特權級 0
DA_DPL_1        equ 0x20    ; DPL = 1,0b 01 0 0000
DA_DPL_2        equ 0x40    ; DPL = 2,0b 10 0 0000
DA_DPL_3        equ 0x60    ; DPL = 3,0b 11 0 0000
;
;P + S + TYPE 位,存在:+ 0x80(0b 1 000 0000)
; 系統段
DA_SS_LDT       equ 0x82     ; 局部描述符表
DA_SS_TSKG      equ 0x85     ; 任務門
DA_SS_TSKSS     equ 0x89     ; 可用 386 TSS(任務狀態)段
DA_SS_CALLG     equ 0x8C     ; 386 調用門
DA_SS_INTG      equ 0x8E     ; 386 中斷門
DA_SS_TRPG      equ 0x8F     ; 386 陷阱門
; 存儲段:+ 0x10(0b 1 0000)
DA_DS_R         equ 0x90     ; 只讀數據段
DA_DS_RW        equ 0x92     ; 可讀寫數據段
DA_DS_RWA       equ 0x93     ; 可讀寫、已訪問數據段
;
DA_CS_E         equ 0x98     ; 只執行代碼段
DA_CS_ER        equ 0x9A     ; 可執行、可讀代碼段
DA_CS_EC        equ 0x9C     ; 可執行、一致代碼段
DA_CS_ERC       equ 0x9E     ; 可執行、可讀、一致代碼段
;
; ****************************************************************************************


; ========================================================================================
; 選擇子:
; ----------------------------------------------------------------------------------------
;  ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓
;  ┃15┃14┃13┃12┃11┃10┃09┃08┃07┃06┃05┃04┃03┃02┃01┃0 ┃
;  ┣━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━┻━╋━╋━┻━┫
;  ┃                   描述符索引                     ┃TI┃  RPL ┃
;  ┗━━━━━━━━━━━━━━━━━━━━━━━━━┻━┻━━━┛
;  TI:Table Indicator,引用描述符表位
;    TI = 0 從全局描述符表(GDT)中讀取描述符;
;    TI = 1 從局部描述符表(LDT)中讀取描述符。
;
;  RPL:Requested Privilege Level,請求特權級位
;    用於特權檢查。
;
; ----------------------------------------------------------------------------------------
; 選擇子屬性常量定義:
; TI 位,默認爲全局描述符表
SA_LDT          equ 4       ; 局部描述符表,0b 1 00
;
; RPL 位,默認請求特權級 0
SA_RPL_1        equ 1
SA_RPL_2        equ 2
SA_RPL_3        equ 3
;
; ****************************************************************************************

; 分頁機制使用的常量說明
;----------------------------------------------------------------------------
PG_P        EQU 1   ; 頁存在屬性位
PG_RWR      EQU 0   ; R/W 屬性位值, 讀/執行
PG_RWW      EQU 2   ; R/W 屬性位值, 讀/寫/執行
PG_USS      EQU 0   ; U/S 屬性位值, 系統級
PG_USU      EQU 4   ; U/S 屬性位值, 用戶級

%endif

BootSector.asm 上來就先把中斷屏蔽掉:oop

; BootSectot.asm
; 引導扇區
; 四彩
; 2015-12-08

; ========================================================================================
; 電腦的啓動過程:
; 一、80x86 CPU 啓動後(加電或復位),CS : IP 被設置爲 0xFFFF : 0x0,CPU 今後處讀取指令
;    開始執行。該單元在基本輸入輸出系統(Basic Input/Output System,BIOS)的地址範圍內,
;    這裏是一條跳轉到 BIOS 中真正啓動代碼處的指令。
; 二、BIOS 首先進行加電自檢(Power-On Self-Test,POST),而後進行更完整的硬件檢測,並加載
;    相關設備。
; 三、接下來按啓動順序(Boot Sequence)讀取第一個設備的第一個扇區,若是該扇區最後兩個字節
;    是 0x55 和 0xAA,代表這個設備能夠用於引導;若是不是,代表這個設備不能用於引導,BIOS
;    繼續讀取啓動順序中的下一個設備……直到找到啓動設備。BIOS 把第一個啓動設備的第一個扇區
;    讀到內存 0x7C00 處,而後把控制權交給該處。
; 四、操做系統經過改寫啓動設備的第一個扇區,被讀入內存後,從內存 0x7C00 處開始接管電腦。
; ****************************************************************************************


; ========================================================================================
; ----------------------------------------------------------------------------------------
; 頭文件
%include "./boot/inc/const.inc"
%include "./boot/inc/FAT12.inc"

; ----------------------------------------------------------------------------------------
    org 0x7C00
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系統引導扇區的頭部(前 62 字節)
    FAT12Head main, "NASM+GCC", "TestX_v0.01"
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系統引導扇區的引導代碼(從第 62 字節開始)
; ----------------------------------------------------------------------------------------
; 程序入口
main:
    ; 屏蔽中斷
    cli

    ; 初始化寄存器
    mov ax, cs
    mov ds, ax
    mov ss, ax
    mov ax, 0x7C00
    mov bp, ax
    mov sp, ax

    ; 尋找 Loader
    mov si, FileNameOfLoader
    call SearchFile
    cmp ax, 0
    jnz .Found
    mov si, strNotFoundLoader
    call PrintStr
    jmp $

.Found:
    ; 加載 Loader
    push SEGMENTOFLOADER
    pop es
    mov bx, STACKSIZE
    call LoadFile

    ; 控制權交給已加載到內存的 Loader
    jmp SEGMENTOFLOADER : STACKSIZE

; ----------------------------------------------------------------------------------------
; 包含 FAT12 子函數
    IncludeFAT12Function SEGMENTOFTEMP
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系統引導扇區引導數據部分
    strNotFoundLoader db "Not found "           ; 提示信息字符串(鏈接着下面的文件名)
    FileNameOfLoader  db "LOADER  SYS", 0, 0    ; Loader 文件名,8 + 3格式
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系統引導扇區引導代碼的剩餘部分用 0 填滿,最後兩個字節置結束標誌(0xAA55)
    times 510 - ($ - $$) db 0
    dw 0xAA55
; ****************************************************************************************

    loader.asm 中增長了取內存容量,再按照 ELF 文件格式把 kernel 各段重定位部分,而且把內存容量和顯存相關參數保存到全局變量段了:ui

; loader.asm
; 加載程序
; 四彩
; 2015-12-08


; ========================================================================================
; ----------------------------------------------------------------------------------------
; 頭文件
%include "./boot/inc/const.inc"
%include "./boot/inc/FAT12.inc"
%include "./boot/inc/PMode.inc"
%include "./boot/inc/ELF.inc"

; ----------------------------------------------------------------------------------------
    org STACKSIZE
    jmp RMode_main
; ****************************************************************************************


;=========================================================================================
[SECTION .data]
; ----------------------------------------------------------------------------------------
; 地址範圍描述符結構(Address Range Descriptor Structure)
struc T_AddrRngDscStruc
;      字段名                       ;偏移 長度 說明
    .dwBaseAddrLow      resd 1      ; 0x00  4   基地址的低 32 位
    .dwBaseAddrHigh     resd 1      ; 0x04  4   基地址的高 32 位(未使用,爲 0)
    .dwLengthLow        resd 1      ; 0x08  4   長度(字節)的低32位
    .dwLengthHigh       resd 1      ; 0x0C  4   長度(字節)的高32位(未使用,爲 0)
    .dwType             resd 1      ; 0x10  4   地址類型:1 = 可用段, 2 = 正用或保留段,
endstruc

; 全局變量結構
struc T_Global_Param
;      字段名                       ;偏移 長度 說明
    .dwMemorySize       resd 1      ; 0x00  4   內存總容量
    .dwPhyAddrOfVideo   resd 1      ; 0x04  4   顯存基址
    .wScreenX           resw 1      ; 0x08  2   分辨率 X
    .wScreenY           resw 1      ; 0x0A  2   分辨率 Y
    .bBitsPerPixel      resb 1      ; 0x0C  1   顏色數
endstruc

; ----------------------------------------------------------------------------------------
; 出錯提示信息及 kernel 文件名
    strCheckMemoryFail  db "Failed to check memory", 0
    strNotSupportVESA   db "Not support VESA", 0
    strNotFoundKernel   db "Not found "             ; 鏈接着下面的文件名
    FileNameOfKernel    db "KERNEL  SYS", 0, 0      ; kernel 文件名,8 + 3 格式

; ----------------------------------------------------------------------------------------
tArds istruc T_AddrRngDscStruc      ; 地址範圍描述符結構實體
    times T_AddrRngDscStruc_size db 0
iend

; ----------------------------------------------------------------------------------------
; GDT
; Loader 把所有內存都做爲一個段使用,分爲兩類:代碼段、數據段
;                       基址        界限        屬性
Desc_Begin : Descriptor 0,          0,          0                           ; 空描述符
Desc_Code  : Descriptor 0,          0xFFFFF,    DA_CS_ER + DA_32 + DA_4K    ; 代碼段
Desc_Data  : Descriptor 0,          0xFFFFF,    DA_DS_RW + DA_32 + DA_4K    ; 數據段
Desc_End   :

; ----------------------------------------------------------------------------------------
; GDTPtr
GDTPtr  dw Desc_End - Desc_Begin - 1            ; 界限
        dd SEGMENTOFLOADER * 0x10 + Desc_Begin  ; 基址

; ----------------------------------------------------------------------------------------
; 選擇子
SelectorCode    equ Desc_Code  - Desc_Begin
SelectorData    equ Desc_Data  - Desc_Begin
; ****************************************************************************************


; ========================================================================================
[SECTION .code16]
[BITS 16]
; 實模式代碼段:取得內存容量、複製 kernel 到內存、設定畫面模式、開啓保護模式
; ----------------------------------------------------------------------------------------
RMode_main:
    ; 初始化寄存器
    mov ax, cs
    mov ds, ax
    mov ss, ax
    mov ax, STACKSIZE
    mov bp, ax
    mov sp, ax

    ; 獲取內存信息
    ; BIOS 中斷 int 0x15
    ; 入口參數:eax = 0xE820 功能號
    ;           ebx = ARDS 所需的後續值,第一個爲 0
    ;           es : di = 內存緩衝區基址,指向一個地址範圍描述符結構(ARDS)
    ;           ecx = ARDS 的大小,一般 BIOS 老是填充 20 字節的信息到 ARDS 中
    ;           edx = 「SMAP」 的 ASCII 碼(0x0534D4150)
    ; 出口參數:CF 置 1 代表調用出錯,不然無錯
    ;           eax = "SMAP" 的 ASCII 碼
    ;           ebx = 下一 ARDS 所需的後續值,若是爲 0 則說明已到最後一個 ARDS。
    push SEGMENTOFGPARAM
    pop fs
    xor ebx, ebx
    mov di, tArds

.MemChkLoop:
    mov eax, 0xE820
    mov ecx, T_AddrRngDscStruc_size
    mov edx, 0x534D4150
    int 15h

    jnc .MemChkOK
    mov si, strCheckMemoryFail
    call PrintStr
    jmp $

.MemChkOK:
    mov eax, [tArds + T_AddrRngDscStruc.dwLengthLow]    ; 累加內存總容量
    add [fs : T_Global_Param.dwMemorySize], eax

    cmp ebx, 0
    jne .MemChkLoop

    ; 複製 Kernel 到內存
    mov si, FileNameOfKernel                ; 尋找
    call SearchFile
    cmp ax, 0
    jnz .Found
    mov si, strNotFoundKernel
    call PrintStr
    jmp $

.Found:
    push SEGMENTOFKERNEL                    ; 複製
    pop es
    xor bx, bx
    call LoadFile


   ; 設置圖形模式
    push SEGMENTOFTEMP                    ; 取得 VBE 信息
    pop es
    xor di, di
    mov ax, 0x4F00
    int 0x10

    mov di, 0x200
    mov ax, 0x4F01                          ; 取得 800*600*16 模式信息
    mov cx, 0x114
    int 0x10
    cmp ax, 0x004F
    jz .Support
    mov si, strNotSupportVESA
    call PrintStr
    jmp $

.Support:
    mov ax, 0x4F02                          ; 設置畫面模式
    mov bx, 0x4114                          ; 800 * 600,16 位色,線性幀緩衝區
    int 0x10

;    mov ax, 0x0013                          ; 320 * 200,8 位色
;    int 0x10

    ; 保存顯存相關信息到全局變量
    mov eax, [es : 0x200 + 0x28]
    mov [fs : T_Global_Param.dwPhyAddrOfVideo], eax

    mov ax, [es : 0x200 + 0x12]
    mov [fs : T_Global_Param.wScreenX], ax

    mov ax, [es : 0x200 + 0x14]
    mov [fs : T_Global_Param.wScreenY], ax

    mov al, [es : 0x200 + 0x19]
    mov [fs : T_Global_Param.bBitsPerPixel], al


    ; 開啓保護模式
    lgdt [GDTPtr]                           ; 加載 GDT

    in al, 0x92                             ; 打開地址線 A20
    or al, 0b10
    out 0x92, al

    mov eax, cr0                            ; 置保護模式標誌位
    or eax, 1
    mov cr0, eax


    ; 跳轉至保護模式代碼段
    jmp dword SelectorCode : (SEGMENTOFLOADER * 0x10 + PMode_main)

; ----------------------------------------------------------------------------------------
; 包含 FAT12 子函數
    IncludeFAT12Function SEGMENTOFTEMP
; ****************************************************************************************


; ========================================================================================
[SECTION .code32]
[BITS 32]
; 保護模式代碼段,由實模式跳入:重定位 kernel,跳轉到 kernel
; ----------------------------------------------------------------------------------------
PMode_main:
    ; 初始化寄存器
    mov ax, SelectorData
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov eax, SEGMENTOFLOADER * 0x10 + STACKSIZE
    mov ebp, eax
    mov esp, eax


    ; 裝載 kernel 各段(重定位)
    xor ecx, ecx
    mov cx, [SEGMENTOFKERNEL * 0x10 + Tag_ELF_Header.e_phnum]   ; 段數
    mov esi, [SEGMENTOFKERNEL * 0x10 + Tag_ELF_Header.e_phoff]  ; 指向程序頭表
    add esi, SEGMENTOFKERNEL * 0x10

.Load:
    mov eax, [esi + Tag_Program_Header.p_type]                  ; 只裝載可裝載段
    cmp eax, PT_LOAD
    jnz .NotNeedLoad

    push dword[esi + Tag_Program_Header.p_filesz]
    mov eax, [esi + Tag_Program_Header.p_offset]
    add eax, SEGMENTOFKERNEL * 0x10
    push eax
    push dword[esi + Tag_Program_Header.p_vaddr]
    call memcpy
    add esp, 3 * 4

.NotNeedLoad:
    add esi, Tag_ELF_Header.e_phentsize
    loop .Load


    ; 跳轉至 kernel(修改 cs 和 eip)
    jmp SelectorCode : KERNELENTRYPHYADDR


; ----------------------------------------------------------------------------------------
; 函數功能:內存複製
; void memcpy(void *pDest, void *pSrc, DWORD dwSize);
; 入口參數:es : pDest = 目標內存地址
;           ds : pSrc  = 源內存地址
;           dwSize     = 數據長度(字節)
; 出口參數:無
memcpy:
    push ebp
    mov ebp, esp

    push esi
    push edi
    push ecx

    mov edi, [ebp + 2 * 4]                  ; pDest
    mov esi, [ebp + 3 * 4]                  ; pSrc
    mov ecx, [ebp + 4 * 4]                  ; dwSize
    jecxz .Return

.Copy:
    lodsb
    stosb
    loop .Copy

.Return:
    pop ecx
    pop edi
    pop esi

    mov esp, ebp
    pop ebp
    ret
; ****************************************************************************************

    因爲轉到了新程序,回不去 loader 了,原來的 GDT 也失效了,要從新實現一次編碼

/*  PMode.c
    保護模式相關函數
    四彩
    2015-12-09
*/


#include "../kernel/include/PMode.h"


// =======================================================================================
// 段描述符(Segment Descriptor)
/*  Base      : dd               ; 基址,32 位
    Limit     : dd               ; 界限,32 位,低 20 位有效,高 12 位無效
    Attribute : dw               ; 屬性,16 位,高 4 位和低 8 位有效,中間 4 位無效。
*/
typedef struct tag_Segment_Descriptor
{
    unsigned short LLimit;                  // 界限低 16 位(0 ~ 15)
    unsigned short LBase;                   // 基址低 16 位(0 ~ 15)
    unsigned char  MBase;                   // 基址中間 8 位(16 ~ 23)
    unsigned char  LAccess;                 // 屬性低 8 位
/*
    unsigned char  Type   : 4;              // 屬性 Type 位(屬性位)
    unsigned char  S      : 1;              // 屬性 S 位(類型位)
    unsigned char  DPL    : 2;              // 屬性 DPL 位(特權級位)
    unsigned char  P      : 1;              // 屬性 P 位(存在位)
*/
    unsigned char HLimit_HAccess;           // 界限高 4位(16 - 19)+ 屬性高 4 位()
/*
    unsigned char  HLimit : 4;              // 界限高 4 位(16 ~ 19)
    unsigned char  Avl    : 1;              // 屬性 AVL 位(可用位)
    unsigned char  Res    : 1;              // 屬性 Res 位(保留位,必須爲 0)
    unsigned char  DB     : 1;              // 屬性 D/B 位(32 位位)
    unsigned char  G      : 1;              // 屬性 G 位(界限粒度位)
*/
    unsigned char  HBase;                   // 基址高 8 位(24 ~ 31)
} SEGMENT_DESCRIPTOR;

// 全局描述符表結構(GDT, Global segment Descriptor Table)
// 把所有內存都做爲一個段使用,分爲兩類:代碼段、數據段
typedef struct tag_Global_Descriptor_Table
{
    SEGMENT_DESCRIPTOR first;               // 空段
    SEGMENT_DESCRIPTOR code;                // 代碼段
    SEGMENT_DESCRIPTOR data;                // 數據段
} GDT;

// GDT 描述符(描述 GDT 的描述符)
typedef struct tag_GDT_Ptr
{
    unsigned short limit;
     unsigned      base;
} GDT_PTR;


// =======================================================================================
// ---------------------------------------------------------------------------------------
// 描述符屬性常量定義:
// G 位,默認 1 字節粒度
#define DA_4K            0x8000  // 4K 字節粒度,0b 1 000 0000 0000 0000

// DB 位,默認 16 位
#define DA_32            0x4000  // 32 位,0b 1 00 0000 0000 0000

// DPL 位,默認特權級 0
#define DA_DPL_1         0x20    // DPL = 1,0b 01 0 0000
#define DA_DPL_2         0x40    // DPL = 2,0b 10 0 0000
#define DA_DPL_3         0x60    // DPL = 3,0b 11 0 0000

// P + S + TYPE 位,存在:+ 0x80(0b 1 000 0000)
// 系統段
#define DA_SS_LDT        0x82     // 局部描述符表
#define DA_SS_TSKG       0x85     // 任務門
#define DA_SS_TSKSS      0x89     // 可用 386 TSS(任務狀態)段
#define DA_SS_CALLG      0x8C     // 386 調用門
#define DA_SS_INTG       0x8E     // 386 中斷門
#define DA_SS_TRPG       0x8F     // 386 陷阱門
// 存儲段:+ 0x10(0b 1 0000)
//    數據段
#define DA_DS_R          0x90     // 只讀數據段
#define DA_DS_RW         0x92     // 可讀寫數據段
#define DA_DS_RWA        0x93     // 可讀寫、已訪問數據段
//    代碼段
#define DA_CS_E          0x98     // 只執行代碼段
#define DA_CS_ER         0x9A     // 可執行、可讀代碼段
#define DA_CS_EC         0x9C     // 可執行、一致代碼段
#define DA_CS_ERC        0x9E     // 可執行、可讀、一致代碼段


// =======================================================================================
extern void asm_lgdt(GDT_PTR *tGdtPtr);


// 相似 PMode.ini 中的結構宏,將段描述符的三個屬性拆開存放到合適的位置
void SetSegmentDesc(SEGMENT_DESCRIPTOR *psd, unsigned base, unsigned limit, unsigned short access)
{
    psd->LLimit         = limit & 0xFFFF;
    psd->LBase          = base & 0xFFFF;
    psd->MBase          = (base >> 16) & 0xFF;
    psd->LAccess        = access & 0xFF;
    psd->HLimit_HAccess = ((limit >> 12) & 0xF0) | (access >> 12);
    psd->HBase          = (base >> 24) & 0xFF;
}

// 初始化 GDT
void InitGdt()
{
    GDT     *pGdt;
    GDT_PTR tGdtPtr;

    pGdt = (GDT *)PHYADDROFGDT;

    SetSegmentDesc(&(pGdt->first), 0, 0, 0);
    SetSegmentDesc(&(pGdt->code), 0, 0xFFFFF, DA_CS_ER + DA_4K + DA_32);
    SetSegmentDesc(&(pGdt->data), 0, 0xFFFFF, DA_DS_RW + DA_4K + DA_32);

    tGdtPtr.limit = sizeof(GDT);
    tGdtPtr.base  = PHYADDROFGDT;
    asm_lgdt(&tGdtPtr);
}

    把初始化 GDT 的函數引出來,供 kernel 調用操作系統

/* PMode.h
   保護模式
   四彩
   2015-12-08
*/

#ifndef _PROTECTEDMODE_H_
#define _PROTECTEDMODE_H_


// GDT 表存放的物理內存地址
#define PHYADDROFGDT	0x270000


// 初始化 GDT
void InitGdt();


#endif

    kernel.c 再也不是文本文件了,把全局變量取出來,而且試驗了直接寫顯存。
線程

/*  kernel.c
    內核
    whoozit
    2012-12-08
*/

#include "../kernel/include/PMode.h"
#include "../lib/include/string.h"

// 顯存相關結構
typedef struct tag_Graphics_Param
{
    unsigned        dwPhyAddrOfVideo;   // 顯存基址
    unsigned short  wScreenX;           // 分辨率 X
    unsigned short  wScreenY;           // 分辨率 Y
    unsigned char   bBitsPerPixel;      // 顏色數
} GRAPHICS_PARAM;

// 全局變量結構
typedef struct tag_Global_param
{
    unsigned int    dwMemorySize;
    GRAPHICS_PARAM  tGraphics;
} GLOBAL_PARAM;

// 存放全局變量的物理地址(const.inc 中的 SEGMENTOFGPARAM * 0x10)
#define PHYADDROFGPARAM     0x500


extern void asm_hlt();


void c_main()
{
    unsigned i, j;
    unsigned short *pVideo;
    unsigned short Screen_X, Screen_Y;
    GLOBAL_PARAM g_Param;

    // 初始化 GDT 表
    InitGdt();

    // 取全局變量
    memcpy((void *)&g_Param, (void *)(PHYADDROFGPARAM), sizeof(GLOBAL_PARAM));
    pVideo = (unsigned short *)g_Param.tGraphics.dwPhyAddrOfVideo;
    Screen_X = g_Param.tGraphics.wScreenX;
    Screen_Y = g_Param.tGraphics.wScreenY;

    for(i = 0; i < Screen_X; i++)
        for(j = 0; j < Screen_Y; j++)
            pVideo[Screen_X * j + i] = (Screen_X * j + i) & 0xFFF;

    while(1)
        asm_hlt();
}

    用到了 memcpy 函數,很簡單,本身實現就行:

/* string.h
   字符串處理函數
   四彩
   2015-12-08
*/


#ifndef _STRING_H
#define _STRING_H


// 模擬同名C庫函數
void *memcpy(void *pDst, const void *pSrc, unsigned int nSzie);


#endif
/*  string.c
    字符串處理函數
    四彩
    2015-12-08
*/

#include "../include/string.h"

void *memcpy(void *pDst, const void *pSrc, unsigned int nSzie)
{
    // 不清楚來源結構,只能逐字節複製
    unsigned char *p1 = (unsigned char *)pDst;
    unsigned char *p2 = (unsigned char *)pSrc;

    // 若目標內存與源內存無重疊,或目標內存尾部與源內存頭部重疊,則順序複製
    if(p1 < p2 || p1 >= (p2 + nSzie))
        while(nSzie--)
            *p1++ = *p2++;
    // 若目標內存頭部與源內存尾部重疊,則逆序複製
    else if(p1 > p2 && p1 < (p2 + nSzie))
    {

        p1 += nSzie - 1;
        p2 += nSzie - 1;

        while(nSzie--)
            *p1-- = *p2--;
    }
    // 若目標內存與源內存徹底一致,則無需複製
    // else if(p1 == p2) return(pDst);

    return(pDst);
}

     用到了兩個個彙編指令, C 語言無能爲力,只能彙編實現了:

; instr.asm
; 彙編指令函數
; 四彩
; 2015-12-08


; 導出函數
global asm_hlt, asm_lgdt


[SECTION .text]
; ========================================================================================
; ----------------------------------------------------------------------------------------
; void asm_hlt();
; 函數功能:hlt 指令
; 參數表:無
; 返回值:無
asm_hlt:
    hlt
    ret

; void asm_lgdt(GDT_PTR *pGDTPtr);
; 函數功能:lgdt 指令
; 參數表:pGDTPtr = GDT_PTR 結構地址
; 返回值:無
asm_lgdt:
    push ebp
    mov ebp, esp

    push eax

    mov eax, [ebp + 2 * 4]
    lgdt [eax]

    pop eax

    mov esp, ebp
    pop ebp
    ret
; *********************************************************************************************

    Makefile 文件也貼出來吧

# Makefile of TestX
# 四彩
# 2012-12-08


.PHONY: all cls

# kernel 入口地址(物理內存地址,必須與 loader 裝載 kernel 的地址一致)
KERNELENTRYPHYADDR = 0x30400


BootPath    = ./boot
KrlPath     = ./kernel
LibPath     = ./lib
RlsPath     = ./release
ImgPath     = ./tool/img

KrlIncPath  = $(KrlPath)/include
LibIncPath  = $(LibPath)/include
KrlIncFile  = $(KrlIncPath)/PMode.h
LibIncFile  = $(LibIncPath)/string.h

KrlNeedFile = $(RlsPath)/PMode.oc $(RlsPath)/string.obj $(RlsPath)/instr.oasm
ImgNeedFile = $(RlsPath)/img $(RlsPath)/BootSector.sys $(RlsPath)/loader.sys $(RlsPath)/kernel.sys

all:
	@[ -d $(RlsPath) ] || mkdir $(RlsPath)
	make vFloppy.img

cls:
	@ -rm -rf $(RlsPath)
	@ -rm -f ./vFloppy.img


vFloppy.img: $(ImgNeedFile) Makefile
	$(RlsPath)/img -n $@
	$(RlsPath)/img -c $@ $(RlsPath)/BootSector.sys;
	$(RlsPath)/img -a $@ $(RlsPath)/loader.sys $(RlsPath)/kernel.sys

$(RlsPath)/kernel.sys : $(RlsPath)/kernel.oc $(KrlNeedFile)
	ld -s -Ttext $(KERNELENTRYPHYADDR) -e c_main -o $@ $^

# .oc 是 C語言的核心文件
$(RlsPath)/%.oc : $(KrlPath)/%.c $(KrlIncFile)
	gcc -c -fno-builtin -fno-stack-protector -o $@ $<

# .obj 是 C語言的庫文件
$(RlsPath)/%.obj : $(LibPath)/%.c $(KrlIncFile) $(LibIncFile)
	gcc -c -fno-builtin -fno-stack-protector -o $@ $<

# .oasm 是彙編語言的庫文件
$(RlsPath)/%.oasm : $(LibPath)/%.asm
	nasm -felf -o $@ $<

# .sys 是彙編語言的引導文件
$(RlsPath)/%.sys : $(BootPath)/%.asm
	nasm -o $@ $<

# img
$(RlsPath)/img: $(ImgPath)/img.c $(ImgPath)/FAT12.c
	gcc -o $@ $^

    貼上運行效果圖:

相關文章
相關標籤/搜索