自幹作業系統 - 初探 Timer 原理
在 自幹作業系統 - Simple OS 的過程中,我的學習方法是快速的先取得成就感,透過與 AI 協作過程,快速刻出能動的、完整的,只用了三週就完成實作 (03/12 - 03/27),但是非常古早味的作業系統,說白話:先能動再說,好聽的就是 麻雀雖小,五臟俱全。但往下探索,很容易暴露一個現實:知其然,不知所以然,有些東西就是沒有透測的理解。但也是這份好奇心停留在心裡,我開始往下了解與深究背後的原理以及知識,同時也連結到以前的經驗與知識 (或者資訊)。
Day12 - Timer/PIT (Programmable Interval Timer) 是我開始深究的題目,底下是我好奇的幾個點:
整理一下探索過程學習到的資料,以及自問自答相關資料。
註:內容僅是自學的一些筆記,如果有發現資訊不正確,後者描述錯誤,請不令給予指教,感謝。
Timer in SimpleOS
計算機構成 Timer (計時器) 整個工作的角色有以下:
CPU: 中央處理單元,負責計算資料PIT: Programmable Interval Timer (可程式化間隔計時器),負責產生固定的頻率為1,193,180 Hz,通常是 INTEL 8253/8254 晶片,背後的物理原理是透過 石英振盪器 (Crystal Oscillator) 輸出固定的頻率PIC: Programmable Interrupt Control (可程式化中斷控制器),當外部硬體 (PIT、鍵盤、滑鼠、網卡) 發出中斷訊號時,負責執行仲裁角色,通常是 INTEL 8259 晶片OS: 作業系統,介於硬體與應用層之間的軟體。
註:PIT, PIC 這兩顆晶片在現代計算機裡,會被整合在主機板上的
南橋晶片組內,但邏輯與職責上還是分開的。
用一個例子來說明,這三個角色的運作:
- 主管 (CPU) 在負責對外溝通與協調,正在忙著開會中;而團隊成員 (其他硬體: 滑鼠、鍵盤) 有事要找他;秘書 (PIC) 則負責管理誰可以去敲老闆的門;行事曆 (PIT) 則是一個時間軸。
- 當整點時間到了 (PIT),秘書 (PIC) 會根據 來敲門的團隊成員 (滑鼠與鍵盤) 優先序,安排跟老闆 (CPU) 碰面。
- PIT 就是個固定計數器,一直在數數 (tick),一個單位時間的數值到了,就會跟 PIC 講,由 PIC 根據次序安排硬體跟 CPU 溝通。
SimpleOS 的 timer.c 有以下介面:
1 | void init_timer(uint32_t frequency); |
其中 init_timer() 負責出根據 PIT 的工作頻率與目標中斷次數 (frequency),計算出 tick 的最小單位,然後把 tick 最小單位的值寫到 PIT。而 timer_handler() 則是負責記錄每次中斷的 tick 累加。
整個 Timer 概念在 Simple OS 裡大概如下:

PIT - INTEL 8254
計數器晶片 INTEL 8254 負責產生的規律電子訊號,他的基礎頻率是 1.19318 MHz (1,193,180 Hz),頻率則透過 石英振蕩器生成 (Crystal Oscillator)。1,193,180 這個數字緣由是彩色電視 NTSC 的副載波頻率(14.31818 MHz)經過 12 分頻計算得來。在電子工程中,「分頻 (Frequency Division)」指的是將一個高頻率的訊號,轉換成較低頻率訊號的過程。簡單說「12 分頻」就是把原始頻率除以 12。
14.31818 MHz / 12 = 1,193,181 Hz
作業系統約定成俗的會使用 1,193,180 或 1,193,182 當常數
80 年代 (IBM PC 誕生初期) 工程師為了省錢,不想在主機板上插滿各種不同頻率的石英鐘,於是採用了「一魚多吃」策略:
- 主頻源:只買一個最便宜、大量生產的 14.31818 MHz 石英震盪器 (當時因為彩色電視普及,這種零件極度便宜)。
- 給顯卡用:這個頻率直接給彩色顯示卡使用,因為它剛好是 NTSC 電視訊號的 4 倍。
- 給 CPU 用:經過 3 分頻 (14.318 MHz / 3 = 4.77 MHz),這就是初代 IBM PC 處理器的時脈。
- 給計時器用:經過 12 分頻 (14.318 MHz / 12 = 1.19 MHz),這就是 8254 PIT 晶片拿到的工作頻率。
PIC - INTEL 8259
中斷控制器 (Programmable Interrupt Controller, PIC) 通常是 Intel 8259 這顆晶片,是介於所有硬體設備(如 PIT、鍵盤、滑鼠)與 CPU 之間的「通訊官」。PIC 的角色如同警衛,在 PIC 向 CPU 發出中斷訊號後,會自動擋住後續的所有硬體中斷訊號。如果 OS 不回覆 EOI (End of Interrupt) 指令給 PIC,PIC 會判定 CPU 仍在處理舊的中斷,導致 PIC 停止發送任何新的中斷訊號給 CPU,最終造成 OS 無法接收新的 Tick 或硬體事件,系統表現如同「當機」。
OS 透過發送 EOI (End of Interrupt) 指令告訴 PIC:
OS 已經處理完當前的中斷請求,PIC 可以發送下一個中斷訊號給 CPU 了。
完成告知之後,OS 就可以做所謂的 Context Switch,把 CPU 的使用權讓出去給其他的 Task,這個過程則是透過 Scheduler 執行。在 SimpleOS 的實作,是在 timer_handler() 裡面最後調用 schedule(),實踐 搶佔式多工 (Preemptive Multitasking),強行切換任務,達成「每個程式都在同時執行」的假象。
timer.c#init_timer() - 初始節拍器最小單位:Tick
tick (狀聲詞, 滴答聲) 用來記錄 作業系統 的 節拍數、虛擬時間,定義作業系統的節奏感,從開機就開始計算,每次的累加,都代表著作業系統時間的流逝。
實際的運作原理是利用 INTEL 8254 的基頻 - 1,193,180 Hz,我們想在每秒產生 100 次中斷 / 100 個 tick,在 timer.c#init_timer() 計算方式如下:
1 | void init_timer(uint32_t frequency) { |
PIT 每經過 1193.18 個震盪週期,就是一個 tick (一拍),每秒則有 100 個 tick。
這是一個 原子操作 或受 中斷保護 的操作,確保所有依賴時間的 SysCall (如 sleep) 都有統一的參考。所以 tick 在 c 語言裡需要宣告 volatile,保證 tick 可見性 (Visibility)。如果沒有它,在多工或編譯器優化下,一個 while(tick < target) 的無窮迴圈可能會因為編譯器認為 tick 在迴圈內沒被修改,而將其優化為死循環,永遠不重新讀取記憶體中的新值。
題外話:編曲軟體 (Digial Audio Workstation, DAW) 中常用的 Piano Roll 畫面,為了要可以紀錄更自然、更真實的演奏時間,每個拍子的單位不是只有固定在節奏數,而是有個單位稱為
PPQ (Pulses Per Quarter Note),例如 PPQ=360,代表一拍可以再細分 360 個等分,透過這樣的方式可以紀錄演奏更細緻的表情,像是琶音這種技巧。現代的 DAW 像是 Logic Pro、FL Studio 預設為 960 PPQ。下圖由 Google 產生
timer.c#timer_handler() - 作業系統的 Game Loop
timer.c#timer_handler() 每一個週期會被 IRQ32 中斷 interrupts.S 呼叫一次,整個內容最重要的幾件事:
- 累加 Tick,也就是現在是第幾個 Clock
- 告訴 中斷控制器,這個 Tick 結束,請重新分配 Task
- 讓 OS 透過 Task Scheduler 執行 Context Switch,重新分配 CPU
整個 Timer 運作流程如下:

timer_handler() 是透過 PIC 固定週期就被調用一次,概念類似於 Game Loop 裡的 FPS。但不同的是:
Game Loop 會因為 Update 處理的效能 (通常是 2D/3D 圖形渲染),因而影響 FPS 的大小。
底下是我在 純手工遊戲開發 - Java 2D RPG Game 的 Game Loop 精簡過後的片段邏輯:
1 | public void run() { |
OS 不能因為 task 慢了,tick 計數就停下來,這在電腦科學中稱為 不可屏蔽性 或 強制性 (Preemption)。遊戲中的 Update() 是協作式的 (前一個跑完下一個才跑);OS 的 timer_handler 是侵入式的(管你跑完沒,硬體訊號強行插隊)。
底下我整理了我自己的理解的差異:
| 特性 | Game Loop (遊戲) | OS Tick (Simple OS) |
|---|---|---|
| 迴圈本體 | while(running) { ... } |
isr32: -> call timer_handler |
| 頻率控制 | 軟體自行計算 dt 並 Sleep |
硬體晶片固定發送電氣訊號 |
| 執行保證 | 若卡住,下一影格就順延 | 若卡住,硬體訊號會強制插隊 (或遺失) |
| 主要任務 | 更新遊戲邏輯、渲染畫面 | 計時、統計 CPU 佔用、強制換人 (schedule) |
Game Loop:主動式。跑者自己看手錶,跑太快就 Sleep。
OS Tick:被動式。硬體監工每隔一段時間「啪」地拍桌子,強制 CPU 停下工作,執行 OS 的家務事。
小結
在 Linux 面前,我的知識是匱乏的、無知的,我在看到 Linux Kernel 是尊敬的,他是人類智慧與文明的精華。開啟 Simple OS 自幹的初衷,就是覺得我自己知識匱乏的緊,即使我已經工作數十餘載,但常常還是覺得自己懂的太少。
洋蔥是學習是我的方法,一圈一圈的扒開,逐漸明朗,好玩又有趣!再配合很多既有的課程,像是 Linux 核心設計: Timer 及其管理機制、以前在研究 分散式系統 讀過的論文 Time, clocks, and the ordering of events in a distributed system – Leslie Lamport (1978),利用幫我 AI 探索,這個過程是有趣且豐富的!
但如果只是把這一切都交給 AI Agent,那最後我將得到一個能用的東西,但我依舊停在原點,呈現 淺薄思考 與 知識匱乏 的狀態。
未完
本來還有還有很多想寫,不過再寫下去篇幅太長了,先做個紀錄,之後慢慢來補。
- timer.c 概念,那麼 User Space 的
sleep()是怎麼被實作出來的?- 已經完成實作,不過內容篇福有點多,加上要講到 Task Scheduler 會有點複雜,需要放到獨立的一篇來講。
- 實務上曾經遇過跟時間有關的想法,包含
sleep(),ntp、k8s cpu=100m的意思 - 摘要現代 Linux Kernel 跟 timer / tick 相關的資訊
- 探索程式語言 Java 21 sleep() 的實作
- 以前在研究 分散式系統 讀過的論文 Time, clocks, and the ordering of events in a distributed system – Leslie Lamport (1978) 的一些想法
延伸閱讀
站內文章
參考資料
- Linux 核心設計: Timer 及其管理機制
- Intel 8253 / 8254 - programmable interval timers (PITs)
- Intel 8259 - programmable interrupt controller (PIC)
- Time, clocks, and the ordering of events in a distributed system - Leslie Lamport (1978)




