跳轉到

1.1 1950年代監督程式與批次處理的誕生#

前言:為什麼需要作業系統?#

要理解作業系統(Operating System, OS)的本質,最好的方法是回到電腦還沒有作業系統的年代。1940年代末期的電腦,如 ENIAC(1946)與 EDVAC,是完全「手工操作」的機器:程式設計師以打孔卡片或紙帶輸入程式,親自到機器前排線、設定開關、按下啟動鈕,然後盯著指示燈看結果。整台機器同一時間只服務一個人,CPU 的絕大多數時間都在「等人」——等操作員換卡片、等讀卡機就緒、等印表機輸出。

在那個年代,「作業系統」這個名詞尚不存在,但一個根本的矛盾已經浮現:電腦是極其昂貴的資源(一台機器動輒數百萬美元),而人類操作的速度遠遠跟不上機器。如何讓價值連城的 CPU 不要閒置?這個以「資源利用率」為核心的問題,驅動了此後三十年作業系統所有重要的發明。

監督程式(Supervisor)的誕生#

GM-NAA I/O(1956):第一個公認的作業系統#

歷史上第一個被廣泛承認的作業系統,是 1956 年由通用汽車(General Motors)研究部門與北美航空(North American Aviation)為 IBM 704 大型主機合作開發的 GM-NAA I/O。它的動機非常務實:當時每家公司都要自己重寫一遍讀卡、載入程式、驅動印表機的底層程式碼。GM 的工程師索性把這些共通的輸入輸出常式集合起來,做成一套所有程式都能共用的函式庫與載入器。

這套系統的核心概念是監督程式(Supervisor / Monitor):一個常駐在記憶體中的特殊程式,負責:

  1. 自動執行(automatic job sequencing):把一批工作(job)放在讀卡機上,監督程式依序載入、執行、輸出,全程不需要人工介入。
  2. I/O 常式共用:程式透過呼叫監督程式提供的副程式來讀卡與列印,避免每個程式師重複造輪子。
  3. 簡單的錯誤處理:程式結束或異常時,控制權交回監督程式,繼續下一個工作。

我們今天熟悉的「程式結束後控制權回傳給作業系統」這個基本循環,就是從這裡開始的。

FMS 與 IBSYS:批次系統的成熟#

同一時期,MIT 為 IBM 709 開發了 FMS(FORTRAN Monitor System),而 IBM 自己在 1950 年代末推出了商用產品 IBSYS(1959,用於 IBM 7090)。IBSYS 是「監督程式」概念的完整商用化:它包含工作控制語言(Job Control Language, JCL)的雛形,程式設計師用特殊卡片(如 $JOB、$FORTRAN、$DATA)告訴監督程式「這是一個工作、用 FORTRAN 編譯、然後執行」。

JCL 是一個影響深遠的發明——它把「使用者對作業系統下指令」這件事變成了正式的語言。從編譯器的角度看,JCL 其實是一種領域特定語言(DSL),其語法結構(工作、步驟、資源宣告)後來演化為 UNIX shell script 與現代工作排程系統(如 cron、Kubernetes Job)的概念祖先。

批次處理(Batch Processing)#

單一道批次與「脫機」技術#

1950年代的批次處理是單一道(single-stream)的:一次只有一個工作在機器上。即使如此,工程師仍發明了關鍵的脫機(offline)技術:由於讀卡機、印表機等機械設備極慢(讀一疊卡片要幾分鐘),IBM 使用一台較便宜的衛星電腦(如 IBM 1401)專門把卡片「預先」抄到磁帶上,再把磁帶搬到主機(如 IBM 7094)上執行;輸出同樣先寫到磁帶,再由衛星機器列印。主機的高速 CPU 於是只跟高速的磁帶打交道。

這個「用緩衝與中介媒體把慢速 I/O 與快速計算解耦」的思想,是後來所有 I/O 緩衝(buffering)、spooling 技術的源頭。

SPOOLing:假離線#

到了 1960 年代初,磁碟出現後,「脫機」可以由主機自己完成:CPU 一邊執行工作 A,一邊用中斷驅動的方式把下一個工作從卡片讀入磁碟,這稱為 SPOOLing(Simultaneous Peripheral Operations On-Line)。SPOOLing 讓輸入與計算重疊(overlap),是「用軟體協調並行資源」的早期典範——今日印表機佇列仍叫 spool,正是歷史的活化石。

組譯器、載入器與「程式如何跑起來」#

監督程式的出現,也直接推動了系統軟體的分化。1950年代的程式生命週期是:組譯器(assembler)→ 載入器(loader)→ 執行。早期的載入器每個程式自帶,後來被監督程式統一提供,並發展出:

  • 絕對載入:程式被組譯成固定起始位址的機器碼。
  • 重定位(relocation):監督程式在載入時把程式碼中的位址加上一個基底值,使程式可以載入到記憶體的任意位置。這是連結器(linker)與動態載入概念的萌芽,也是編譯器後端需要產生「可重定位目標碼」(relocatable object code)的原因。

從系統程式的角度,這段歷史說明了一件事:編譯器與作業系統是共同演化的。編譯器產生什麼樣的目標碼格式(絕對/可重定位/位置無關),決定了作業系統載入與記憶體管理的複雜度;反過來,作業系統提供的執行環境(堆疊、系統呼叫介面、函式庫約定)也約束了編譯器的呼叫慣例(calling convention)。

核心虛擬碼:1950年代監督程式的主迴圈#

用現代 C 語言風格重寫一個 GM-NAA I/O 式的監督程式,其核心不過數十行,卻包含了作業系統最原始的骨架:

// 極簡化的 1950 年代監督程式
char *memory[MEMORY_SIZE];   // 全部記憶體
#define SUPERVISOR_SIZE 4096 // 監督程式常駐前 4K

void supervisor_main(void) {
    while (1) {
        job_t job = read_next_job();       // 從讀卡機/磁帶讀入
        load_at(job.code, SUPERVISOR_SIZE); // 重定位並載入
        set_timer(job.max_time);            // 無限迴圈防呆
        jump_to(SUPERVISOR_SIZE);           // 交出控制權
        // 工作結束(正常或 trap)會跳回這裡
        if (abnormal_halt()) {
            print_error_dump();             // 傾印記憶體供除錯
        }
        print_separator();                  // 報表分頁
    }
}

注意兩個細節:set_timer 是為了防止一個寫壞的工作(無限迴圈)癱瘓整批工作——這是保護概念的起源;abnormal_halt 後的記憶體傾印,則是核心傾印(core dump)除錯文化的開端。

歷史意義與後續發展#

1950年代的監督程式解決了「自動依序執行」的問題,但 CPU 利用率仍受限於一個根本限制:一次只有一個工作。當工作在做 I/O 時(讀磁帶需要數秒),CPU 仍然閒置。要突破這個限制,必須讓多個工作同時駐留記憶體、在 I/O 等待時自動切換到另一個工作——這就是 1960 年代多工程式設計(multiprogramming)的課題,其巔峰之作是 IBM System/360 的 OS/360(1964,Fred Brooks 主導,其教訓化為《人月神話》)與 MIT 的 CTSS 分時系統。

換句話說,1950年代回答了「如何不用人手操作電腦」,1960年代則要回答「如何讓電腦同時服務許多人」。後者的故事,正是下一節的主題。

重點整理#

  • 第一個作業系統是 1956 年的 GM-NAA I/O,本質是共用的 I/O 常式庫+自動工作排程的監督程式。
  • 批次處理的核心動機是提高昂貴 CPU 的利用率;脫機與 SPOOLing 用緩衝解耦慢速 I/O。
  • JCL 是「對 OS 下指令」語言的起源,可視為 shell 的祖先。
  • 監督程式引入了重定位載入與執行期保護(timer),使編譯器、載入器、OS 的分工開始成形。