您當前的位置:首頁 > 書法

重學計算機組成原理(8)-程式是如何被裝載的

作者:由 JavaEdge 發表于 書法時間:2021-12-08

80年代640K記憶體對哪個人都夠用了。那時微軟開發的還是DOS os,程式設計師們還在想如何壓榨完有限的640K記憶體。

而現在,隨便一個筆記本都16G記憶體了,比那時多了一萬倍。那當時這種言論是無稽之談嗎?為何覺得這麼小記憶體就夠了呢?

1 如何才能實現

程式裝載

在執行這些可執行檔案時,是透過裝載器解析ELF或PE格式的可執行檔案。

裝載器會將對應指令和資料載入到記憶體,讓CPU去執行。

要實現

裝載到記憶體

,則裝載器需滿足:

可執行程式載入後,佔用的記憶體空間是連續的

執行指令時,

程式計數器

是順序一條條指令執行。這就要求這一條條指令得連續儲存

需同時載入很多個程式 && 不能讓程式自己規定在記憶體中載入的位置

雖編譯出的指令裡已有對應的各種記憶體地址,但實際載入時,其實無法確保這個程式一定載入在哪段記憶體地址。因為現在計算機通常會同時執行很多個程式,可能你想要的記憶體地址已被其他載入了的程式佔用了

如何才能滿足如上需求呢?

可在記憶體裡,找到一段連續記憶體空間,然後分配給裝載的程式,然後把這段連續的記憶體空間地址,和整個

程式指令

裡指定的記憶體地址做個對映:

指令裡用到的記憶體地址叫

虛擬記憶體地址(Virtual Memory Address)

實際在記憶體硬體裡的空間地址,叫

物理記憶體地址(Physical Memory Address)

程式裡有指令和各種記憶體地址,但只需關心

虛擬記憶體

地址。

任一程式,它看到的都是同樣的記憶體地址。我們維護一個虛擬記憶體到物理記憶體的對映表,這樣實際程式指令執行時,會透過虛擬記憶體地址,找到對應

物理記憶體

地址,然後執行。

因為是連續記憶體地址空間,所以只需維護對映關係的:

起始地址

對應的空間大小

2

記憶體分段

(Segmentation)

找出一段連續的物理記憶體和虛擬記憶體地址進行對映的方法。這裡的段指系統分配出來的那個連續的記憶體空間。

重學計算機組成原理(8)-程式是如何被裝載的

這很好,解決了程式本身無需關心具體物理記憶體地址的問題,但也有不足,如

記憶體碎片

(Memory Fragmentation)

比如電腦有1GB記憶體,先啟動一個 圖形渲染,佔了512MB記憶體,接著啟動個Chrome,佔了128MB記憶體,再啟動一個Py程式,佔了256MB記憶體。

現在關掉Chrome,

空閒記憶體

還有:

1024 - 512 - 256 = 256MB1024−512−256=256

MB

講道理,已有足夠的空間再裝載個200MB程式。但這256MB

記憶體空間

並非連續,而是被分成兩段128MB記憶體。於是實際上我們的程式無法被載入進來。

重學計算機組成原理(8)-程式是如何被裝載的

如何解決這個問題呢?

記憶體交換(Memory Swapping)

可將Py程式所用256MB記憶體寫到硬碟,再從硬碟讀回到記憶體。讀回時,不再把它載入到原來位置,而是緊跟在那已被佔用的

512MB記憶體

後。

這就有了連續的256MB記憶體空間,就能去載入個新的200MB程式。

若你安裝過Linux,你肯定遇到過分配一個swap硬碟分割槽抉擇,這塊分出的磁碟空間,就是給Linux進行

記憶體交換

用的。

虛擬記憶體、分段,再加上記憶體交換

,三駕馬車,看起來已完美解決計算機同時裝載執行很多個程式的問題。不過仍有效能瓶頸:

硬碟的訪問速度比記憶體慢太多

每次記憶體交換,都要把一大段連續記憶體資料寫到硬碟

所以,若記憶體交換時,交換的是個很佔記憶體空間的程式,整個機器會變得卡頓。

3

記憶體分頁

既然問題原因是:

記憶體碎片

記憶體交換的空間太大

則解決方案自然就是想著:

怎麼少出現記憶體碎片

當需記憶體交換時,讓需交換寫入或從磁碟裝載的資料更少一點

於是記憶體管理給出了

記憶體分頁(Paging)

方案。

和分段這樣分配一整段連續的空間給到程式相比,

分頁

是把整個物理記憶體空間切成一段段固定尺寸的大小。而對應程式所需佔用的虛擬記憶體空間,也會切成一段段固定尺寸的大小。

這一個個連續&&尺寸固定的記憶體空間,叫

頁(Page)

從虛擬記憶體 =》物理記憶體的對映,不再是拿整段連續的記憶體的物理地址,而是按一個個頁,一般來說:

頁的尺寸 << 整個程式的大小頁的尺寸<<整個程式的大小

Linux下,通常設定成4KB,可檢視:

Linux設定的頁大小

重學計算機組成原理(8)-程式是如何被裝載的

macOS 的記憶體頁大小

重學計算機組成原理(8)-程式是如何被裝載的

由於記憶體空間都是預先劃分好的,也就沒有不能使用的碎片,而只有被釋放出來的很多4K的頁。

即使記憶體空間不夠,需要讓現有的、正在執行的其他程式透過記憶體交換釋放出一些記憶體頁出來,一次性寫入磁碟的也只有少數的一個頁或幾個頁,不會花太多時間,讓整個機器被記憶體交換的過程給卡住。

重學計算機組成原理(8)-程式是如何被裝載的

分頁的方式使得載入程式的時候,不再需要一次性把程式載入到物理記憶體中。

可在進行虛擬記憶體、物理記憶體頁之間的對映後,並不真的把頁載入到物理記憶體,而是隻在程式執行中,需要用到對應虛擬記憶體頁裡面的指令和資料時,再載入到物理記憶體。

os也就是這麼做的,當要讀取特定的頁,卻發現數據並未載入到物理記憶體,就會觸發來自CPU的

缺頁錯誤(Page Fault)

os會捕捉到這個錯誤,然後將對應頁,從存放在硬碟上的虛擬記憶體讀出,載入到物理記憶體。

這使得可以執行那些遠大於我們實際物理記憶體的程式。同時,任何程式都無需一次性載入完所有指令和資料,只需載入當前所需的。

透過

虛擬記憶體、記憶體交換和記憶體分頁

技術組合,最終得到了一個讓程式無需考慮實際物理記憶體地址、大小和當前分配空間的解決方案。

這些技術和方法,對於我們程式的編寫、編譯和連結過程都是透明的。這也是我們在計算機的軟硬體開發中常用的一種方法,就是

加入一個間接層

透過引入虛擬記憶體、頁對映和記憶體交換,程式不再需考慮對應真實記憶體地址、程式載入、記憶體管理等問題。

任一程式只需把記憶體當成是一塊完整而連續的空間來直接使用即可,其它都是 os 保姆幫你做好的。

4 總結

在虛擬記憶體、記憶體交換和記憶體分頁這三者結合之下,要執行一個程式,“必需”的記憶體是很少的。CPU只需要執行當前的指令,極限情況下,記憶體也只需要載入一頁就好了。

再大程式,也可分成一頁。每次,只在需要用到對應的資料和指令的時候,從硬碟上交換到記憶體裡面來就好了。

以現在4K記憶體一頁的大小,640K記憶體也能放下足足160頁呢,也無怪乎在比爾·蓋茨會說出“640K ought to be enough for anyone”。

不過硬碟的訪問速度比記憶體慢很多,所以我們現在的計算機沒有幾G記憶體都不好意思賣。

除了程式分頁裝載這種方式之外,還有其他最佳化記憶體使用的方式:“動態裝載”,請見後文分解。

FAQ

在Java這樣使用虛擬機器的程式語言裡,程式是怎麼裝載到記憶體裡的?也和本文一樣,透過記憶體分頁和記憶體交換的方式載入到記憶體裡面來的麼?

JVM已是上層應用,無需考慮物理分頁,一般更直接是考慮物件本身空間大小,物理硬體管理統一由承載JVM的os解決。

參考

深入淺出計算機組成原理

《程式設計師的自我修養——連結、裝載和庫》的第1章和第6章