記憶體保護與保護錯誤
記憶體保護(memory protection)是這條規則:不是每一塊記憶體都可以用每一種方式使用——有些頁你可讀不可寫,有些你可執行不可寫,有些你根本不可碰。它是「上鎖的門」與「唯讀」「員工專用」標示牌由硬體強制執行的對應物,正是它擋住一個亂跑的指標或一筆惡意輸入悄悄改寫你程式的程式碼、或踐踏核心。
它按頁強制執行,透過每筆分頁表項中的權限位元:可讀位元、可寫位元,以及可執行(或不可執行)位元。每次存取時,MMU 拿這次操作去對照這些位元。讀一個可讀的頁:沒問題。試著寫一個可寫位元為 0 的頁、或從一個不可執行的頁取指令,MMU 會拒絕並引發一次保護錯誤——這是一種分頁錯誤,起因不是該頁缺席,而是該操作被禁止。作業系統收到它,因為它非法,通常會交付一個致命信號(Unix 上的 SIGSEGV)終結程式。這正是為什麼典型的程式碼頁被對映成「可讀可執行但不可寫」,資料頁被對映成「可讀可寫但不可執行」:這就是著名的 W 互斥 X(W xor X)策略,阻止攻擊者把程式碼注入資料再執行它。
兩個誠實的區別很重要。第一,保護錯誤不同於缺頁錯誤:同一套例外機制,但一個意指「你不可以」,另一個意指「還沒載入」。第二,這種硬體保護是以頁為粒度的,並不等於攔住一個在你確實擁有的區域內亂跑的指標——寫過陣列尾端、跨進你自己另一個合法頁,並不會破壞任何 MMU 看得見的東西,這正是那類臭蟲(緩衝區溢位)如此危險又如此無聲的原因。
字串字面值住在一個唯讀頁。char *s = "hi"; s[0] = 'H'; 試著寫那一頁;可寫位元為 0,所以 MMU 引發一次保護錯誤,程式以記憶體區段錯誤死亡——即使那個指標本身完全合法。
一個指向唯讀頁的合法指標,仍然不能被寫入。
記憶體保護是以頁為單位、而非以物件為單位:它無法攔住一個仍留在你合法擁有之頁內的越界。所以保護擋得住跨行程與唯讀違規,但寫進你自己可寫記憶體的緩衝區溢位會默默通過 MMU——那是消毒器的工作,不是硬體的。