Chapter 03:Loader === :::info Loader 有以下主要任務: &emsp; 1. 檢測記憶體。 &emsp; 2. Enable Protected Mode (開啟保護模式)。 &emsp; 3. Enable Memory Paging (開啟分頁)。 &emsp; 4. Load Kernel。 &emsp; 5. Jump to Kernel。 ::: >[time=Mon, Aug 25, 2025 10:42 AM] --- https://youtu.be/MbbbA-DMkwM <iframe width="560" height="315" src="https://www.youtube.com/embed/MbbbA-DMkwM?si=0SOenAoqlnDj26Dk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> https://youtu.be/7NGrvHhI2e0 <iframe width="560" height="315" src="https://www.youtube.com/embed/7NGrvHhI2e0?si=LAPv43GnHIBdq46e" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> # Enable Protected Mode ## Background Knowledge ### Intel 80386(1985年) >Reference: [Intel 80386 Reference Programmer's Manual](https://pdos.csail.mit.edu/6.828/2004/readings/i386/toc.htm) Intel x86 系列繼 8086(1978年) 之後推出著名的 ==80386(1985年)==,終於開啟了 x86 32 bits 的輝煌時代。80386的廣泛應用,將PC從16位元時代帶入了32位元時代。80386的強大運算能力也使PC機的應用領域得到極多擴充,商業辦公、科學計算、工程設計、多媒體處理等應用得到迅速發展。CPU的快速演進,在1990年代後期使用80386的個人電腦已相當罕見,但因可應用於嵌入式系統、工業電腦及航天等用途,英特爾公司仍持續生產此CPU產品,直到2007年才停產。 ![KL_Intel_i386DX](https://hackmd.io/_uploads/HJTPkHmqle.jpg) 80386 最主要的貢獻就是: * CPU register 全面使用 32 bits。 * memory addressing bus 使用 32 bits,最大定址空間為 4 GB。 * 首度導入 Protected Mode(保護模式)。 在保護模式之前 (真實模式),對於記憶體的存取,基本上並沒有任何硬體手段的管控,不論是作業系統,還是使用者程式,都可以隨意存取。在導入保護模式後,CPU 使用 GDT 的方式來對於記憶體空間做管控。 ### Global Descriptor Table (GDT) >Reference: [80386 Programmer's Reference Manual](https://pdos.csail.mit.edu/6.828/2005/readings/i386/s05_01.htm) CPU 究竟是如何做到「保護」模式的呢?答案就是使用 GDT! 保護模式一旦開啟後,CPU segment register 將不會再用 shift 4 bits 來處理了,取而代之的是利用「selector」來取得 base address,然後再加上 offset 就成為最終的定址空間。 ![fig5-2](https://hackmd.io/_uploads/Sy78SHQceg.gif) 所以下一個問題就會是 CPU 怎麼透過 selector 來取得 base address? 透過 `LGDT` CPU instruction。它是個 48 bits register,透過設定這個 register,CPU 藉此載入整張 GDT。`LGDT` 是 特權指令,只能在 CPL = 0 (Ring 0) 執行。 ![截圖 2025-09-02 凌晨12.23.01](https://hackmd.io/_uploads/rkCYdS75lx.png) ![截圖 2025-09-02 凌晨12.32.53](https://hackmd.io/_uploads/rkVBsrm5eg.png) 那下個問題就會是 GDT 到底長怎樣? 每一個 GDT element 共有 64 bits。 ![1_gND2k0Jr3Q3WPAbr9mHsOQ](https://hackmd.io/_uploads/Bk2ECSQqle.png) 你會不會好奇為什麼要把 base address/segment limit 拆成這麼細?原因是第一個推出保護模式的 CPU 其實是 Intel 80286(1982年),當時只有 CPU register 只有 16 bits,addressing bus 也只有擴充到 24 bits,所以 base address 有 24 bits,而 limit 只有 16 bits。80386 為了相容 80286,因此只能在此基礎上繼續往後擴充,最終長成這奇怪的樣子。 以下稍微簡述一下這些欄位: | Column | Description | Value | | ------ | ------------------------------------ | ------------------------------------------------------------------ | | Type | Segment type | 0x8 : code <br> 0x2 : data | | S | System | 0x0 : Hardware <br> 0x1 : Software | | DPL | Descriptor Privilege Level | 0x0 : Ring 0 <br> 0x1 : Ring 1 <br> 0x2 : Ring 2 <br> 0x3 : Ring 3 | | P | Present | 0x0 : Not in memory <br> 0x1 : in memory | | AVL | Available for use by system software | reserved for operation system | | L | 64-bit code segment | 0x0 : 32-bit code segment <br> 0x1 : 64-bit code segment | | D/B | Default operand size | 0x0 : 16-bit segment <br> 0x1 : 32-bit segment | | G | Granularity | 0x0 : 1 Byte <br> 0x1 : 4 KB | ![IMG_3401](https://hackmd.io/_uploads/HyVkf1Ccge.jpg) 最後是我們會透過設定 segment register(16 bits) 來決定要套用哪個 GDT element。 ![截圖 2025-09-02 凌晨1.00.22](https://hackmd.io/_uploads/BJiBbL7cll.png) >[!Tip] TI = Table Indicator x86 CPU level privilege control,原生的 CPU 有提供四個等級,但是作業系統(如 Linux)只使用其中的兩個:Ring 0 for kernel space;Ring 3 for user space。 ==DPL 就是根據此概念做設定== ![截圖 2025-09-02 下午5.09.48](https://hackmd.io/_uploads/r1iK44E9gl.png) ### Enable A20 >Reference: [Bochs' map of I/O ports to functions](https://bochs.sourceforge.io/techspec/PORTS.LST) 在 8086 時代,addressing bus 只有 20 bits,縱使後續 80286 使用 24 bits,以及 80386 使用 32 bits,為了相容仍然只使用 20 bits addressing bus,如果想要突破 20 bits 位址線的話,就必須開啟 A20 Gate,讓 CPU 有能力定址到 32 bits。 ![upload_58a64016b6640cd296ad8b4ae82fdd1e](https://hackmd.io/_uploads/Hy-R9NN9ee.png) ### Enable Protected Mode 終於要進入最後一步,開啟 CPU Protected Mode,方法也很簡單,就是直接操控 CPU control register CR0。 ![upload_82e918df4e82582142f0f74198a6b5c9](https://hackmd.io/_uploads/HkHMo4E5gx.jpg) ## Memory Layout ![截圖 2025-09-09 凌晨12.36.27](https://hackmd.io/_uploads/S1mLUYhqle.png) ## Source code https://github.com/srhuang/a-os/commit/79828d0a6a9d46b57d3ab576fc65938bf0f6cc4a ## Compile ```sh nasm boot/mbr.s -I boot/ -o out/mbr.bin nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-26 凌晨4.46.52](https://hackmd.io/_uploads/B1FYhr5Ygl.png) # Print Log >Reference: https://bochs.sourceforge.io/techspec/PORTS.LST ![截圖 2025-08-26 凌晨4.55.52](https://hackmd.io/_uploads/HkIx0rctee.png) ![截圖 2025-08-26 凌晨4.52.19](https://hackmd.io/_uploads/BJewTHcYxg.png) ![IMG_3390](https://hackmd.io/_uploads/BJ94Iv65le.jpg) ![截圖 2025-09-09 下午4.42.42](https://hackmd.io/_uploads/HyziuDT9ex.png) ![截圖 2025-08-26 下午1.52.37](https://hackmd.io/_uploads/S1jAjTcKgx.png) ## Source code https://github.com/srhuang/a-os/commit/18cdce104842896fb375d2ba0ec23e083ea8d3ad >[!Warning]把 print log 變成一個 function >https://github.com/srhuang/a-os/commit/b22c230a32cd61b28af139cbe869eb2d3eedc8ab ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-26 下午3.35.33](https://hackmd.io/_uploads/S1Ok4kiYee.png) # Get RAM Size >Reference: >[BIOS中斷呼叫](https://zh.wikipedia.org/zh-tw/BIOS%E4%B8%AD%E6%96%B7%E5%91%BC%E5%8F%AB) >[OSDev wiki](https://wiki.osdev.org/Detecting_Memory_(x86)) >[Query System Address Map](http://www.uruk.org/orig-grub/mem64mb.html) ![upload_a6924ec7f2b52929c00341f33ea39862](https://hackmd.io/_uploads/HyUFfxjYge.png) * 0x88: 最大支援 64MB * 0xe801: 最大支援 4GB * 0xe820: 檢查全部記憶體 >[!Note]為了簡化,我就只實作 0xe820 ## 利用 BIOS Interrupt 0x15/0xE820 取得實體記憶體 利用資料結構(ARDS)來描述記憶體資訊,主要步驟如下: * 寫好呼叫前輸入的暫存器資訊。 * 執行中斷呼叫 int 0x15 * 檢查暫存器的結果,ARDS會存放在記憶體中。 >[!Note] Address Range Descriptor Structure (ARDS) >![截圖 2025-09-09 下午5.11.35](https://hackmd.io/_uploads/By8tJ_T5lx.png) >![截圖 2025-09-09 下午5.12.56](https://hackmd.io/_uploads/SkD6yOT5xe.png) ## Source Code https://github.com/srhuang/a-os/commit/7092715826df4c6be5b8240c8564f2e1d0fcf555 :::danger 修正 source code 應該改為 `jae`,而不是`jge`。 `jge`: Jump if greater or equal, for signed number `jae`: Jump if above or equal, for unsigned number ```asm=104 jae next_ards ``` ::: ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-27 下午5.26.55](https://hackmd.io/_uploads/BysugI3Kxx.png) each ARDS data ![截圖 2025-08-27 下午5.33.53](https://hackmd.io/_uploads/S1x7WLnKll.png) 畫成圖長這樣 ![截圖 2025-08-27 下午4.43.05](https://hackmd.io/_uploads/B1VSHB2tee.png) # Enable Memory Paging >[!Note]Why two-level paging? https://www.youtube.com/watch?v=Z4kSOv49GNc <iframe width="560" height="315" src="https://www.youtube.com/embed/Z4kSOv49GNc?si=Of8ZMbLPW1HglkJ4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> >Reference: >[\[OSDev\] Paging](https://wiki.osdev.org/Paging#Page_Directory) >[\[80386 Manual\] Page Translation](https://pdos.csail.mit.edu/6.828/2005/readings/i386/s05_02.htm) >[!Note] Linear Address >![截圖 2025-09-10 下午2.47.18](https://hackmd.io/_uploads/Bylfks0qeg.png) >[!Note] Translation >![截圖 2025-09-10 下午2.45.56](https://hackmd.io/_uploads/BJCTAqAcxe.png) >[!Note] Page Directory Entry (PDE) / Page Table Entry (PTE) >![截圖 2025-09-10 下午5.01.35](https://hackmd.io/_uploads/H1P2CnAqee.png) | Column | Name | Description | | ------ | ------------------------ | ------------------------------------------------------------------- | | P | Present | 0x0: in memory<br> 0x1: Not in memory | | RW | Read/Write | 0x0: read only<br> 0x1: read and write | | US | User/Supervisor | 0x0: access only for Ring 0-2<br> 0x1: all Ring can access | | PWT | Page-Level write-through | 0x0: No write-through<br> 0x1: write-through | | PCD | Page-Level cache disable | 0x0: cache enable<br> 0x1: cache disable | | A | Accessed | 0x0: Never accessed by CPU<br> 0x1: Already Accessed by CPU | | D | Dirty | 0x0: page has NOT been written to<br> 0x1: page has been written to | | PAT | Page attribute table | it is reserved and must be set to 0. | | G | Global | 0x0: Not in TLB<br> 0x1: in TLB | | AVL | Available | Reserved for OS. | >[!Note] GDT and Paging: the CPU does perform a check on the GDT before paging >![fig5-2](https://hackmd.io/_uploads/Sy78SHQceg.gif) >[!Note] 啟動分頁機制 >1. 準備好分頁目錄表和分頁表。 >2. 將分頁目錄表的實體位置寫入 Register CR3。 >3. Register CR0 PG 的位置設置為 1。 >[!Note] Set Page Directory Address >![contol](https://hackmd.io/_uploads/r1gWTZZysxl.jpg) >[!Note] enable paging CR0[31:31] >![upload_82e918df4e82582142f0f74198a6b5c9](https://hackmd.io/_uploads/HkHMo4E5gx.jpg) >[!Note] Memory Layout >![截圖 2025-08-30 下午2.00.30](https://hackmd.io/_uploads/rkljmzx5xl.png) > 一般規劃 `0xC000_0000` - `0xFFFF_FFFF` 這 1 GB 的空間為 kernel space。 >![截圖 2025-09-12 下午5.54.18](https://hackmd.io/_uploads/S1Hl0Dbiee.png) >`0xFFC0_0000` 用來存取 page table,`0xFFFF_F000` 用來存取 page directory。 ## Source Code https://github.com/srhuang/a-os/commit/501b06fdb0382c4f53c4b0ead40e4b4f9900cb76 ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-30 下午4.49.23](https://hackmd.io/_uploads/HybG3Ne5xe.png) 說明一下上面 paging table 顯示的結果: 1. `0x0000_0000` - `0x000F_FFFF`: Kernel Space 1 MB 2. `0xC000_0000` - `0xC00F_FFFF`: Kernel Space 1 MB >[!Tip]以下是把 PDE 當成 PTE 來看待了 4. `0xFFC0_0000` - `0xFFC0_0FFF`: PDE 0 5. `0xFFF0_0000` - `0xFFFF_EFFF`: PDE 768 - 1022 6. `0xFFFF_F000` - `0xFFFF_FFFF`: PDE 1023 # Update GDT ## Source Code https://github.com/srhuang/a-os/commit/3ec8bc02dee2a164a94bdecf421f87785e52a9a8 ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-30 下午6.15.49](https://hackmd.io/_uploads/B161gLxqee.png) ![截圖 2025-08-31 凌晨2.06.06](https://hackmd.io/_uploads/S1A2aheqle.png) # Kernel Preparation ## Source Code ```c= int main(void) { while(1); return 0; } ``` ## Compile ```sh x86_64-linux-gnu-gcc -m32 -c -o build/main.o kernel/main.c x86_64-linux-gnu-ld -m elf_i386 build/main.o -Ttext 0xc0001000 -e main \ -o out/kernel.bin ``` ## Put on hard disk ```sh dd if=../code/out/kernel.bin of=./60mb.img bs=512 count=200 seek=5 conv=notrunc ``` >[!Tip] kernel.bin 最大支援 100 KB ## Checkpoint ![截圖 2025-08-31 凌晨12.43.12](https://hackmd.io/_uploads/rk7johg9el.png) ```sh xxd -u -a -g 1 -s 0 -l 300 out/kernel.bin ``` ![截圖 2025-08-31 凌晨2.59.50](https://hackmd.io/_uploads/S1l8qTe9xx.png) ```sh readelf -e out/kernel.bin ``` ![截圖 2025-08-31 凌晨3.01.48](https://hackmd.io/_uploads/H192calcxg.png) # Load Kernel ![截圖 2025-08-31 凌晨2.11.58](https://hackmd.io/_uploads/SyEMJpe9gg.png) ## Source Code https://github.com/srhuang/a-os/commit/d6f2cf3565a62f61c879c140a1d5cbfd367234a1 ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-31 凌晨2.56.57](https://hackmd.io/_uploads/S1p-5ax5lg.png) # Jump to Kernel >Reference: [Linux manual page](https://man7.org/linux/man-pages/man5/elf.5.html) :::info ELF Size ```c= typedef uint16_t Elf32_Half; typedef uint32_t Elf32_Addr; typedef uint32_t Elf32_Off; typedef uint32_t Elf32_Word; ``` ::: :::info ELF header >Reference: [ELF Header](https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html) ```c= struct Elf32_Ehdr { unsigned char e_ident[EI_NIDENT]; // ELF Identification bytes Elf32_Half e_type; // Type of file (see ET_* below) Elf32_Half e_machine; // Required architecture for this file (see EM_*) Elf32_Word e_version; // Must be equal to 1 Elf32_Addr e_entry; // Address to jump to in order to start program Elf32_Off e_phoff; // Program header table's file offset, in bytes Elf32_Off e_shoff; // Section header table's file offset, in bytes Elf32_Word e_flags; // Processor-specific flags Elf32_Half e_ehsize; // Size of ELF header, in bytes Elf32_Half e_phentsize; // Size of an entry in the program header table Elf32_Half e_phnum; // Number of entries in the program header table Elf32_Half e_shentsize; // Size of an entry in the section header table Elf32_Half e_shnum; // Number of entries in the section header table Elf32_Half e_shstrndx; // Sect hdr table index of sect name string table }; ``` ```asm= ;------------------------ ; ELF header ;------------------------ E_IDENT equ 0 E_TYPE equ 16 E_MACHINE equ 18 E_VERSION equ 20 E_ENTRY equ 24 E_PHOFF equ 28 E_SHOFF equ 32 E_FLAGS equ 36 E_EHSIZE equ 40 E_PHENTSIZE equ 42 E_PHNUM equ 44 E_SHENTSIZE equ 46 E_SHNUM equ 48 E_SHSTRNDX equ 50 E_END equ 52 ``` ::: :::info Program Header >Reference: [Program Header](https://refspecs.linuxbase.org/elf/gabi4+/ch5.pheader.html) ```c= struct Elf32_Phdr{ Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr; ``` ```asm= ;------------------------ ; Program header ;------------------------ P_TYPE equ 0 P_OFFSET equ 4 P_VADDR equ 8 P_PADDR equ 12 P_FILESZ equ 16 P_MEMSZ equ 20 P_FLAGS equ 24 P_ALIGN equ 28 P_END equ 32 ``` | Value | Name | Meaning | -- | -- | -- | `0` | `PT_NULL` | Unused entry | `1` | ==PT_LOAD== | Loadable segment (instructions, data, etc.) | `2` | `PT_DYNAMIC` | Dynamic linking information | `3` | `PT_INTERP` | Path to program interpreter | `4` | `PT_NOTE` | Auxiliary information (notes) | `5` | `PT_SHLIB` | Reserved (unspecified) | `6` | `PT_PHDR` | Program header table itself | `0x70000000` | `PT_LOPROC` | Start of processor-specific range | `0x7fffffff` | `PT_HIPROC` | End of processor-specific range ::: >[!Note] Check kernel.bin ``` readelf -h -l out/kernel.bin ``` ![截圖 2025-08-31 晚上11.40.11](https://hackmd.io/_uploads/ry4bayf5xl.png) ``` xxd -u -a -g 1 -s 0x0 -l 0xf4 out/kernel.bin ``` ![截圖 2025-08-31 晚上11.41.44](https://hackmd.io/_uploads/HJEUaJM9xx.png) >[!Note] Check Program Header ``` xxd -u -a -g 1 -s 0x1000 -l 0x14 out/kernel.bin xxd -u -a -g 1 -s 0x2000 -l 0x48 out/kernel.bin xxd -u -a -g 1 -s 0x2ff4 -l 0xc out/kernel.bin ``` ![截圖 2025-08-31 晚上11.44.00](https://hackmd.io/_uploads/HJkyAJGcee.png) >[!Note] kernel.bin ![截圖 2025-08-31 晚上11.58.05](https://hackmd.io/_uploads/ry-EWxMqex.png) >[!Note] Instructions for Memory Copy >![截圖 2025-09-01 凌晨12.46.32](https://hackmd.io/_uploads/rkWq3lMcgx.png) >![截圖 2025-09-01 凌晨12.47.50](https://hackmd.io/_uploads/B19AngM5ex.png) >![截圖 2025-09-01 凌晨12.50.41](https://hackmd.io/_uploads/BJ1YaxG5xg.png) >If DF is cleared, the pointers move from lower memory addresses to higher memory addresses.If DF is set, the pointers move from higher memory addresses to lower memory addresses. ## Source Code https://github.com/srhuang/a-os/commit/1d4480e14edfd0f7ae75e901aacad1a3c7baf5a0 ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=4 seek=1 conv=notrunc ``` ## Checkpoint 程式最後停在 kernel space。 ![截圖 2025-09-01 凌晨2.52.28](https://hackmd.io/_uploads/Hkfm9zfqll.png) ![截圖 2025-09-01 凌晨2.53.32](https://hackmd.io/_uploads/ry2r5Mf9xe.png) ![截圖 2025-09-14 下午4.18.06](https://hackmd.io/_uploads/H1Aj5xNiel.png) # Give me more ## Privilege Level Switching CPU 特權等級的轉換,這部分在未來實作使用者程序時會用到。建議這部分一定要很熟悉,不然後面實作時會很困惑。 [Privilege Level Switching](https://hackmd.io/@srhuang/BJO73NE5le) ## CPU Multitasking CPU 硬體支援的多工切換,但因為耗費成本太高,現代作業系統皆不採用此方式。時間有限的話,可以跳過這部分。 [CPU Multitasking](https://hackmd.io/@srhuang/SJqVDRtqlx)