什麼沒變,什麼裂開了
你在整道階梯上學到的,是一組出奇穩定的觀念。行程不是程式;核心是那個跑在特權模式、發放 CPU、記憶體與裝置的大樓管理員;虛擬記憶體透過一張分頁表與一塊TLB,給每個行程一份自己私有的位址空間;檔案系統把區塊變成有名字的檔案;一個中斷就是那顆把 CPU 拉進核心的門鈴。這些骨架已有數十年歷史,而且哪裡也不會去。前沿並不是要把它們丟掉——它問的是,包在它們外頭的那些假設,還有哪些站得住腳。
有三個大假設裂開了,而這一階裡幾乎每一道前沿,都是對其中之一的反應。第一,課本上的作業系統假設許多互不信任的程式共用一台機器——但一個雲端函式、或一台單一用途的設備,往往是一台機器跑一支應用,那又何必為那一整套共用機制付帳?第二,它假設核心的政策在開機時就定死了——但維運者如今想安全地重新編程那個正在執行的核心,而這正是 eBPF 的全部要點。第三,它假設硬體長得像課本上那樣:一顆慢吞吞的磁碟、一顆誠實的 CPU、一斷電就遺忘的 RAM。持久記憶體、不可信的主機、以及比核心自己的開銷還快的裝置,全都打破了這幅圖。把這三道裂縫記在心裡,前沿就不再像一袋散裝的縮寫了。
裂縫一:我們還需要整套作業系統嗎?
如果一台機器只跑唯一一支應用,核心的工作有一大半就蒸發了。沒有第二支程式需要防範,所以你花了好幾階學的、使用者模式與核心模式之間那道牆,如今變成一支應用與它自己之間的牆——純粹的開銷。這正是單核應用(unikernel)背後的賭注:把你那唯一一支應用,連同它真正會呼叫到的那幾塊作業系統零件,編譯成單一一份映像檔,直接開機跑在硬體或超管理器上,應用與核心之間不再有分隔。它在數毫秒內就開機完畢,攻擊面極小——因為沒用到的程式碼乾脆就不存在——而一個系統呼叫,變成一次普通的函式呼叫,而不是一次陷入核心。
對那個代價要誠實,因為它和以往是同一個代價。單核應用之所以拋棄了應用與作業系統之間的保護,正正是因為只有一支應用需要保護——於是應用裡的一個臭蟲,如今能踩爛緊鄰它的核心資料,沒有雙模式的牆去攔。它也丟掉了通用作業系統的種種舒適:沒有可登入的命令列、沒有第二支行程去跑除錯器、驅動程式有限。雲端與無伺服器的世界讓這筆交易變得誘人,是因為那道外層隔離由別處提供:你把每個小小的單核應用,跑在它自己、架在超管理器上的虛擬機器裡,於是那道強牆住在超管理器上,而單核應用本身就能理直氣壯地極簡。這也是無伺服器的天然歸宿:一個只跑 40 毫秒的函式,想要的是在 1 毫秒內啟動,而不是把一整套 Linux 開機。
裂縫二:一個你能重新編程的核心
從前要改變核心行為的辦法很粗暴:改核心原始碼、重新編譯、重新開機——不然就用 C 寫一個可載入核心模組,而一旦你把一個指標弄錯,就有把整台機器弄當的風險。eBPF 是前沿那個優雅的答案。你寫一支小程式,核心讓它先通過一個驗證器,證明它不會無窮迴圈、不會碰它不該碰的記憶體、而且一定會終止,然後才把它掛到那個正在執行的核心深處的某個鉤子上——掛在一個系統呼叫、一個網路封包、一個驅動程式事件、或一個函式進入點上。它實際上,就是一個給「跑在核心情境裡的程式碼」用的、微小而安全的沙箱,讓你不必重新編譯或重開機,就能觀察、甚至駕馭核心。
這正是為什麼這一階稍早那套追蹤與剖析工具——perf、ftrace 及其同伴——愈來愈倚靠 eBPF。與其去猜一台伺服器為何變慢,你掛上一支小小的 eBPF 程式,精確地數出每一次分頁錯誤花了多久、或哪支行程正在猛搥磁碟,而開銷小到足以讓它在正式環境裡一直開著。同一套鉤子機制,如今也驅動著高速網路、安全監控與負載平衡。誠實的提醒是:「安全」指的是驗證器保證這支程式守規矩,而不是保證你寫對了邏輯——而驗證器本身就是一段複雜的核心程式碼,所以 eBPF 拓寬了「一支差不多算無特權的程式」能在核心裡做的事,這是一種必須小心治理的威力。
裂縫三:當硬體打破了課本
好幾道前沿之所以存在,純粹是因為硬體不再符合那些烤進核心裡的假設。先講儲存。你整套虛擬記憶體與檔案系統的設計,都假設了一道陡峭的懸崖:快速、一斷電就遺忘的 RAM,與緩慢、會記住的磁碟。持久記憶體(儲存級記憶體)卡在兩者之間——它插在記憶體匯流排上、像 RAM 一樣可以按位元組定址,卻又像磁碟一樣在斷電後仍保住內容。突然之間,那套圍著緩慢區塊輸入輸出蓋起來的日誌式與緩衝快取機制,可能成了錯的工具,因為你大可用一次普通的記憶體寫入,就更新一個耐久的資料結構。這對一個你以為早已塵埃落定的層次,是一場靜悄悄卻激進的改變。
再講速度。一張現代網路卡、或一顆 NVMe 固態硬碟,搬資料可以快到讓核心自己「每次操作」的開銷——那次情境切換、那次跨越使用者/核心邊界的複製、那次中斷處理——反倒成了瓶頸。繞過核心是那個激進的解法:讓一支可信的應用幾乎直接跟裝置對話,在最熱的路徑上跳過核心。像 DPDK 這樣的框架替網路做這件事;io_uring 則把它柔化,給你一圈共用的「已提交與已完成請求」環,讓你把系統呼叫成批處理,而不是每次操作付一次陷入。那筆取捨,正正是你從整道階梯該預期到的:你靠放棄核心的居中協調來換回速度,因此也放棄了它的一部分保護與公平——所以你只對可信的、效能關鍵的程式碼這麼做。
最後,講信任。課本假設你的程式底下那套作業系統與硬體是誠實的。但在雲端,你的程式跑在別人的機器上、別人的超管理器與核心底下。機密運算翻轉了一個很深的假設:在硬體支援下,你的程式跑在一個加密的飛地(enclave)裡,連那台主機的作業系統與超管理器都讀不到它的記憶體,哪怕它們比你更有特權。這是一個奇異的新世界,在那裡,核心不再被它所服務的應用「理所當然地」信任——保護如今是雙向的,由 CPU 自己來執行。
那個永不消失的結構性問題
在每一道前沿底下,都坐著一個你研究核心結構時遇過的、古老而懸而未決的問題:到底有多少該住在特權核心裡頭,又有多少該住在外頭?單體式這一派,為了速度(一次函式呼叫、不跨邊界),把驅動程式、檔案系統、網路全塞進一個大核心裡。微核心這一派,則把幾乎這一切都推到外頭、變成普通行程,把核心保持得極小,於是一個當掉的驅動程式扳不倒整個系統——代價是不停跨邊界的 行程間通訊訊息。記住那個誠實的說法:微核心拿 IPC 的成本去換韌性,並不「自動就比較好」。整個產業大致落腳在混合式設計上,當作一個務實的折衷。
前沿上真正新的東西,是我們如今有時能取得微核心的韌性,而不再只靠信仰付帳。形式驗證意思是用數學去證明,核心的程式碼符合它的規格——對每一種可能的輸入,它都沒有緩衝區溢位、沒有特權升級的臭蟲、沒有任何辦法違反隔離。seL4 就是一個用這種方式被形式驗證過的微核心:不是「測了很多次、看起來沒事」,而是被證明對著它的規格正確無誤。對任何「核心一出錯就是大災難」的場合——飛機、醫療裝置、安全模組——這是一場深刻的轉變,而它也輕推了那場古老的「單體對微核心」之爭,因為一個微小、被驗證過的微核心,能給出一個龐大的單體核心現實上永遠給不出的保證。
你如今站在哪裡
回頭看這整趟攀登。你出發時連程式和行程都分不清,如今你已能推敲排程、競爭條件與臨界區間、死結及它的四個條件、分頁與分頁編號/位移的切分、分頁錯誤與輾轉、檔案系統與日誌、虛擬化與容器,現在更到了開放的前沿。前沿不是階梯頂端的一堵牆;它是那同一小撮你早已擁有的觀念,正被拿去對著新硬體與新需求重新秤量的地方。你已握有那套詞彙,而更重要的是,握有追下去的直覺。
也要誠實面對那些至今仍真正開放的問題,因為那份誠實是這門手藝的一部分。我們還沒有一個定論,說該如何驗證一個像 Linux 那麼大、那麼快的核心。我們還不知道,當記憶體變成持久、而網路跑贏 CPU 之後,最好的分工該是什麼。我們也還沒有一個乾淨的說法,去描述一個「應用並不信任底下核心」的作業系統。這些不是你理解上的缺口——它們是整個領域的缺口。把它們當作開放問題來持守,而不是假裝它們已被解決,正正是讓下一代作業系統得以被打造出來的那種心態。你如今已準備好去讀懂這些問題,也許有一天,去回答其中一個。