# 2025 交大 OSC 紀錄


## Lab0

因為是跨平台開發 ARM 機器，所以要用到 Cross compiler 開發，並且使用自己的 Linker 把寫的程式弄到特定記憶體位置。

中間有 Assembler，裡面會有 .data, .text 等，寫組合語言時，檔案裡會有兩種東西：

* CPU 指令：例如 add, sub, syscall。這些會被轉換成 0101 的機器碼，讓 CPU 去執行
* 組譯器指示詞（Directives）：以點開頭（例如 .text, .data）。這些不是給 CPU 執行的指令，而是給「組譯器（Assembler）」看的排版說明書

在變成二進制的時候，使用雙階段掃描：

* 第一遍掃描時，組譯器先不急著把指令翻譯成二進位，它的核心任務是搞清楚每個標籤（Label）的記憶體位址。展開虛擬指令 (Expands pseudo instructions)：例如把好讀的 li（Load Immediate）展開成真正的硬體指令 addi。因為虛擬指令可能由多個真實指令組成，必須先展開，組譯器才能精確計算出每一行指令會佔用多少位元組（Bytes）
  * 記錄標籤位置 (Remember position of labels)：組譯器一邊讀，一邊在心裡數格子（追蹤 Program Counter/PC）
* 組譯器帶著剛剛做好的「符號表筆記本」，從頭開始讀第二遍。這時每個 Label 都知道記憶體位置了

![alt text](image.png)

![alt text](image-1.png)

然後在靠 Linker 解決要跳躍到「外部檔案的標籤」怎麼辦：

* 符號表 (Symbol Table)： 記錄這個檔案裡面提供了哪些全域變數或函式（例如：我這裡有 main 喔！），以及這個檔案裡面引用了哪些外部的黑盒子（例如：我這行需要 printf 和 delay_ms）
* 重定位表 (Relocation Table)： 這是一張「待辦清單」。組譯器會告訴 Linker：「老兄，我剛剛在 .text 段的第 24 個位元組（lui）和第 28 個位元組（addi）開了天窗。等你在記憶體裡把所有檔案和變數排好、知道確切位址後，記得回來幫我把這兩行指令的二進位欄位填滿！」

![alt text](image-2.png)

並且在編譯成 Object file 後，可以用 `objcopy` 把他變成純粹的 Binary file，就像是 ISO 檔案也是一個 Binary image，當安裝 ISO 檔案就是在目標硬碟上面建立分割區，將檔案「解開並寫入」成硬碟的二進位資料。

* 燒錄隨身碟時：當你用 dd 指令或 Rufus 把 ISO 寫進隨身碟時，你是把 ISO 這個「二進位映像」原封不動、逐個位元（Bit-by-bit）地複製到隨身碟上。隨身碟的開頭會有引導程式（Bootloader），後面會跟著檔案系統
* 安裝到硬碟時：系統安裝程式啟動後，它並不是直接把 ISO 的二進位複製到硬碟。相反地，它會在硬碟上格式化出新的檔案系統（例如 Ext4, NTFS），然後把 ISO 裡面包含的作業系統檔案（內核、驅動、應用程式）解包、複製過去

### 投影片

Process 使用 OS 要透過 ABI，OS 使用硬體要透過 Instruction。

Process 有 PCB，Thread 有 TCB。

中斷，CPU 有一個 INT PIN，所有中斷都要連上去這個針腳，但鍵盤、網卡（NIC）、滑鼠、硬碟等一堆裝置都要找 CPU，但 CPU 寶貴的 INT 針腳只有一個。怎麼辦？ 硬體工程師在中間加了一個「中斷控制器」（Interrupt Controller，例如古早的 8259A 或現代的 APIC）當作管理員。所有裝置的線都接到中斷控制器上，中斷控制器再用一條線接到 CPU 的 INT 針腳。

1. 你敲了鍵盤，鍵盤透過它專屬的總線控制線（Bus control line）向「中斷控制器」發出訊號
2. 中斷控制器收到後，轉身去拉高 CPU 的 INT Pin 電壓
3. CPU 偵測到 INT Pin 被觸發了，它會立刻存下目前正在執行的程式狀態（Context Switch），然後透過資料總線反問中斷控制器：「是誰在按電鈴？」
4. 中斷控制器回答：「是 1 號（假設 1 代表鍵盤）」。這個號碼在 OS 裡通常被稱為 IRQ (Interrupt Request) 或中斷向量（Interrupt Vector）

中斷：Software Trap vs Hardware Interrupt vs Software Exception

當遇到例外：

1. CPU 在跳轉去求救之前，必須先把「證據」留下來。它會把這條犯錯指令的記憶體位址（PC，Program Counter）以及犯錯的原因（Reason / Cause），自動寫入到 CPU 內部的特權暫存器（Privileged Registers）中。以 RISC-V 架構為例，這兩個暫存器就是 sepc（Exception PC）和 scause（Cause）
2. 為了防止處理到一半被其他硬體干擾，CPU 會先關閉中斷（disable interrupts），然後硬體會強行把 PC 切換到作業系統在開機時就註冊好的 Exception Handler（例外處理程式的進入點），正式把控制權交給 OS Kernel
3. 如果這個錯誤是「能救的」，例如分頁錯誤（Page Fault，程式想存取的資料還在硬碟裡，還沒載入記憶體）。作業系統會默默地把資料從硬碟讀到記憶體排好，然後執行一個特權回傳指令（如 RISC-V 的 sret）。CPU 收到後會調回原本的 User 權限，並回到最初這條指令重新執行一次。因為 OS 在背後把事情做完了，原本的程式完全不會察覺到自己剛剛被中斷過，這就叫做透明的（Transparent）

## Lab1

因為是 Bare Metal 環境，所以使用創建自己的 Bootloader，自己做 C Runtime 會作的事情，像是設定 SP, 清空 BSS。

然後 RPi3 有兩個 UART

* PL011 UART: 完整 UART
* Mini UART: 簡單 UART

通常 PL011 常被藍牙拿走，然後在設定 GPIO，因為 GPIO 的 Pin Multiplexing 會讓 GPIO 可以對應很多不同硬體，所以比需設定 GPIO 讓現在 IN/OUT 針腳 for Mini UART，我們這邊使用 GPIO 14 and 15 腳位。

然後在設定 Mailbox，[Mailboxes 是 ARM Cores 與 VideoCore GPU 間溝通的橋樑，透過 Mailboxes 可以設定 framebuffer 或是設定一些周邊元件](https://blog.ykzheng.com/series/nctu/osdi/lab2/mailbox/)，這邊的 Lab 用 Mailbox 獲得硬體資訊，列印出電路板版本、ARM 記憶體基底位址和大小。

| 機制        | 用途                     | 是否通用 |
| ----------- | ------------------------ | -------- |
| Mailbox     | ARM <-> GPU              | 否       |
| MMIO        | 控制硬體 register        | 是       |
| Device Tree | 描述硬體                 | 是       |
| SMC/HVC     | 安全/虛擬化              | 是       |
| Interrupt   | 事件通知                 | 是       |
| DMA         | 高速資料搬移             | 是       |

## Lab2

樹莓派硬體上沒有實體重設鈕。程式碼範例透過寫入 BCM2835 的電源管理暫存器（PM_RSTC 和 PM_WDOG），刻意觸發看門狗計時器（Watchdog Timer）逾時，藉此完成硬體重置（Full Reset）。

寫一個微型引導程式（Bootloader）燒進 SD 卡，在電腦端（Host）寫一個 Python 腳本，透過序列埠（UART）將你真正要測試的 kernel8.img 傳過去。

核心剛開機，此時你還沒有寫 SD 卡的驅動程式與檔案系統，核心抓不到任何檔案。利用 Linux 內建的 cpio 工具將測試用的純文字檔打包成一個 initramfs.cpio 映像檔，在核心中寫一個 Cpio 格式解析器（Parser），透過讀取 cpio_newc_header 結構體（注意 4-byte 對齊機制），讓核心能根據路徑讀取裡面的檔案內容。

硬寫死（Hardcode）硬體周邊的 IO 記憶體位址會讓核心喪失可移植性，此 Lab 同樣實作一個 DTB (Flattened Devicetree) 二進位檔案解析器。

## Lab3

參考 Armv8-A 的異常模型，有四個 Exception level，開機在 EL2，Kernel 在 EL2，User program 在 EL1。

Exception 發生時 CPU 會自動：

* 存 PC → ELR_ELx
* 存 PSTATE → SPSR_ELx
* 關 interrupt
* 跳到 vector table

我們要自己建立 Vector Table，裡面會分成

* Sync exception
* IRQ
* EL0 -> EL1
* EL1 -> EL1

因為 Handler 會破壞 Register，所以我們要自己 save all x0-x30/sp register。

我們要實作 Core timer/mini UART interrupt。

## Lab4

Buddy system allocator

## Lab5

學習 User thread，每個 Thread 有自己的 Register snapshot，會有一個 switch_to 函式 save current registers
並且 load next registers。

還有實作自己的 Scheduler，什麼都沒有工作的時候就是 Idle Thread。

也要實作 Syscall，使用 `svc` 進到 Kernel，進到 Kernel 要儲存 Register，這邊叫做 Trap Frame。

最難的 Syscall 是 `fork`，因為要從同一個地方分成兩個 Thread。

然後會給一個 Video Player 測試播放影片。

## Lab6

開啟 MMU，拓寫 Page Table，分成 User/Kernel Space，每個 Process 一個 PGD，並且實作 `mmap` syscall。

還有實作 Demand Paging and Copy-On-Write (COW)。

## Lab7

拓寫 VFS。

## Reference

* [Lab](https://nycu-caslab.github.io/OSC2025/labs/lab0.html)
* [Lecture](https://people.cs.nycu.edu.tw/~ttyeh/course/2024_Spring/IOC5226/outline.html)

