怎么應(yīng)付面試問了解Linux內(nèi)存管理嗎?
今天,我將帶您了解Linux內(nèi)存管理。 對于精通CURD的學(xué)生,內(nèi)存管理似乎離我們還很遠(yuǎn),但是盡管這一知識點并不熱門(據(jù)估計,很多人在學(xué)習(xí)后不會再使用它),但這無疑是基礎(chǔ)。 雖然學(xué)完后沒有看到即時的效果,但是它將對您將來的開發(fā)工作非常有益。
文中所有示例圖都是我親手畫的,畫圖比碼字還費(fèi)時間,但是看圖理解比文字更直觀,需要高清示例圖片的同學(xué),文末有獲取方式自取。
再功利點的說,面試的時候不經(jīng)意間透露你懂這方面知識,并且能說出個一二三來,也許能讓面試官對你更有興趣,離升職加薪,走上人生巔峰又近了一步。
前提約定:本文討論技術(shù)內(nèi)容前提,操作系統(tǒng)環(huán)境都是 x86架構(gòu)的 32 位 Linux系統(tǒng)。
虛擬地址
即使是現(xiàn)代操作系統(tǒng)中,內(nèi)存依然是計算機(jī)中很寶貴的資源,看看你電腦幾個T固態(tài)硬盤,再看看內(nèi)存大小就知道了。為了充分利用和管理系統(tǒng)內(nèi)存資源,Linux采用虛擬內(nèi)存管理技術(shù),利用虛擬內(nèi)存技術(shù)讓每個進(jìn)程都有4GB 互不干涉的虛擬地址空間。
進(jìn)程初始化分配和操作的都是基于這個「虛擬地址」,只有當(dāng)進(jìn)程需要實際訪問內(nèi)存資源的時候才會建立虛擬地址和物理地址的映射,調(diào)入物理內(nèi)存頁。
打個不是很恰當(dāng)?shù)谋确?。這個原理其實和現(xiàn)在的某某網(wǎng)盤一樣,假如你的網(wǎng)盤空間是1TB,真以為就一口氣給了你這么大空間嗎?那還是太年輕,都是在你往里面放東西的時候才給你分配空間,你放多少就分多少實際空間給你,但你和你朋友看起來就像大家都擁有1TB空間一樣。
虛擬地址的好處
避免用戶直接訪問物理內(nèi)存地址,防止一些破壞性操作,保護(hù)操作系統(tǒng)每個進(jìn)程都被分配了4GB的虛擬內(nèi)存,用戶程序可使用比實際物理內(nèi)存更大的地址空間4GB 的進(jìn)程虛擬地址空間被分成兩部分:「用戶空間」和「內(nèi)核空間」
物理地址
上面章節(jié)我們已經(jīng)知道不管是用戶空間還是內(nèi)核空間,使用的地址都是虛擬地址,當(dāng)需進(jìn)程要實際訪問內(nèi)存的時候,會由內(nèi)核的「請求分頁機(jī)制」產(chǎn)生「缺頁異?!拐{(diào)入物理內(nèi)存頁。
把虛擬地址轉(zhuǎn)換成內(nèi)存的物理地址,這中間涉及利用MMU 內(nèi)存管理單元(Memory Management Unit ) 對虛擬地址分段和分頁(段頁式)地址轉(zhuǎn)換,關(guān)于分段和分頁的具體流程,這里不再贅述,可以參考任何一本計算機(jī)組成原理教材描述。
Linux 內(nèi)核會將物理內(nèi)存分為3個管理區(qū),分別是:
ZONE_DMA
DMA內(nèi)存區(qū)域。包含0MB~16MB之間的內(nèi)存頁框,可以由老式基于ISA的設(shè)備通過DMA使用,直接映射到內(nèi)核的地址空間。
ZONE_NORMAL
普通內(nèi)存區(qū)域。包含16MB~896MB之間的內(nèi)存頁框,常規(guī)頁框,直接映射到內(nèi)核的地址空間。
ZONE_HIGHMEM
高端內(nèi)存區(qū)域。包含896MB以上的內(nèi)存頁框,不進(jìn)行直接映射,可以通過永久映射和臨時映射進(jìn)行這部分內(nèi)存頁框的訪問。
用戶空間
用戶進(jìn)程能訪問的是「用戶空間」,每個進(jìn)程都有自己獨立的用戶空間,虛擬地址范圍從從 0x00000000 至 0xBFFFFFFF 總?cè)萘?G 。
用戶進(jìn)程通常只能訪問用戶空間的虛擬地址,只有在執(zhí)行內(nèi)陷操作或系統(tǒng)調(diào)用時才能訪問內(nèi)核空間。
進(jìn)程與內(nèi)存
進(jìn)程(執(zhí)行的程序)占用的用戶空間按照「 訪問屬性一致的地址空間存放在一起 」的原則,劃分成 5個不同的內(nèi)存區(qū)域。 訪問屬性指的是“可讀、可寫、可執(zhí)行等 。
代碼段代碼段是用來存放可執(zhí)行文件的操作指令,可執(zhí)行程序在內(nèi)存中的鏡像。代碼段需要防止在運(yùn)行時被非法修改,所以只準(zhǔn)許讀取操作,它是不可寫的。數(shù)據(jù)段數(shù)據(jù)段用來存放可執(zhí)行文件中已初始化全局變量,換句話說就是存放程序靜態(tài)分配的變量和全局變量。BSS段BSS段包含了程序中未初始化的全局變量,在內(nèi)存中 bss 段全部置零。堆 heap堆是用于存放進(jìn)程運(yùn)行中被動態(tài)分配的內(nèi)存段,它的大小并不固定,可動態(tài)擴(kuò)張或縮減。當(dāng)進(jìn)程調(diào)用malloc等函數(shù)分配內(nèi)存時,新分配的內(nèi)存就被動態(tài)添加到堆上(堆被擴(kuò)張);當(dāng)利用free等函數(shù)釋放內(nèi)存時,被釋放的內(nèi)存從堆中被剔除(堆被縮減)棧 stack棧是用戶存放程序臨時創(chuàng)建的局部變量,也就是函數(shù)中定義的變量(但不包括 static 聲明的變量,static意味著在數(shù)據(jù)段中存放變量)。除此以外,在函數(shù)被調(diào)用時,其參數(shù)也會被壓入發(fā)起調(diào)用的進(jìn)程棧中,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會被存放回棧中。由于棧的先進(jìn)先出特點,所以棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場。從這個意義上講,我們可以把堆??闯梢粋€寄存、交換臨時數(shù)據(jù)的內(nèi)存區(qū)。上述幾種內(nèi)存區(qū)域中數(shù)據(jù)段、BSS段、堆通常是被連續(xù)存儲在內(nèi)存中,在位置上是連續(xù)的,而代碼段和棧往往會被獨立存放。堆和棧兩個區(qū)域在i386體系結(jié)構(gòu)中棧向下擴(kuò)展、堆向上擴(kuò)展,相對而生。
你也可以再linux下用size 命令查看編譯后程序的各個內(nèi)存區(qū)域大?。?
內(nèi)核空間
在 x86 32 位系統(tǒng)里,Linux 內(nèi)核地址空間是指虛擬地址從 0xC0000000 開始到 0xFFFFFFFF 為止的高端內(nèi)存地址空間,總計 1G 的容量, 包括了內(nèi)核鏡像、物理頁面表、驅(qū)動程序等運(yùn)行在內(nèi)核空間 。
直接映射區(qū)
直接映射區(qū) Direct Memory Region:從內(nèi)核空間起始地址開始,最大896M的內(nèi)核空間地址區(qū)間,為直接內(nèi)存映射區(qū)。
直接映射區(qū)的896MB的「線性地址」直接與「物理地址」的前896MB進(jìn)行映射,也就是說線性地址和分配的物理地址都是連續(xù)的。內(nèi)核地址空間的線性地址0xC0000001所對應(yīng)的物理地址為0x00000001,它們之間相差一個偏移量PAGE_OFFSET = 0xC0000000該區(qū)域的線性地址和物理地址存在線性轉(zhuǎn)換關(guān)系「線性地址 = PAGE_OFFSET + 物理地址」也可以用 virt_to_phys()函數(shù)將內(nèi)核虛擬空間中的線性地址轉(zhuǎn)化為物理地址。
高端內(nèi)存線性地址空間
內(nèi)核空間線性地址從 896M 到 1G 的區(qū)間,容量 128MB 的地址區(qū)間是高端內(nèi)存線性地址空間,為什么叫高端內(nèi)存線性地址空間?下面給你解釋一下:
前面已經(jīng)說過,內(nèi)核空間的總大小 1GB,從內(nèi)核空間起始地址開始的 896MB 的線性地址可以直接映射到物理地址大小為 896MB 的地址區(qū)間。退一萬步,即使內(nèi)核空間的1GB線性地址都映射到物理地址,那也最多只能尋址 1GB 大小的物理內(nèi)存地址范圍。
請問你現(xiàn)在你家的內(nèi)存條多大?快醒醒都0202年了,一般PC的內(nèi)存都大于1GB了吧!
所以,內(nèi)核空間拿出了最后的 128M 地址區(qū)間,劃分成下面三個高端內(nèi)存映射區(qū),以達(dá)到對整個物理地址范圍的尋址。而在 64 位的系統(tǒng)上就不存在這樣的問題了,因為可用的線性地址空間遠(yuǎn)大于可安裝的內(nèi)存。
動態(tài)內(nèi)存映射區(qū)
vmalloc Region 該區(qū)域由內(nèi)核函數(shù)vmalloc來分配,特點是:線性空間連續(xù),但是對應(yīng)的物理地址空間不一定連續(xù)。 vmalloc 分配的線性地址所對應(yīng)的物理頁可能處于低端內(nèi)存,也可能處于高端內(nèi)存。
永久內(nèi)存映射區(qū)
Persistent Kernel Mapping Region 該區(qū)域可訪問高端內(nèi)存。訪問方法是使用 alloc_page (_GFP_HIGHMEM) 分配高端內(nèi)存頁或者使用kmap函數(shù)將分配到的高端內(nèi)存映射到該區(qū)域。
固定映射區(qū)
Fixing kernel Mapping Region 該區(qū)域和 4G 的頂端只有 4k 的隔離帶,其每個地址項都服務(wù)于特定的用途,如 ACPI_BASE 等。
回顧一下
上面講的有點多,先別著急進(jìn)入下一節(jié),在這之前我們再來回顧一下上面所講的內(nèi)容。如果認(rèn)真看完上面的章節(jié),我這里再畫了一張圖,現(xiàn)在你的腦海中應(yīng)該有這樣一個內(nèi)存管理的全局圖。
內(nèi)存數(shù)據(jù)結(jié)構(gòu)
要讓內(nèi)核管理系統(tǒng)中的虛擬內(nèi)存,必然要從中抽象出內(nèi)存管理數(shù)據(jù)結(jié)構(gòu),內(nèi)存管理操作如「分配、釋放等」都基于這些數(shù)據(jù)結(jié)構(gòu)操作,這里列舉兩個管理虛擬內(nèi)存區(qū)域的數(shù)據(jù)結(jié)構(gòu)。
用戶空間內(nèi)存數(shù)據(jù)結(jié)構(gòu)
在前面「進(jìn)程與內(nèi)存」章節(jié)我們提到,Linux進(jìn)程可以劃分為 5 個不同的內(nèi)存區(qū)域,分別是:代碼段、數(shù)據(jù)段、BSS、堆、棧,內(nèi)核管理這些區(qū)域的方式是,將這些內(nèi)存區(qū)域抽象成vm_area_struct的內(nèi)存管理對象。
vm_area_struct是描述進(jìn)程地址空間的基本管理單元,一個進(jìn)程往往需要多個vm_area_struct來描述它的用戶空間虛擬地址,需要使用「鏈表」和「紅黑樹」來組織各個vm_area_struct。
鏈表用于需要遍歷全部節(jié)點的時候用,而紅黑樹適用于在地址空間中定位特定內(nèi)存區(qū)域。內(nèi)核為了內(nèi)存區(qū)域上的各種不同操作都能獲得高性能,所以同時使用了這兩種數(shù)據(jù)結(jié)構(gòu)。
用戶空間進(jìn)程的地址管理模型:
內(nèi)核空間動態(tài)分配內(nèi)存數(shù)據(jù)結(jié)構(gòu)
在內(nèi)核空間章節(jié)我們提到過「動態(tài)內(nèi)存映射區(qū)」,該區(qū)域由內(nèi)核函數(shù)vmalloc來分配,特點是:線性空間連續(xù),但是對應(yīng)的物理地址空間不一定連續(xù)。
vmalloc 分配的線性地址所對應(yīng)的物理頁可能處于低端內(nèi)存,也可能處于高端內(nèi)存。
vmalloc 分配的地址則限于vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內(nèi)核虛擬內(nèi)存都對應(yīng)一個vm_struct結(jié)構(gòu)體,不同的內(nèi)核空間虛擬地址之間有4k大小的防越界空閑區(qū)間隔區(qū)。與用戶空間的虛擬地址特性一樣,這些虛擬地址與物理內(nèi)存沒有簡單的映射關(guān)系,必須通過內(nèi)核頁表才可轉(zhuǎn)換為物理地址或物理頁,它們有可能尚未被映射,當(dāng)發(fā)生缺頁時才真正分配物理頁面。
總結(jié)一下
Linux內(nèi)存管理是一個非常復(fù)雜的系統(tǒng),本文所述只是冰山一角,從宏觀角度給你展現(xiàn)內(nèi)存管理的全貌,但一般來說,這些知識在你和面試官聊天的時候還是夠用的,當(dāng)然我也希望大家能夠通過讀書了解更深層次的原理。