首頁 >系統教程 >Linux >別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

WBOY
WBOY轉載
2024-02-10 23:48:03697瀏覽

今天我們來研究一下 Linux 的記憶體管理。

對於精通 CURD 的業務同學來說,記憶體管理似乎離我們很遠。但這個知識點雖然冷門(估計很多人學完後根本用不上),但它絕對是基礎中的基礎。

這就像武俠小說中的內功修煉,學完後看不到立竿見影的效果,但對你日後的開發工作大有裨益,因為你站得更高了。

文中所有範例圖都是我親手畫的。畫圖比碼字還花時間,但大家看圖理解比文字更直觀,所以還是畫了。需要高畫質範例圖片的同學,文末有取得方式自取。

再功利地說,如果在面試時不經意間透露你懂這方面的知識,並能說出個一二三來,也許能讓面試官對你更感興趣,離升職加薪、走上人生巔峰又更近了一步。

前提約定:本文討論技術內容的前提是作業系統環境都是 x86 架構的 32 位元 Linux 系統。

虛擬位址

#即使在現代作業系統中,記憶體仍然是電腦中非常寶貴的資源。看看你電腦幾個 T 的固態硬碟,再看看記憶體大小就知道了。

為了充分利用和管理系統記憶體資源,Linux 採用虛擬記憶體管理技術。利用虛擬記憶體技術,每個行程都擁有 4GB 互不干涉的虛擬位址空間。

進程初始化分配和操作都是基於這個「虛擬位址」。只有當進程需要實際存取記憶體資源時才會建立虛擬位址和實體位址的映射,並調入實體記憶體頁。

打個不太恰當的比方,這個原理其實跟現在的某某網盤一樣。假如你的網盤空間是 1TB,真以為就一口氣給了你這麼大空間嗎?那還是太年輕了。都是在你往裡面放東西時才給你分配空間,放多少就分多少實際空間給你。但你和你朋友看起來就像大家都擁有 1TB 空間一樣。

虛擬位址的好處

  • # 避免使用者直接存取實體記憶體位址,防止破壞性操作,保護作業系統。
  • 每個進程都被分配了 4GB 的虛擬內存,用戶程式可以使用比實際物理內存更大的位址空間。

4GB 的進程虛擬位址空間被分成兩部分:「用戶空間」和「核心空間」。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

使用者空間核心空間

物理位址

上面章節我們已經知道不管是使用者空間還是核心空間,使用的位址都是虛擬位址,當需進程要實際存取記憶體的時候,會由核心的「請求分頁機制」產生「缺頁異常」調入實體記憶體頁。

把虛擬位址轉換成記憶體的實體位址,這中間涉及利用MMU 記憶體管理單元(Memory Management Unit ) 對虛擬位址分段和分頁(段頁式)位址轉換,關於分段和分頁的具體流程,這裡不再贅述,可以參考任何一本電腦組成原理教材描述。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

段頁式記憶體管理位址轉換

Linux 核心會將實體記憶體分成3個管理區,分別是:

ZONE_DMA

#DMA記憶體區域。包含0MB~16MB之間的記憶體頁框,可以由老式基於ISA的裝置透過DMA使用,直接對應到核心的位址空間。

ZONE_NORMAL

普通記憶體區域。包含16MB~896MB之間的記憶體頁框,常規頁框,直接映射到核心的位址空間。

ZONE_HIGHMEM

高階記憶體區域。包含896MB以上的記憶體頁框,不進行直接映射,可以透過永久映射和暫時映射進行這部分記憶體頁框的存取。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

物理記憶體區劃分

使用者空間

用戶進程能存取的是「用戶空間」,每個進程都有自己獨立的用戶空間,虛擬位址範圍從從0x000000000xBFFFFFFF 總容量3G 。

使用者程序通常只能存取使用者空間的虛擬位址,只有在執行內陷操作或系統呼叫時才能存取核心空間。

進程與記憶體

#進程(執行的程式)佔用的使用者空間依照「 存取屬性一致的位址空間存放在一起 」的原則,劃分成 5個不同的記憶體區域。存取屬性指的是「可讀、可寫入、可執行等 。

  • 程式碼片段

    程式碼片段是用來存放可執行檔的操作指令,可執行程式在記憶體中的鏡像。程式碼段需要防止在運行時被非法修改,所以只準許讀取操作,它是不可寫的。

  • 資料段

    資料段用來存放可執行檔中已初始化全域變量,換句話說就是存放程式靜態分配的變數和全域變數。

  • BSS段

    BSS段包含了程式中未初始化的全域變量,在記憶體中 bss 段全部置零。

  • heap

    #

    堆是用來存放進程運行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當進程呼叫malloc等函數分配記憶體時,新分配的記憶體就被動態加入到堆上(堆被擴張);當利用free等函數釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)

  • 堆疊 stack

    #堆疊是使用者存放程式暫時建立的局部變量,也就是函數中定義的變數(但不包含 static 宣告的變量,static意味著在資料段中存放變數)。除此之外,在函數被​​呼叫時,其參數也會被壓入發起呼叫的進程棧中,並且待到呼叫結束後,函數的回傳值也會被存放回棧中。由於棧的先進後出特點,所以棧特別方便用來保存/恢復呼叫現場。從這個意義上講,我們可以把堆疊看成一個寄存、交換臨時資料的記憶體區。

上述幾種記憶體區域中資料段、BSS 段、堆通常是被連續儲存在記憶體中,在位置上是連續的,而程式碼段和堆疊往往會被獨立存放。堆疊和堆疊兩個區域在 i386 體系結構中堆疊向下擴展、堆疊向上擴展,相對而生。 別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

你也可以在linux下用size 指令查看編譯後程式的各個記憶體區域大小:

[lemon ~]# size /usr/local/sbin/sshd
   text   data    bss    dec    hexfilename
1924532  12412 4268962363840 2411c0/usr/local/sbin/sshd

核心空間

x86 32 位元系統裡,Linux 核心位址空間是指虛擬位址從0xC0000000 開始到0xFFFFFFFF 為止的高階記憶體位址空間,總計1G 的容量, 包括了核心鏡像、實體頁面表、驅動程式等運行在核心空間。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

核心空間細分區域.

直接對映區

#直接映射區 Direct Memory Region:從核心空間起始位址開始,最大896M的核心空間位址區間,為直接記憶體映射區。

直接映射區的896MB的「線性位址」直接與「物理位址」的前896MB進行映射,也就是說線性位址和分配的物理位址都是連續的。核心位址空間的線性位址0xC0000001所對應的實體位址為0x00000001,它們之間相差一個偏移量PAGE_OFFSET = 0xC0000000

#該區域的線性位址和實體位址存在線性轉換關係「線性位址= PAGE_OFFSET 實體位址」也可以用virt_to_phys()函數將核心虛擬空間中的線性位址轉換為實體地址。

高階記憶體線性位址空間

#核心空間線性位址從 896M 到 1G 的區間,容量 128MB 的位址區間是高階記憶體線性位址空間,為什麼叫高階記憶體線性位址空間?下面給你解釋一下:

前面已經說過,核心空間的總大小 1GB,從核心空間起始位址開始的 896MB 的線性位址可以直接對應到實體位址大小為 896MB 的位址區間。

退一萬步,即使核心空間的1GB線性位址都會對應到實體位址,那也最多只能尋址 1GB 大小的實體記憶體位址範圍。

請問你現在你家的記憶體有多大?快醒都 2023 年了,一般 PC 的記憶體都大於 1GB 了吧!

所以,核心空間拿出了最後的 128M 位址區間,劃分成下面三個高階記憶體映射區,以達到整個實體位址範圍的尋址。而在 64 位元的系統上就不存在這樣的問題了,因為可用的線性位址空間遠大於可安裝的記憶體。

動態記憶體映射區

#vmalloc Region 此區域由核心函數vmalloc來分配,特點是:線性空間連續,但對應的實體位址空間不一定連續。 vmalloc 分配的線性位址所對應的物理頁可能處於低階內存,也可能處於高階記憶體。

永久記憶體映射區

#Persistent Kernel Mapping Region 此區域可存取高階記憶體。存取方法是使用 alloc_page (_GFP_HIGHMEM) 分配高階記憶體頁或使用kmap函數將分配到的高階記憶體對應到該區域。

固定映射區

#Fixing kernel Mapping Region 該區域和 4G 的頂端只有 4k 的隔離帶,其每個位址項目都服務於特定的用途,如 ACPI_BASE 等。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

核心空間物理記憶體映射

回顧一下

上面講的有點多,先別急著進入下一節,在這之前我們再來回顧一下上面所講的內容。如果認真看完上面的章節,我這裡再畫了一張圖,現在你的腦海中應該會有這樣一個記憶體管理的全域圖。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

核心空間使用者空間全圖

記憶體資料結構

#要讓核心管理系統中的虛擬內存,必然要從中抽像出內存管理資料結構,內存管理操作如“分配、釋放等”都基於這些數據結構操作,這裡列舉兩個管理虛擬內存區域的數據結構。

使用者空間記憶體資料結構

#在前面「進程與記憶體」章節我們提到,Linux進程可以分割為5 個不同的記憶體區域,分別是:程式碼段、資料段、BSS、堆疊、堆疊,核心管理這些區域的方式是,將這些記憶體區域抽象化為vm_area_struct的記憶體管理物件。

vm_area_struct是描述行程位址空間的基本管理單元,一個行程往往需要多個vm_area_struct來描述它的使用者空間虛擬位址,需要使用「鍊錶」和「紅黑樹」來組織各個vm_area_struct

鍊錶用於需要遍歷全部節點的時候用,而紅黑樹適用於在位址空間中定位特定記憶體區域。核心為了記憶體區域上的各種不同操作都能獲得高效能,所以同時使用了這兩種資料結構。

使用者空間程序的位址管理模型:

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

wm_arem_struct

#

核心空間動態分配記憶體資料結構

在核心空間章節我們提到「動態記憶體映射區」,該區域由內核函數vmalloc來分配,特點是:線性空間連續,但是對應的物理位址空間不一定連續。 vmalloc 分配的線性位址所對應的物理頁可能處於低階內存,也可能處於高階記憶體。

vmalloc 指派的位址則限於vmalloc_startvmalloc_end之間。每一塊vmalloc分配的核心虛擬記憶體都對應一個vm_struct結構體,不同的核心空間虛擬位址之間有4k大小的防越界空閒區間隔區。

與使用者空間的虛擬位址特性一樣,這些虛擬位址與實體記憶體沒有簡單的映射關係,必須透過核心頁表才可轉換為實體位址或實體頁,它們有可能尚未被映射,當發生缺頁時才真正分配實體頁面。

別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!

動態記憶體映射

總結一下

Linux記憶體管理是一個非常複雜的系統,本文所述只是冰山一角,從宏觀角度給你展現記憶體管理的全貌,但一般來說,這些知識在你和麵試官聊天的時候還是夠用的,當然也希望大家能透過閱讀了解更深層的原理。

本文可以作為一個索引一樣的學習指南,當你想深入某一點學習的時候可以在這些章節裡找到切入點,以及這個知識點在記憶體管理宏觀上的位置。

本文創作過程我也畫了大量的示例圖解,可以作為知識索引,個人感覺看圖還是比看文字更清晰明了,你可以在我公眾號“後端技術學堂”後台回复“記憶體管理」取得這些圖片的高清原圖。

老規矩,感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反复求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習。今天的技術分享就到這裡,我們下期再見。

以上是別再說你不懂Linux記憶體管理了,10張圖給你安排的明明白白!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除