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 都知道記憶體位置了


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

並且在編譯成 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 針腳。
- 你敲了鍵盤,鍵盤透過它專屬的總線控制線(Bus control line)向「中斷控制器」發出訊號
- 中斷控制器收到後,轉身去拉高 CPU 的 INT Pin 電壓
- CPU 偵測到 INT Pin 被觸發了,它會立刻存下目前正在執行的程式狀態(Context Switch),然後透過資料總線反問中斷控制器:「是誰在按電鈴?」
- 中斷控制器回答:「是 1 號(假設 1 代表鍵盤)」。這個號碼在 OS 裡通常被稱為 IRQ (Interrupt Request) 或中斷向量(Interrupt Vector)
中斷:Software Trap vs Hardware Interrupt vs Software Exception
當遇到例外:
- CPU 在跳轉去求救之前,必須先把「證據」留下來。它會把這條犯錯指令的記憶體位址(PC,Program Counter)以及犯錯的原因(Reason / Cause),自動寫入到 CPU 內部的特權暫存器(Privileged Registers)中。以 RISC-V 架構為例,這兩個暫存器就是 sepc(Exception PC)和 scause(Cause)
- 為了防止處理到一半被其他硬體干擾,CPU 會先關閉中斷(disable interrupts),然後硬體會強行把 PC 切換到作業系統在開機時就註冊好的 Exception Handler(例外處理程式的進入點),正式把控制權交給 OS Kernel
- 如果這個錯誤是「能救的」,例如分頁錯誤(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 或是設定一些周邊元件,這邊的 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。