iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Software Development

用作業系統讀懂另一半的OS系列 第 20

【2025鐵人賽】用作業系統讀懂另一半的OS:Main Memory 01

  • 分享至 

  • xImage
  •  

接下來,就是要來談記憶體相關的部分了~

基本概念

在電腦系統中,記憶體(Memory) 是運作的核心之一。CPU 必須從記憶體中取得指令與資料,才能執行程式。CPU 的基本執行流程如下:

  1. 抓取指令(Fetch):CPU 從記憶體中讀取要執行的指令。
  2. 解碼(Decode):CPU 解釋該指令的意義,判斷需要進行什麼操作(例如,加法、乘法等)。
  3. 抓取操作數(Fetch Operand):CPU 根據指令的要求,從記憶體或暫存器中讀取所需的資料。
  4. 執行指令(Execute):CPU 在算術邏輯單元(ALU)或其他執行單元中執行指令。這個步驟可能需要將運算結果寫回暫存器或記憶體。

於 CPU 只能直接操作暫存器(Registers)與主記憶體(Main Memory),而不能直接操作速度較慢的硬碟。為了彌補 CPU 與主記憶體之間巨大的速度差異,快取(Cache)便應運而生。快取是一種容量較小但速度極快的記憶體,它會儲存經常被 CPU 存取的主記憶體資料副本。當 CPU 需要資料時,會先從快取中尋找。如果資料在快取中,就能快速讀取,大幅提升效率。如果快取中沒有,才會去主記憶體抓取。

Logical vs Physical Address Space(邏輯位址 vs 實體位址)

現代作業系統運用虛擬記憶體 (Virtual Memory) 與多工 (Multitasking) 機制,讓每個程式都感覺像是「擁有一整塊自己的連續記憶體空間」,但實際上,所有的程式都共用主記憶體。因此,系統必須將程式「看到的位址」與「實際硬體記憶體的位址」分開處理,並由作業系統負責進行轉換與保護。

  • 邏輯位址 (Logical Address):也稱為虛擬位址 (Virtual Address),這是程式在執行時所看到的位址。例如,當你存取一個陣列 arr[3] 時,程式碼中使用的就是一個邏輯位址。
  • 實體位址 (Physical Address):這是真正送進記憶體匯流排 (Memory Bus) 的位址,也就是資料在主記憶體中的真實位置。這個位址是由 記憶體管理單元 (MMU) 計算出來的。

而 MMU (Memory Management Unit) 就是一個硬體元件,它負責在程式執行過程中,即時 (on the fly) 將邏輯位址轉換為實體位址。這個轉換過程可以簡單地表示:實體位址=邏輯位址+基底位址

其中,「基底位址 (Base Address)」是由作業系統為每個程式指定的起始位址。

邏輯位址與實體位址設計的優點:

  1. 記憶體隔離 (Memory Isolation):每個程式看到的記憶體區塊都從位址 0 開始,但作業系統會將它們對應到主記憶體中不同的實體位址,確保各程式互不干擾。
  2. 高度安全性 (High Security):由於程式只能存取它自己的邏輯位址空間,無法直接看到或讀寫其他程式的記憶體,大幅提升了系統的穩定性與安全性。
  3. 支援多工 (Multitasking Support):多虧了這個機制,每個程式可以獨立地載入到主記憶體的任意空閒位置,不會因為位址衝突而互相干擾。
  4. 彈性管理 (Flexible Management):作業系統可以更彈性地管理主記憶體。當程式需要被載入時,系統只需要在任何一個有足夠空間的連續區塊中載入程式,並更新 MMU 的基底暫存器 (Relocation Register),而無需考慮程式的原始位址。這讓記憶體的使用效率更高。

基底暫存器(base)+ 限制暫存器(limit)

在多工系統中,許多程式 (processes) 同時在主記憶體中執行。為了確保系統穩定與安全,作業系統必須實現以下保護機制:

  • 作業系統 (OS) 不可被使用者程式讀寫:如果使用者程式能夠任意修改 OS 的核心程式或資料,將可能導致系統行為被惡意竄改,引發嚴重後果。
  • 程式間不可互相干擾:每個程式都應該在自己的獨立記憶體空間中執行,防止一個程式的錯誤操作意外地破壞另一個程式的資料,造成資料毀損或系統崩潰。

為了解決這些問題,作業系統採用了基底暫存器 (Base Register) 和限制暫存器 (Limit Register) 的方式來實施保護。每個程式的記憶體使用範圍都被精確地限制在一個區塊內:

  • 基底暫存器 (Base Register):存放程式在主記憶體中允許使用的起始實體位址。
  • 限制暫存器 (Limit Register):存放程式可使用的記憶體空間大小。

https://ithelp.ithome.com.tw/upload/images/20250729/20177764Zwxr6OL4k2.png

記憶體存取流程與保護

當使用者程式提出一個邏輯位址 (Logical Address) 進行存取時,硬體會自動檢查位址是否合法:硬體會先檢查這個邏輯位址是否超過限制暫存器所設定的大小。如果 logical_address > limit,代表程式試圖存取超出其允許範圍的記憶體。這將觸發一個陷阱 (trap),將控制權交給作業系統處理,通常會終止該程式。

physical_address = base + logical_address

// 系統檢查是否有違規:若 logical_address > limit → 觸發 trap → 切入 OS 處理
舉例而言:
base = 1000,limit = 500
使用者提出 logical_address = 400
系統計算:physical_address = 1000 + 400 = 1400 ✅
若提出 logical_address = 600 → 超過 limit → ❌ 非法存取

https://ithelp.ithome.com.tw/upload/images/20250729/20177764KiGnO4aFY3.png

位址繫結(Address Binding)

當我們撰寫程式時,變數或指令的位址都是抽象的符號位址 (symbolic address),例如 x = y + 3。此時,我們並不知道 x 和 y 在記憶體中的實際位置。位址繫結 (Address Binding) 就是將這些符號位址轉換為實際的實體位址 (physical address) 的過程。這個轉換可以發生在不同的時間點,主要有三種:

第一種,編譯時 (Compile Time):

在程式被編譯成執行檔時,編譯器會為程式碼中的每個符號位址分配固定的實體位址。例如,程式碼會被指定從主記憶體的位址 1000 開始載入。
優點:簡單、快速。
缺點:彈性極低。如果程式需要載入到不同的記憶體位置,例如從位址 2000 開始,那麼程式必須重新編譯。這在多工環境下幾乎不可行。

第二種,載入時 (Load Time)

在程式被載入到記憶體時,編譯器會產生可重定位 (relocatable) 的程式碼。這意味著程式碼中的位址不是固定的,而是相對位址。當程式載入器 (loader) 將程式載入到主記憶體時,會根據實際載入的起始位置,動態地調整所有的位址。
優點:比編譯時繫結更具彈性,程式可以在記憶體中移動,只要在載入時決定最終位置即可。
缺點:一旦程式載入完成,其位址便固定了,之後便無法在記憶體中移動。

第三種, 執行時 (Execution Time)

這是現今最常見且最靈活的位址繫結方式。在程式執行過程中。程式碼中的位址在執行過程中會不斷地被轉換。
這個機制需要硬體支援,例如 記憶體管理單元 (MMU)。MMU 會即時將程式產生的邏輯位址 (logical address) 轉換為實體位址 (physical address)。
優點:極度靈活。程式在執行過程中可以被搬動到不同的記憶體位置,例如從磁碟載入,或在記憶體中進行區塊移動,這對虛擬記憶體和分頁 (paging) 等技術至關重要。
缺點:需要額外的硬體支援和軟體開銷。

https://ithelp.ithome.com.tw/upload/images/20250729/20177764cZs7XDOrD5.png

動態載入 (Dynamic Loading) 與動態連結 (Dynamic Linking)

在早期的作業系統中,當你執行一個程式時,整個程式(包含主程式、所有函式以及所需的函式庫)都會一次性地全部載入到記憶體中。這種方法的主要缺點是記憶體使用量高,因為許多不常使用甚至從未被呼叫的程式碼也佔據了寶貴的記憶體空間。為了解決這個問題,動態載入 (Dynamic Loading) 與動態連結 (Dynamic Linking) 的設計應運而生。

動態載入 (Dynamic Loading)

動態載入的核心概念是:只在「需要用到某個函式時」才將其載入到記憶體中,否則就先不載入。
在初始階段:當一個程式開始執行時,作業系統只會載入主程式本身。
在執行階段:當程式碼執行到一個需要呼叫特定函式(例如 load())的位置時,載入器才會把這個函式從磁碟載入到記憶體中。

這種方式的優點顯而易見:

  • 節省記憶體空間:只載入正在使用的函式,避免不必要的記憶體浪費。
  • 提升效能:減少程式啟動時的初始載入時間,讓程式可以更快地開始執行。
  • 彈性運用:特別適合用來處理錯誤處理函式或不常用功能(例如:印錯誤日誌、進階功能),這些功能可以延後載入,不影響主程式的執行效率。

動態連結 (Dynamic Linking) 與共享函式庫 (Shared Libraries)

動態連結是動態載入概念的延伸,主要解決的是函式庫 (libraries) 的重複載入問題。

  • 靜態連結 (Static Linking):在編譯程式時,每個程式都會將所需的函式庫程式碼完整地嵌入到自己的執行檔中。這樣做的結果是,如果有多個程式都使用同一個函式庫(例如 libc),那麼這個函式庫的程式碼就會在記憶體中重複出現多次,造成巨大的空間浪費。
  • 動態連結 (Dynamic Linking):又稱為共享函式庫 (Shared Libraries),例如在 Windows 中的 DLL (Dynamic Link Library) 或 Linux 中的 so (Shared Object)。
    • 程式在編譯時,並不會將函式庫的程式碼內嵌進去,而只會留下一個參照 (reference)。
    • 當程式執行時,作業系統才會將所需的共享函式庫載入到記憶體中,並建立連結。
    • 多個程式可以共用一份儲存在記憶體中的函式庫實體。

這種機制的主要優點是:

  • 極大化記憶體節省:如果系統中有 10 個程式都在使用 libc,動態連結只需要在記憶體中載入一份 libc,而不是 10 份。
  • 簡化維護:當函式庫有更新時,你只需要更新一份共享函式庫檔案,所有依賴它的程式都能自動獲得更新,無需重新編譯。

上一篇
【2025鐵人賽】用作業系統讀懂另一半的OS:Deadlock 03
下一篇
【2025鐵人賽】用作業系統讀懂另一半的OS:Main Memory 02
系列文
用作業系統讀懂另一半的OS30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言