---
# System prepended metadata

title: Chapter 03：Loader

---

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)