Chapter 03:Loader
===
:::info
Loader 有以下主要任務:
  1. 檢測記憶體。
  2. Enable Protected Mode (開啟保護模式)。
  3. Enable Memory Paging (開啟分頁)。
  4. Load Kernel。
  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年才停產。

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 就成為最終的定址空間。

所以下一個問題就會是 CPU 怎麼透過 selector 來取得 base address?
透過 `LGDT` CPU instruction。它是個 48 bits register,透過設定這個 register,CPU 藉此載入整張 GDT。`LGDT` 是 特權指令,只能在 CPL = 0 (Ring 0) 執行。


那下個問題就會是 GDT 到底長怎樣?
每一個 GDT element 共有 64 bits。

你會不會好奇為什麼要把 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 |

最後是我們會透過設定 segment register(16 bits) 來決定要套用哪個 GDT element。

>[!Tip] TI = Table Indicator
x86 CPU level privilege control,原生的 CPU 有提供四個等級,但是作業系統(如 Linux)只使用其中的兩個:Ring 0 for kernel space;Ring 3 for user space。
==DPL 就是根據此概念做設定==

### 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。

### Enable Protected Mode
終於要進入最後一步,開啟 CPU Protected Mode,方法也很簡單,就是直接操控 CPU control register CR0。

## Memory Layout

## 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

# Print Log
>Reference: https://bochs.sourceforge.io/techspec/PORTS.LST





## 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

# 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)

* 0x88: 最大支援 64MB
* 0xe801: 最大支援 4GB
* 0xe820: 檢查全部記憶體
>[!Note]為了簡化,我就只實作 0xe820
## 利用 BIOS Interrupt 0x15/0xE820 取得實體記憶體
利用資料結構(ARDS)來描述記憶體資訊,主要步驟如下:
* 寫好呼叫前輸入的暫存器資訊。
* 執行中斷呼叫 int 0x15
* 檢查暫存器的結果,ARDS會存放在記憶體中。
>[!Note] Address Range Descriptor Structure (ARDS)
>
>
## 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

each ARDS data

畫成圖長這樣

# 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
>
>[!Note] Translation
>
>[!Note] Page Directory Entry (PDE) / Page Table Entry (PTE)
>
| 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
>
>[!Note] 啟動分頁機制
>1. 準備好分頁目錄表和分頁表。
>2. 將分頁目錄表的實體位置寫入 Register CR3。
>3. Register CR0 PG 的位置設置為 1。
>[!Note] Set Page Directory Address
>
>[!Note] enable paging CR0[31:31]
>
>[!Note] Memory Layout
>
> 一般規劃 `0xC000_0000` - `0xFFFF_FFFF` 這 1 GB 的空間為 kernel space。
>
>`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

說明一下上面 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


# 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

```sh
xxd -u -a -g 1 -s 0 -l 300 out/kernel.bin
```

```sh
readelf -e out/kernel.bin
```

# Load Kernel

## 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

# 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
```

```
xxd -u -a -g 1 -s 0x0 -l 0xf4 out/kernel.bin
```

>[!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
```

>[!Note] kernel.bin

>[!Note] Instructions for Memory Copy
>
>
>
>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。



# 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)