面試問了解Linux記憶體管理嗎?10張圖給你安排的明明白白!
文章每週持續更新,各位的「三連」是對我最大的肯定。可以微信搜尋公眾號「 後端技術學堂 」第一時間閱讀(一般比部落格早更新一到兩篇)
今天來帶大家研究一下Linux
記憶體管理。對於精通 CURD
的業務同學,記憶體管理好像離我們很遠,但這個知識點雖然冷門(估計很多人學完根本就沒機會用上)但絕對是基礎中的基礎,這就像武俠中的內功修煉,學完之後看不到立竿見影的效果,但對你日後的開發工作是大有裨益的,因為你站的更高了。
文中所有示例圖都是我親手畫的,畫圖比碼字還費時間,但是看圖理解比文字更直觀,需要高清示例圖片的同學,文末有獲取方式自取。
再功利點的說,面試的時候不經意間透露你懂這方面知識,並且能說出個一二三來,也許能讓面試官對你更有興趣,離升職加薪,走上人生巔峰又近了一步。
前提約定:本文討論技術內容前提,作業系統環境都是 x86
架構的 32 位 Linux
系統。
虛擬地址
即使是現代作業系統中,記憶體依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看記憶體大小就知道了。為了充分利用和管理系統記憶體資源,Linux採用虛擬記憶體管理技術,利用虛擬記憶體技術讓每個程序都有4GB
互不干涉的虛擬地址空間。
程序初始化分配和操作的都是基於這個「虛擬地址」,只有當程序需要實際訪問記憶體資源的時候才會建立虛擬地址和實體地址的對映,調入實體記憶體頁。
打個不是很恰當的比方。這個原理其實和現在的某某網盤一樣,假如你的網盤空間是1TB
,真以為就一口氣給了你這麼大空間嗎?那還是太年輕,都是在你往裡面放東西的時候才給你分配空間,你放多少就分多少實際空間給你,但你和你朋友看起來就像大家都擁有1TB
虛擬地址的好處
- 避免使用者直接訪問實體記憶體地址,防止一些破壞性操作,保護作業系統
- 每個程序都被分配了4GB的虛擬記憶體,使用者程式可使用比實際實體記憶體更大的地址空間
4GB
的程序虛擬地址空間被分成兩部分:「使用者空間」和「核心空間」
實體地址
上面章節我們已經知道不管是使用者空間還是核心空間,使用的地址都是虛擬地址,當需程序要實際訪問記憶體的時候,會由核心的「請求分頁機制」產生「缺頁異常」調入實體記憶體頁。
把虛擬地址轉換成記憶體的實體地址,這中間涉及利用MMU
記憶體管理單元(Memory Management Unit ) 對虛擬地址分段和分頁(段頁式)地址轉換,關於分段和分頁的具體流程,這裡不再贅述,可以參考任何一本計算機組成原理教材描述。
Linux
核心會將實體記憶體分為3個管理區,分別是:
ZONE_DMA
DMA
記憶體區域。包含0MB~16MB之間的記憶體頁框,可以由老式基於ISA
的裝置通過DMA
使用,直接對映到核心的地址空間。
ZONE_NORMAL
普通記憶體區域。包含16MB~896MB之間的記憶體頁框,常規頁框,直接對映到核心的地址空間。
ZONE_HIGHMEM
高階記憶體區域。包含896MB以上的記憶體頁框,不進行直接對映,可以通過永久對映和臨時對映進行這部分記憶體頁框的訪問。
使用者空間
使用者程序能訪問的是「使用者空間」,每個程序都有自己獨立的使用者空間,虛擬地址範圍從從 0x00000000
至 0xBFFFFFFF
總容量3G 。
使用者程序通常只能訪問使用者空間的虛擬地址,只有在執行內陷操作或系統呼叫時才能訪問核心空間。
程序與記憶體
程序(執行的程式)佔用的使用者空間按照「 訪問屬性一致的地址空間存放在一起 」的原則,劃分成 5
個不同的記憶體區域。 訪問屬性指的是“可讀、可寫、可執行等 。
-
程式碼段
程式碼段是用來存放可執行檔案的操作指令,可執行程式在記憶體中的映象。程式碼段需要防止在執行時被非法修改,所以只准許讀取操作,它是不可寫的。
-
資料段
資料段用來存放可執行檔案中已初始化全域性變數,換句話說就是存放程式靜態分配的變數和全域性變數。
-
BSS段
BSS
段包含了程式中未初始化的全域性變數,在記憶體中bss
段全部置零。 -
堆
heap
堆是用於存放程序執行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當程序呼叫malloc等函式分配記憶體時,新分配的記憶體就被動態新增到堆上(堆被擴張);當利用free等函式釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)
-
棧
stack
棧是使用者存放程式臨時建立的區域性變數,也就是函式中定義的變數(但不包括
static
宣告的變數,static意味著在資料段中存放變數)。除此以外,在函式被呼叫時,其引數也會被壓入發起呼叫的程序棧中,並且待到呼叫結束後,函式的返回值也會被存放回棧中。由於棧的先進先出特點,所以棧特別方便用來儲存/恢復呼叫現場。從這個意義上講,我們可以把堆疊看成一個寄存、交換臨時資料的記憶體區。
上述幾種記憶體區域中資料段、BSS
段、堆通常是被連續儲存在記憶體中,在位置上是連續的,而程式碼段和棧往往會被獨立存放。堆和棧兩個區域在 i386
體系結構中棧向下擴充套件、堆向上擴充套件,相對而生。
你也可以再linux下用size
命令檢視編譯後程序的各個記憶體區域大小:
[lemon ~]# size /usr/local/sbin/sshd
text data bss dec hex filename
1924532 12412 426896 2363840 2411c0 /usr/local/sbin/sshd
核心空間
在 x86 32
位系統裡,Linux 核心地址空間是指虛擬地址從 0xC0000000
開始到 0xFFFFFFFF
為止的高階記憶體地址空間,總計 1G
的容量, 包括了核心映象、物理頁面表、驅動程式等執行在核心空間 。
直接對映區
直接對映區 Direct Memory Region
:從核心空間起始地址開始,最大896M
的核心空間地址區間,為直接記憶體對映區。
直接對映區的896MB的「線性地址」直接與「實體地址」的前896MB
進行對映,也就是說線性地址和分配的實體地址都是連續的。核心地址空間的線性地址0xC0000001
所對應的實體地址為0x00000001
,它們之間相差一個偏移量PAGE_OFFSET = 0xC0000000
該區域的線性地址和實體地址存線上性轉換關係「線性地址 = PAGE_OFFSET
+ 實體地址」也可以用 virt_to_phys()
函式將核心虛擬空間中的線性地址轉化為實體地址。
高階記憶體線性地址空間
核心空間線性地址從 896M 到 1G 的區間,容量 128MB 的地址區間是高階記憶體線性地址空間,為什麼叫高階記憶體線性地址空間?下面給你解釋一下:
前面已經說過,核心空間的總大小 1GB,從核心空間起始地址開始的 896MB 的線性地址可以直接對映到實體地址大小為 896MB 的地址區間。退一萬步,即使核心空間的1GB線性地址都對映到實體地址,那也最多隻能定址 1GB 大小的實體記憶體地址範圍。
請問你現在你家的記憶體條多大?快醒醒都 0202 年了,一般 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程序可以劃分為 5 個不同的記憶體區域,分別是:程式碼段、資料段、BSS
、堆、棧,核心管理這些區域的方式是,將這些記憶體區域抽象成vm_area_struct
的記憶體管理物件。
vm_area_struct
是描述程序地址空間的基本管理單元,一個程序往往需要多個vm_area_struct
來描述它的使用者空間虛擬地址,需要使用「連結串列」和「紅黑樹」來組織各個vm_area_struct
。
連結串列用於需要遍歷全部節點的時候用,而紅黑樹適用於在地址空間中定位特定記憶體區域。核心為了記憶體區域上的各種不同操作都能獲得高效能,所以同時使用了這兩種資料結構。
使用者空間程序的地址管理模型:
核心空間動態分配記憶體資料結構
在核心空間章節我們提到過「動態記憶體對映區」,該區域由核心函式vmalloc
來分配,特點是:線性空間連續,但是對應的實體地址空間不一定連續。 vmalloc
分配的線性地址所對應的物理頁可能處於低端記憶體,也可能處於高階記憶體。
vmalloc
分配的地址則限於vmalloc_start
與vmalloc_end
之間。每一塊vmalloc
分配的核心虛擬記憶體都對應一個vm_struct
結構體,不同的核心空間虛擬地址之間有4k
大小的防越界空閒區間隔區。與使用者空間的虛擬地址特性一樣,這些虛擬地址與實體記憶體沒有簡單的對映關係,必須通過核心頁表才可轉換為實體地址或物理頁,它們有可能尚未被對映,當發生缺頁時才真正分配物理頁面。
總結一下
Linux
記憶體管理是一個非常複雜的系統,本文所述只是冰山一角,從巨集觀角度給你展現記憶體管理的全貌,但一般來說,這些知識在你和麵試官聊天的時候還是夠用的,當然我也希望大家能夠通過讀書瞭解更深層次的原理。
希望這篇文章可以作為一個索引一樣的學習指南,當你想深入某一點學習的時候可以在這些章節裡找到切入點,以及這個知識點在記憶體管理巨集觀上的位置。
本文創作過程我也畫了大量的示例圖解,可以作為知識索引,個人感覺看圖還是比看文字更清晰明瞭,你可以在我公眾號「後端技術學堂」後臺回覆「記憶體管理」獲取這些圖片的高清原圖。
老規矩,感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習。今天的技術分享就到這裡,我們下期再見。
原創不易,看到這裡,如果在我這有一點點收穫,就動動手指「轉發」和「在看」是對我最大的支援。
我的更多精彩文章:
非常詳細的 Linux C/C++ 學習路線總結!已拿騰訊offer
面試官又來喊你造飛機了,你來說說看微服務介面怎麼設計?
面試都在問的微服務、服務治理、RPC、下一代微服務框架... 一文帶你徹底搞懂!
Linux下「程序」出問題不要慌,資深程式設計師教你6招搞定!
面試官:你說對MySQL事務很熟?那我問你10個問題
我用大資料分析了一線城市1000多份崗位招聘需求,告訴你如何科學找工作
騰訊後臺開發面試筆試C++知識點參考筆記
還能這麼玩?我用VsCode畫類圖、流程圖、時序圖、狀態圖不要太爽!
面試官:你會幾種redis分散式鎖?我會三種!
最詳細的個人部落格教程搭建教程GithubPages+Jekyll 簡約風格部落格
創作不易,點贊關注支援一下吧
可以微信搜尋公眾號「 後端技術學堂 」回覆「資料」有我給你準備的各種程式設計學習資料。文章每週持續更新,我們下期見!