Chapter 02:MBR
===
:::info
Master Boot Record(MBR),一切軟體的起點,千里之行,始於足下。
MBR 主要有兩個任務:
  1. Load loader。
  2. Jump to loader。
:::
>[time=Sun, Aug 17, 2025 12:04 AM]
---
https://youtu.be/emxQnZTVUTo
<iframe width="560" height="315" src="https://www.youtube.com/embed/emxQnZTVUTo?si=Db742bB-u822GDyK" 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>
# Background Knowledge
首先,先補充一些非常基礎的背景知識~
## Von Neumann Architecture
==1945.06.30 由美籍猶太人 Von Neumann 所提出==,乃至今日的 Computer Science 都是以此架構為基礎。CPU 都是從 memory 提取指令來執行,並且按照順序沿著 memory 從低位址到高位址循序執行。除此之外,指令本身可以要求 CPU 去讀取 device 資料,並且寫資料到 device。CPU 都是先把資料放在內部 register,然後再進行運算。

這邊推薦一個遊戲「Human Resource Machine」。
這款遊戲會讓你對於此架構,乃至於整個 CPU 的運作更加印象深刻。
<iframe width="560" height="315" src="https://www.youtube.com/embed/vHhlwPee6zA?si=vHrBswDr3f59qv-g" 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>
## CPU Architecture
可以想像一台家用電器,它的功能在出廠的時候就已經決定一切了,使用者能做的就是透過提供的介面按鈕去設定和操控家用電器。CPU 也是如此,晶片出廠的時候線路設計都已經大致抵定,差別是我們軟體可以透過 machine language(機器語言) 去操控 CPU 硬體去完成特定任務。但是 machine language 是由一群 01 組成的,非常不利於人類閱讀和理解,因此出現 assembly language(組合語言) 方便程式設計師開發,而這中間的轉換過程,就是靠 assembler(組譯器) 來協助完成。

目前世界上有兩個主流的 CPU Architecture: x86(CISC) and ARM(RISC)。可以想像這兩大 CPU 架構的操作方式是截然不同的,因此他們的 assembly language and assembler 也是不同的。
* x86 使用 ==nasm==
* ARM 使用 ==armasm==
>[!Note]本次專案專注於 x86 CPU architecture。
## Assembly Language (x86)
> Reference: [x86 Assembly Guide](https://www.cs.virginia.edu/~evans/cs216/guides/x86.html)
* section/label: 僅是在邏輯上供開發人員方便整理程式之用。
* vstart: 並不是真正載入的位置,而是告訴編譯器,後面所有的指令都從 vstart 開始編吧!編譯器只負責編址,不具備載入的功能。
* align=16: 保證編譯出來的位址是 16 的整數倍,換句話說,最右邊 4 bits 一定為 0。
* DB - Define 1 byte.
* DW - Define 2 bytes.
* DD - Define 4 bytes.
* DQ - Define 8 bytes.
```asm=
section aaa vstart=0x1234
mov ax, $$
mov ax, section.bbb.start
mov ax, [var1]
mov ax, [var2]
jmp $
section ddd align=16
mov ax, $$
mov ax, section.aaa.start
ccc:
jmp ccc
section bbb
var1 dw 0x9527
var2 dd 0x12345678
```
```
nasm test.s -o test.o
ndisasm test.o
```

## Instruction Set (x86)
> Reference:
> [Wiki](https://en.wikipedia.org/wiki/X86_instruction_listings)
> [Intel 官方手冊](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
> [有人整理成網頁](https://c9x.me/x86/)
這邊要先提醒一點,==CPU 的運算主要是 CPU 內部的 registers==,因此資料要先從外部裝置載到 register 上,運算完後再儲存回外部裝置。

這邊列出要完成 MBR 所需的 CPU instructions。
### Data Movement



### Arithmetic/Logic






### Control Flow







## CPU Registers (x86)
> Reference: https://wiki.osdev.org/CPU_Registers_x86
### General-Purpose Registers

### Segment Registers

### Status Register (8086)

### Control Register

## Memory Addressing
Memory Addressing(記憶體定址) 是指 CPU 存取記憶體中資料的方法,每個記憶體位置都有一個唯一的編號,稱為記憶體位址,就像是資料的門牌號碼。Address bus(位址匯流排) 的寬度決定了可以定址的範圍。例如:
| Address Bus | Memory Address Range
| - | -
| 1 bit | 2 ($2^1$)
| 2 bits | 4 ($2^2$)
| 4 bits | 16 ($2^4$)
| 6 bits | 64 ($2^6$)
| 8 bits | 256 ($2^8$)
| 10 bits | ==1K ($2^{10}$)==
| 16 bits | 64K ($2^{16}$)
| 20 bits | ==1M ($2^{20}$)==
| 24 bits | 16M ($2^{24}$)
| 30 bits | ==1G ($2^{30}$)==
| 32 bits | 4G ($2^{32}$)
## 讓我們回到 Intel 8086 (1978年)
要了解 x86 MBR 就必須沿著歷史脈絡回到當初 x86 系列最輝煌的時刻:Intel 8086(1978年)。因為很多我們現今看起來有點奇怪的設計,都是當時的時代背景所造成的,所以我們必須回到當時的時空背景,才能更深刻的瞭解 x86 MBR 的設計。Intel 8086 是由英特爾公司於1976年初開始設計,1978年年中發表的Intel第一款 16 位元微處理器。8086 是 Intel 最成功的 x86 架構處理器系列的開端。

## Memory Segmentation (8086)
首先,最簡單單純的做法就是根據 CPU address bus(位址匯流排) 所支援的寬度直接記憶體定址。
但是這種做法會有什麼問題呢?
如果 CPU memory addressing 沒有 Segmentation(分段)的概念,編譯過後的程式,遇到指令中含有位址的數字,==只能用絕對實體位址來看待它==,因此整個程式必須放在記憶體中固定的地方;如果放到記憶體的其他地方,會導致存取到錯誤的絕對實體位址。
因此為了方便記憶體管理以及充分使用記憶體,Intel 8086(1978年)開始導入 memory segmentation 技術。CPU 對於編譯後的程式,==有能力以相對實體位址來看待它==。

記憶體容量當時正在從 KB 轉成 MB 的時代,所以 8086 為了能夠支援最大 1MB 的記憶體定址能力,address bus 從 16 bits 擴充到 20 bits。這也是為何 segment base address 需要左移 4 bits,如此才能讓 ==CPU 最大定址空間提升到 1 MB==。
記憶體發展史:
| 時代 | 個人電腦典型記憶體 | 技術 |
| ------ | --------------------------- | ----------------- |
| 1950s | 幾 KB | 磁芯記憶體 |
| 1970s | 4–64 KB | DRAM 問世 |
| 1980s | 256 KB–4 MB | IBM PC、640 KB 限制 |
| 1990s | 4–128 MB | Windows 95/98 |
| 2000s | 256 MB–4 GB | DDR, DDR2 |
| 2010s | 4–16 GB | DDR3, DDR4 |
| 2020s | 16–64 GB (PC),12–16 GB (手機) | DDR5, LPDDR5, HBM |
| 2030s? | 128 GB+ (PC),32 GB+ (手機) | DDR6, 3D 堆疊記憶體 |
## Memory Map (8086)
>Reference: [Memory Map (x86)](https://wiki.osdev.org/Memory_Map_(x86))
典型的 Intel 8086 記憶體分配如下:
| 位址範圍 (Hex) | 大小 | 用途 |
| ----------------- | ------ | ----------------------------------------|
| `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**|
| `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**|
| `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)**|
| `0x0_0000 – 0x9_FFFF` | 640 KB | **可用 RAM** (程式與資料)|

Intel 8086 架構實際把這 1 MB 切成 640 KB RAM + 384 KB I/O/ROM,形成「==640 KB memory barrier==」。因此就實務上而言,8086+DOS 1.0 可以搭載的 RAM 大小為 ==32 KB~640 KB==。
## Power Reset (8086)
當 8086/8088 重置 (RESET) 時:
CS = `0xF000`
IP = `0xfff0`
實體位址 = CS × 16 + IP = `0xf_fff0`
👉 也就是 1 MB 空間的最後 16 bytes(`0xf_fff0 ~ 0xf_ffff`),CPU 從這裡開始執行。
這段區域通常在 BIOS ROM 裡,只會放一個跳躍指令,跳到 BIOS 主要程式。
```
jmp f000:e05b
```
## BIOS (8086)
> Reference: [bochs BIOS source code](https://github.com/ipxe/bochs/blob/master/bios/rombios32.c)
>[!Note] bochs 採用 x86 BIOS open source: SeaBIOS
>Reference:
>[Wiki](https://en.wikipedia.org/wiki/SeaBIOS)
>[SeaBIOS](https://www.seabios.org/downloads/)
1. BIOS 會進行 POST (Power-On Self Test),檢查記憶體、硬體。
2. 建立中斷向量表 Interrupt Vector Table(IVT)。
3. 接著 BIOS 會搜尋可開機裝置(磁碟、磁帶、ROM 卡)。
4. 若是磁碟開機,BIOS 會去讀取 磁碟第 0 磁區 (MBR, Master Boot Record)。
5. 將 MBR 程式載入記憶體位址 `0x7c00`,並且跳到該處開始執行 MBR 程式。
## BIOS IVT (8086)
>Reference: [BIOS 中斷呼叫](https://zh.wikipedia.org/zh-tw/BIOS%E4%B8%AD%E6%96%B7%E5%91%BC%E5%8F%AB)
BIOS 會在 `0x000 - 0x3FF` 處建立中斷向量表 IVT,並填寫中斷常式。
可支援 256 個中斷,每個中斷儲存著 4 bytes 中斷常式地址,總共 1 KB。
IVT 的內容大概長這樣:
| 中斷號 | Handler CS:IP | 功能 |
| --- | -------------------| ----------------------- |
| 00h | F000:FF53 | 除零錯誤 (Divide Error) |
| 01h | F000:FF53 | 單步除錯 (Debug Exception) |
| 02h | F000:FF53 | NMI 非屏蔽中斷 |
| 03h | F000:FF53 | Breakpoint (INT3) |
| 04h | F000:FF53 | Overflow (INTO) |
| 05h | F000:FF53 | Print Screen / Bound |
| 06h | F000:FF53 | 無效 Opcode |
| 07h | F000:FF53 | Device Not Available |
| 08h | F000:FEA5 | IRQ0 系統計時器 |
| 09h | F000:E987 | IRQ1 鍵盤 |
| 0Ah | F000:E9DF | IRQ2 (PIC 級聯) |
| 0Bh | F000:E9DF | IRQ3 (COM2/COM4) |
| 0Ch | F000:E9DF | IRQ4 (COM1/COM3) |
| 0Dh | F000:E9DF | IRQ5 (LPT2/Sound) |
| 0Eh | F000:EF57 | IRQ6 (Floppy) |
| 0Fh | F000:E9DF | IRQ7 (LPT1) |
| 10h | C000:014A | BIOS 視訊服務 |
| 11h | F000:F84D | BIOS 設備清單 |
| 12h | F000:F841 | BIOS 記憶體大小 |
| 13h | F000:E3FE | BIOS 磁碟服務 |
| 14h | F000:E739 | BIOS 串列埠服務 |
| 15h | F000:F859 | BIOS 系統服務 (A20, etc.) |
| 16h | F000:E82E | BIOS 鍵盤服務 |
| 17h | F000:EFD2 | BIOS 印表機服務 |
| 18h | F000:969B | ROM BASIC |
| 19h | F000:E6F2 | 開機載入 (Bootstrap) |
| 1Ah | F000:FE6E | RTC / 系統時鐘 |
| 1Bh | F000:FF53 | Ctrl-Break Handler |
| 1Ch | F000:FF53 | Timer Tick Hook |
BIOS 跑起來建立完 IVT 後,RAM 會長這樣:
| 位址範圍 (Hex) | 大小 | 用途 |
| ----------------- | ------- | ----------------------------------------|
| `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**|
| `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**|
| `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)** |
| `0x0_0500 – 0x9_FFFF` | 638.75 KB | **可用 RAM** (程式與資料)|
| `0x0_0400 - 0x0_04FF` | 256 B | ==BDA (BIOS data area)==|
| `0x0_0000 - 0x0_03FF` | 1 KB | ==BIOS IVT==
## 0x7C00 的由來
個人電腦一定要有個作業系統來完成任務,而當時(1981年) IBM PC 搭配 Intel 8086 的作業系統是 DOS 1.0,而 ==DOS 1.0 最低記憶體需求為 32 KB==,因此當時的 BIOS 就是以最低 32 KB RAM 大小來設計的。MBR 為了避免過早被其他程式覆蓋,所以 MBR 盡可能地放在 RAM 的頂端。雖然 MBR 本身只有 512 B 大小,但是要為堆疊預留一點空間,所以當初設計在 32 KB 的頂端預留 1 KB 給 MBR 使用,32 KB - 1 KB(`0x8000 - 0x400 = 0x7C00`),這就是為何 BIOS 設計成將 MBR 載入到 `0x7C00` 的原因,為了 x86 的相容,此設計也就保留下來了。

BIOS 成功載入 MBR 後,RAM 會長這樣:
| 位址範圍 (Hex) | 大小 | 用途 |
| ----------------- | ------ | ----------------------------------------|
| `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**|
| `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**|
| `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)**|
| `0x0_8000 - 0x9_FFFF` | 608 KB | **可用 RAM** (程式與資料) |
| `0x0_7E00 - 0x0_7FFF` | 512 B | ==MBR stack== |
| `0x0_7C00 - 0x0_7DFF` | 512 B | ==MBR== |
| `0x0_0500 – 0x9_FFFF` | 29.75 KB | **可用 RAM** (程式與資料)|
| `0x0_0400 - 0x0_04FF` | 256 B | ==BDA (BIOS data area)==|
| `0x0_0000 - 0x0_03FF` | 1 KB | BIOS IVT

## MBR Magic Number
>Reference: [Wiki](https://en.wikipedia.org/wiki/Master_boot_record)
BIOS 會去檢查每個一顆硬碟的第 0 sector 看有沒有 MBR 程式,檢查方式就是 512 bytes 最後兩個 byte 是不是 `0x55` 和 `0xaa`。
# Boot Disk
## 製作 Boot Disk
```
cd bochs-2.8/
./bximage
```

## 設定 bochsrc
```
cd bochs-2.8/
vim .bochsrc
```

## Checkpoint
>[!Caution] 這個錯誤是因為我們還沒製作 MBR。

# Load & Jump MBR
終於要開始 coding 了!這邊簡單設計一下我的檔案目錄架構。我在 shared folder 下新增一個目錄 `code/`,原因是如果你想要把 source code 從 Guest(Ubuntu) 抓到 Host(mac OS) 上的話相當方便,另外盡量把 source code 和 bochs 程式主體分開,原因是這樣也方便你用同一份 source code 跑在不同版本的 bochs 上面。

>[!Note] 理論上你會需要先安裝 tree 才能使用 tree command。
```
sudo apt install tree
```
`code/` 底下的架構規劃長這樣。
* boot: 所有 kernel 之前的 source code。
* out: 準備放到 hard disk 的 output bin file。

## Source code
https://github.com/srhuang/a-os/commit/cecfd3192b1404d724afe171d92e49f81737c63b
## Compile
```sh=
nasm boot/mbr.s -o out/mbr.bin
```
## Put on hard disk
```sh=
dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc
```
## Checkpoint


# MBR Print Log
> Reference:
> https://en.wikipedia.org/wiki/INT_10H
> https://en.wikipedia.org/wiki/BIOS_color_attributes
## Source code
https://github.com/srhuang/a-os/commit/5320745bf6a1eeace2d39c4c7d4fc0e54daa44b4
## Compile
```sh
nasm boot/mbr.s -o out/mbr.bin
```
## Put on hard disk
```sh
dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc
```
## Checkpoint

# Loader Preparation
在這一章節,我們只規劃一個極度簡單的 loader,目的是讓 MBR 能夠順利的跳轉到 loader,繼續往核心載入前進。
* Hard Disk:MBR 放在第 0 sector,所以我直接將 loader 放在第 1 sector。
* RAM:規劃放在盡可能低位址空間,原因是 loader 可能會長到很大,因此如果放在高位址空間,會很難掌握要預留多少,如果屆時超出預期,有可能要改動 loader 的起始位址,這不是我們所預期要發生的事。另外,也要預留一點低位址空間可以作為 loader stack,而 stack 是往低位址空間慢慢長大的。綜合以上考量,loader 會放在 `0x0800`,而且 top of stack 也是 `0x0800`,stack 最大支援空間為 `0.75 KB`,loader 最大支援空間為`30 KB`。

## Source code
https://github.com/srhuang/a-os/commit/b4915fd39ef4e9415c4072370cdb372b35aab77f
## 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=1 seek=1 conv=notrunc
```
# Load $ Jump Loader
MBR 最後的任務就是從 hard disk 載入 loader 到 RAM 上,然後跳轉過去執行 loader。首先我們要先學會如何操控 hard disk。
>Reference:
>[Bochs' map of I/O ports to functions](https://bochs.sourceforge.io/techspec/PORTS.LST)
>[osdev wiki](https://wiki.osdev.org/ATA_PIO_Mode#Addressing_Modes)





主要分成以下五大步驟:
* 設定 sector count,預計要讀取幾個 sector。
* 設定 hard disk LBA 地址。
* 寫入讀取指令。
* Polling 等待完成。
* 讀取資料到 RAM。
:::warning
另外要注意的是:
* port number 8 bits : 可以直接使用立即數。
* port number 16 bits : 必須使用 register dx。
* in/out 使用的 data register 只能是 al(8 bits) 或是 ax(16 bits)。
:::
## Source code
https://github.com/srhuang/a-os/commit/b4915fd39ef4e9415c4072370cdb372b35aab77f
## Compile
```
nasm boot/mbr.s -I boot/ -o out/mbr.bin
```
## Put on hard disk
```
dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc
```
## Checkpoint
