指令重新啟動(instruction restart)
想像你寫一句話寫到一半,墨水用完了。你停下、拿一支新筆,然後從第一個字開始把整句話重寫一遍,彷彿什麼都沒發生。你不會試圖從半個字的中間接著寫——那會留下一團亂。指令重新啟動對 CPU 而言正是這種紀律:當一道指令執行到一半被分頁錯誤打斷時,最簡單而正確的修正方式,就是在缺頁進入記憶體後,從頭重新執行整道指令。
具體來說,分頁錯誤可能在執行某一道機器指令的中途發生——例如,一道從記憶體加上某值的指令,可能在抓取那個值時發生錯誤。作業系統服務這次錯誤,但接著那道指令必須完成。乾淨的做法是重新啟動它:把程式計數器重設回指向那道造成錯誤的指令,讓 CPU 從頭執行它,此時頁已存在。這要求指令是可重新啟動的,意即從頭重新執行能產生正確結果,且沒有有害的雙重作用。對大多數指令而言這是自動成立的。微妙的危險在於某些指令在發生錯誤前已經改變了狀態——例如一道逐位元組搬移並隨之更新指標的區塊搬移指令,或一種已經把某暫存器加一的自動遞增定址模式。天真地重跑這類指令,可能讓某個副作用發生兩次,或覆寫到已經複製過的資料。
為什麼重要:指令重新啟動是讓需求分頁能成立的硬性硬體要求。如果一顆 CPU 無法在指令中途發生錯誤後乾淨地復原,你就根本無法在它上面使用需求分頁,因為任何觸及記憶體的指令都可能在任何時刻發生錯誤。架構設計者用兩種方式處理棘手的情況:讓指令可被檢查且可重新啟動,或保存足夠的狀態(或撤銷部分作用),使指令能正確地恢復。教訓是:虛擬記憶體並非純粹的作業系統觀念——它對 CPU 指令集的設計加諸了實實在在的限制。
一道「add r1, [x]」指令在抓取 x 時發生錯誤。作業系統載入 x 的頁之後,CPU 不會試圖從指令中途恢復;它把程式計數器設回「add r1, [x]」,再把整道指令重跑一次,這次乾淨地成功。
從頭重跑整道指令,而非從中間。
棘手的情況是那些副作用會在可能發生錯誤之前就出現的指令——區塊搬移與自動遞增定址。架構必須能事先檢查指令將觸及的所有頁,或能撤銷部分已做的工作;否則天真的重新啟動會破壞資料。