避開 Linux OOM:先懂動(dòng)態(tài)內(nèi)存管理
在 Linux 操作系統(tǒng)的龐大體系中,內(nèi)存管理堪稱核心樞紐,其重要性怎么強(qiáng)調(diào)都不為過。它就像是一位技藝精湛的大管家,有條不紊地掌控著系統(tǒng)內(nèi)存資源的分配與回收,確保整個(gè)系統(tǒng)穩(wěn)定、高效地運(yùn)行。從系統(tǒng)性能的角度來看,內(nèi)存管理直接影響著計(jì)算機(jī)的運(yùn)行速度和響應(yīng)能力。當(dāng)我們在 Linux 系統(tǒng)中同時(shí)開啟多個(gè)應(yīng)用程序,或是運(yùn)行復(fù)雜的計(jì)算任務(wù)時(shí),高效的內(nèi)存管理機(jī)制能夠確保每個(gè)進(jìn)程都能及時(shí)獲得所需的內(nèi)存資源,避免因內(nèi)存不足而導(dǎo)致的程序卡頓甚至系統(tǒng)崩潰。這就好比在繁忙的交通樞紐,交通管理員合理地指揮車輛通行,使得道路保持暢通,車流暢行無阻。
在多任務(wù)處理環(huán)境下,內(nèi)存管理更是保障了各個(gè)進(jìn)程的獨(dú)立性和安全性。它為每個(gè)進(jìn)程分配獨(dú)立的內(nèi)存空間,使得不同進(jìn)程之間的內(nèi)存訪問相互隔離,避免了進(jìn)程間的內(nèi)存沖突和非法訪問,就如同為每個(gè)居民分配獨(dú)立的居住空間,保障了各自的隱私和安全。而動(dòng)態(tài)內(nèi)存管理機(jī)制,作為 Linux 內(nèi)存管理的關(guān)鍵組成部分,更是在運(yùn)行時(shí)根據(jù)程序的實(shí)際需求動(dòng)態(tài)地分配和釋放內(nèi)存,極大地提高了內(nèi)存的利用率和系統(tǒng)的靈活性,是我們深入理解 Linux 系統(tǒng)運(yùn)行機(jī)制的關(guān)鍵所在。接下來,就讓我們一同深入探索 Linux 動(dòng)態(tài)內(nèi)存管理機(jī)制的神秘世界吧。
一、Linux 內(nèi)存管理基礎(chǔ)概念
在深入探究 Linux 動(dòng)態(tài)內(nèi)存管理機(jī)制之前,我們先來夯實(shí)一下基礎(chǔ),了解幾個(gè)關(guān)鍵的內(nèi)存管理基礎(chǔ)概念,它們就像是搭建高樓大廈的基石,是我們后續(xù)深入學(xué)習(xí)的重要前提。
1.1 虛擬內(nèi)存與物理內(nèi)存
物理內(nèi)存,是計(jì)算機(jī)硬件層面實(shí)實(shí)在在存在的內(nèi)存條,通常也被稱為隨機(jī)存取存儲器(Random Access Memory,RAM) ,它就像是計(jì)算機(jī)的 “高速緩存區(qū)”,直接與 CPU 進(jìn)行數(shù)據(jù)交互,為正在運(yùn)行的程序提供即時(shí)的數(shù)據(jù)存儲和讀取服務(wù),其讀寫速度極快,能夠滿足 CPU 對數(shù)據(jù)的快速訪問需求,確保程序的高效運(yùn)行。例如,當(dāng)我們運(yùn)行一個(gè)辦公軟件時(shí),軟件的代碼和正在處理的數(shù)據(jù)就會被加載到物理內(nèi)存中,CPU 可以迅速從中讀取和寫入數(shù)據(jù),讓我們能夠流暢地進(jìn)行文字編輯、表格制作等操作。
而虛擬內(nèi)存則是 Linux 操作系統(tǒng)精心構(gòu)建的一種內(nèi)存管理機(jī)制,它為每個(gè)進(jìn)程營造出一種擁有獨(dú)立且連續(xù)內(nèi)存空間的假象 ,讓進(jìn)程誤以為自己獨(dú)占了大量的內(nèi)存資源,而無需擔(dān)憂實(shí)際物理內(nèi)存的有限性和復(fù)雜布局。這就好比每個(gè)進(jìn)程都擁有一個(gè)專屬的 “虛擬大倉庫”,可以自由地規(guī)劃和使用內(nèi)存空間,而不必在意倉庫的實(shí)際大小和位置。虛擬內(nèi)存的實(shí)現(xiàn)依賴于硬盤空間,當(dāng)物理內(nèi)存不足時(shí),操作系統(tǒng)會將部分暫時(shí)不使用的內(nèi)存頁數(shù)據(jù)轉(zhuǎn)移到硬盤的交換空間(swap space)中保存起來 ,就像是把倉庫里暫時(shí)不用的貨物搬到了倉庫外的臨時(shí)存儲區(qū)。當(dāng)進(jìn)程再次需要這些數(shù)據(jù)時(shí),再將其從交換空間重新加載回物理內(nèi)存中,這個(gè)過程就如同從臨時(shí)存儲區(qū)取回貨物放回倉庫一樣。
虛擬內(nèi)存對于 Linux 系統(tǒng)而言,重要性不言而喻。它極大地拓展了系統(tǒng)的內(nèi)存使用范圍,使得系統(tǒng)能夠運(yùn)行那些內(nèi)存需求超過物理內(nèi)存容量的大型程序,為用戶提供了更為強(qiáng)大的計(jì)算能力和更豐富的應(yīng)用體驗(yàn)。同時(shí),虛擬內(nèi)存還在進(jìn)程隔離和內(nèi)存保護(hù)方面發(fā)揮著關(guān)鍵作用,它為每個(gè)進(jìn)程分配獨(dú)立的虛擬地址空間,使得進(jìn)程之間的內(nèi)存訪問相互隔離,避免了進(jìn)程間的內(nèi)存沖突和非法訪問,就像為每個(gè)倉庫都設(shè)置了獨(dú)立的門禁系統(tǒng),保障了各個(gè)倉庫的安全和隱私。
1.2 內(nèi)存空間劃分:用戶空間與內(nèi)核空間
在 Linux 系統(tǒng)中,虛擬內(nèi)存空間被細(xì)致地劃分為兩個(gè)重要部分:用戶空間和內(nèi)核空間 。對于 32 位操作系統(tǒng)來說,其尋址空間(虛擬地址空間)為 4GB,在這 4GB 的虛擬地址空間里,最高的 1GB 字節(jié)(從虛擬地址 0xC0000000 到 0xFFFFFFFF)被內(nèi)核所占用,這部分空間被稱為內(nèi)核空間 ;而較低的 3GB 字節(jié)(從虛擬地址 0x00000000 到 0xBFFFFFFF)則分配給各個(gè)進(jìn)程使用,這便是用戶空間 ??梢赃@樣理解,每個(gè)進(jìn)程都擁有 4GB 的虛擬地址空間,其中最高的 1GB 是所有進(jìn)程共享的內(nèi)核空間,剩下的 3GB 才是進(jìn)程自己獨(dú)有的用戶空間。
用戶空間是應(yīng)用程序的 “專屬領(lǐng)地”,是用戶程序運(yùn)行的主場。在用戶空間中,進(jìn)程運(yùn)行在用戶地址空間內(nèi),被執(zhí)行的代碼要接受 CPU 的嚴(yán)格檢查,只能訪問映射其地址空間的頁表項(xiàng)中規(guī)定的在用戶態(tài)下可訪問頁面的虛擬地址 ,并且只能對任務(wù)狀態(tài)段(Task State Segment,TSS)中 I/O 許可位圖(I/O Permission Bitmap)里規(guī)定的可訪問端口進(jìn)行直接訪問 。簡單來說,用戶空間的進(jìn)程就像是被 “戴上了枷鎖”,其操作權(quán)限受到了諸多限制,這樣的設(shè)計(jì)主要是為了保護(hù)系統(tǒng)的穩(wěn)定性和安全性,防止用戶程序的錯(cuò)誤操作或惡意行為對系統(tǒng)造成嚴(yán)重破壞。比如,當(dāng)我們運(yùn)行一個(gè)普通的游戲程序時(shí),這個(gè)游戲程序就在用戶空間中運(yùn)行,它只能訪問自己被分配的內(nèi)存區(qū)域和特定的端口,無法隨意篡改系統(tǒng)內(nèi)核數(shù)據(jù)或直接訪問硬件設(shè)備,從而確保了系統(tǒng)的穩(wěn)定運(yùn)行,不會因?yàn)橛螒虺绦虻谋罎⒍鴮?dǎo)致整個(gè)系統(tǒng)癱瘓。
內(nèi)核空間則是操作系統(tǒng)內(nèi)核的 “核心地帶”,是一個(gè)受到嚴(yán)格保護(hù)的區(qū)域,只有操作系統(tǒng)內(nèi)核以及與之緊密相關(guān)的程序才有資格訪問 。內(nèi)核空間擁有至高無上的權(quán)限,它可以直接訪問系統(tǒng)的硬件資源和內(nèi)核數(shù)據(jù)結(jié)構(gòu) ,能夠執(zhí)行特權(quán)指令,全面掌控操作系統(tǒng)內(nèi)核的所有功能 。內(nèi)核就像是系統(tǒng)的 “大管家”,負(fù)責(zé)系統(tǒng)的硬件管理、進(jìn)程調(diào)度、內(nèi)存管理、文件系統(tǒng)操作、網(wǎng)絡(luò)通信等一系列關(guān)鍵任務(wù) ,為上層應(yīng)用程序提供穩(wěn)定、高效的運(yùn)行環(huán)境和豐富的服務(wù)接口。
例如,當(dāng)我們在 Linux 系統(tǒng)中進(jìn)行文件讀寫操作時(shí),應(yīng)用程序會通過系統(tǒng)調(diào)用向內(nèi)核發(fā)起請求,內(nèi)核在接收到請求后,會在內(nèi)核空間中完成對磁盤的實(shí)際讀寫操作,然后將數(shù)據(jù)返回給用戶空間的應(yīng)用程序,整個(gè)過程就像是用戶向管家提出需求,管家負(fù)責(zé)具體執(zhí)行并將結(jié)果反饋給用戶。
二、動(dòng)態(tài)內(nèi)存管理機(jī)制原理
2.1 分頁機(jī)制
分頁機(jī)制是 Linux 動(dòng)態(tài)內(nèi)存管理的重要基石,它巧妙地將程序的邏輯地址空間和物理內(nèi)存劃分為固定大小的頁(page)和頁框(page frame) ,就像是把一本厚厚的書籍按照固定的章節(jié)(頁)進(jìn)行劃分,每個(gè)章節(jié)都有對應(yīng)的書架位置(頁框)。在 Linux 系統(tǒng)中,頁的大小通常為 4KB ,當(dāng)然,根據(jù)不同的系統(tǒng)配置和硬件平臺,頁的大小也可能會有所調(diào)整,比如在一些高性能計(jì)算場景中,可能會采用更大的頁大小來提高內(nèi)存訪問效率。
在分頁機(jī)制下,程序加載時(shí),系統(tǒng)可以將任意一頁放入內(nèi)存中的任意一個(gè)頁框,這些頁框不必連續(xù) ,這就如同書籍的章節(jié)可以分散放置在書架的不同位置,只要有對應(yīng)的索引(頁表)能夠找到它們即可。這種離散分配方式極大地提高了內(nèi)存的利用率,有效避免了連續(xù)分配內(nèi)存時(shí)可能出現(xiàn)的內(nèi)存碎片問題 ,就像把書籍隨意擺放,只要有索引就能找到想要的章節(jié),而不會因?yàn)闀芸臻g不連續(xù)而無法放置書籍。
分頁機(jī)制如何啟用:
在我們進(jìn)行程序開發(fā)的時(shí)候,一般情況下,是不需要管理內(nèi)存的,也不需要操心內(nèi)存夠不夠用,其實(shí),這就是分頁機(jī)制給我們帶來的好處。它是實(shí)現(xiàn)虛擬存儲的關(guān)鍵,位于線性地址與物理地址之間,在使用這種內(nèi)存分頁管理方法時(shí),每個(gè)執(zhí)行中的進(jìn)程(任務(wù))可以使用比實(shí)際內(nèi)存容量大得多的連續(xù)地址空間。而且當(dāng)系統(tǒng)內(nèi)存實(shí)際上被分成很多凌亂的塊時(shí),它可以建立一個(gè)大而連續(xù)的內(nèi)存空間的映象,好讓程序不用操心和管理這些分散的內(nèi)存塊。分頁機(jī)制增強(qiáng)了分段機(jī)制的性能。頁地址變換是建立在段變換基礎(chǔ)之上的。因?yàn)?,段管理機(jī)制對于Intel處理器來說是最基本的,任何時(shí)候都無法關(guān)閉。所以即使啟用了頁管理功能,分段機(jī)制依然是起作用的,段部件也依然工作。
分頁只能在保護(hù)模式(CR0.PE = 1)下使用。在保護(hù)模式下,是否開啟分頁,由 CR0. PG 位(位 31)決定:
- 當(dāng) CR0.PG = 0 時(shí),未開啟分頁,線性地址等同于物理地址;
- 當(dāng) CR0.PG = 1 時(shí),開啟分頁。
為了實(shí)現(xiàn)虛擬頁到物理頁框的映射,Linux 使用了多級頁表 ,這就像是一個(gè)復(fù)雜的目錄系統(tǒng),通過層層索引,能夠快速準(zhǔn)確地找到所需的頁面。以常見的四級頁表為例,當(dāng) CPU 訪問一個(gè)虛擬地址時(shí),它會首先根據(jù)虛擬地址的高位部分索引頁全局目錄(Page Global Directory,PGD) ,就像在一個(gè)大型圖書館中,首先通過總目錄找到對應(yīng)的書架區(qū)域;然后使用頁全局目錄項(xiàng)的高位部分索引頁上級目錄(Page Upper Directory,PUD) ,接著再通過頁上級目錄項(xiàng)的高位部分索引頁中間目錄(Page Middle Directory,PMD) ,最后使用頁中間目錄項(xiàng)的高位部分索引頁表(Page Table Entry,PTE) ,從而得到物理頁框號,再結(jié)合虛擬地址的低位部分(頁內(nèi)偏移),就可以計(jì)算出最終的物理地址 。整個(gè)過程就像是在圖書館中,通過層層目錄找到具體的書籍位置。
在這個(gè)過程中,為了加速地址轉(zhuǎn)換,CPU 還引入了快表(Translation Lookaside Buffer,TLB) ,它就像是一個(gè)常用書籍的快速索引,緩存了最近訪問的頁表項(xiàng) 。當(dāng) CPU 進(jìn)行地址轉(zhuǎn)換時(shí),會首先在 TLB 中查找,如果能夠找到對應(yīng)的頁表項(xiàng),就可以直接得到物理地址,而無需再去訪問內(nèi)存中的頁表,大大提高了地址轉(zhuǎn)換的速度 ,就像我們在查找常用書籍時(shí),可以直接通過快速索引找到,而不必再去翻閱整個(gè)目錄。只有當(dāng) TLB 中沒有命中時(shí),才會去內(nèi)存中查詢頁表,這就好比快速索引中沒有找到書籍,才需要去翻閱總目錄。
2.2 分段機(jī)制(簡單提及與分頁的配合)
Linux 的分段機(jī)制將程序的地址空間劃分為多個(gè)邏輯段,如代碼段、數(shù)據(jù)段、堆棧段等 ,每個(gè)段都有自己的基地址、限長和訪問權(quán)限等屬性 ,就像是將一個(gè)大型工廠劃分為不同的車間,每個(gè)車間都有自己的工作區(qū)域、工作范圍和進(jìn)入權(quán)限。分段機(jī)制最初是為了滿足程序模塊化設(shè)計(jì)和內(nèi)存保護(hù)的需求而誕生的,它在早期的操作系統(tǒng)內(nèi)存管理中發(fā)揮了重要作用 ,就像工廠的車間劃分,有利于不同工作的開展和管理。
在現(xiàn)代 Linux 系統(tǒng)中,雖然分頁機(jī)制在內(nèi)存管理中占據(jù)主導(dǎo)地位,但分段機(jī)制仍然存在,并與分頁機(jī)制協(xié)同工作 ,共同保障系統(tǒng)的穩(wěn)定運(yùn)行。分段機(jī)制主要用于權(quán)限審核 ,它通過段描述符中的權(quán)限字段來判斷當(dāng)前程序是否有權(quán)限訪問特定的內(nèi)存段 ,就像車間的門禁系統(tǒng),根據(jù)權(quán)限來控制人員的進(jìn)出。例如,用戶態(tài)程序只能訪問用戶空間的內(nèi)存段,而內(nèi)核態(tài)程序則可以訪問內(nèi)核空間的內(nèi)存段,當(dāng)用戶態(tài)程序試圖訪問內(nèi)核空間的內(nèi)存時(shí),分段機(jī)制會進(jìn)行權(quán)限檢查,并阻止這種非法訪問 ,從而保護(hù)了系統(tǒng)內(nèi)核的安全,避免了用戶程序的錯(cuò)誤操作或惡意行為對內(nèi)核造成破壞,就像沒有權(quán)限的人員試圖進(jìn)入限制區(qū)域,會被門禁系統(tǒng)阻攔。
在實(shí)際的內(nèi)存訪問過程中,邏輯地址首先會經(jīng)過分段機(jī)制的轉(zhuǎn)換,生成線性地址 ,然后線性地址再經(jīng)過分頁機(jī)制的轉(zhuǎn)換,最終得到物理地址 ,這就好比貨物在運(yùn)輸過程中,首先要經(jīng)過區(qū)域劃分的檢查,然后再根據(jù)具體的位置信息找到存放的倉庫。不過,在 Linux 系統(tǒng)中,為了簡化內(nèi)存管理和提高性能,大部分情況下,分段機(jī)制的作用被弱化,所有段的基地址都被設(shè)置為 0 ,使得線性地址和邏輯地址在數(shù)值上相等 ,就像是區(qū)域劃分的范圍變得非常簡單,幾乎可以忽略不計(jì),這樣就可以讓分頁機(jī)制更加高效地發(fā)揮作用,就像簡化了運(yùn)輸過程中的一個(gè)環(huán)節(jié),提高了運(yùn)輸效率。
2.3 內(nèi)存映射(mmap)與 brk
在 Linux 系統(tǒng)中,內(nèi)存映射(mmap)和 brk 是兩種重要的內(nèi)存分配方式,它們各自有著獨(dú)特的原理、特點(diǎn)和適用場景。全面解析請考這篇文章《解鎖Linux內(nèi)存映射:讓你的程序飛起來》。
mmap 是一種強(qiáng)大的內(nèi)存分配機(jī)制,它通過系統(tǒng)調(diào)用在進(jìn)程的虛擬地址空間中創(chuàng)建一個(gè)新的映射區(qū)域 ,可以將文件、設(shè)備或者匿名內(nèi)存映射到該區(qū)域 ,使得進(jìn)程可以像訪問普通內(nèi)存一樣對映射的內(nèi)容進(jìn)行讀寫操作 ,就像是在自己的倉庫中開辟了一個(gè)特殊區(qū)域,這個(gè)區(qū)域與外部的文件倉庫或者其他存儲設(shè)備建立了直接的聯(lián)系,我們可以直接在自己的倉庫中對外部倉庫的貨物進(jìn)行操作。當(dāng)我們使用 mmap 將一個(gè)文件映射到內(nèi)存中時(shí),對該文件的讀寫操作就可以通過普通的內(nèi)存訪問指令來完成,而無需進(jìn)行傳統(tǒng)的文件 I/O 操作,大大提高了數(shù)據(jù)訪問的效率 ,就像我們不需要再通過繁瑣的手續(xù)去外部倉庫取貨,而是直接在自己倉庫的特殊區(qū)域就能拿到貨物并進(jìn)行處理。
mmap 支持多種映射模式,如共享映射(MAP_SHARED)和私有映射(MAP_PRIVATE) 。在共享映射模式下,多個(gè)進(jìn)程可以共享同一個(gè)映射區(qū)域,對映射區(qū)域的修改會直接反映到被映射的文件或其他進(jìn)程中 ,就像多個(gè)用戶共同使用一個(gè)公共倉庫,任何一個(gè)用戶對倉庫貨物的修改,其他用戶都能看到;而在私有映射模式下,每個(gè)進(jìn)程對映射區(qū)域的修改都是私有的,不會影響到其他進(jìn)程和被映射的文件 ,就像每個(gè)用戶都有自己獨(dú)立的小倉庫,雖然倉庫與公共倉庫有聯(lián)系,但自己對小倉庫的修改不會影響到公共倉庫和其他用戶的小倉庫。這種靈活性使得 mmap 在實(shí)現(xiàn)文件映射、共享內(nèi)存、進(jìn)程間通信等場景中得到了廣泛的應(yīng)用 ,比如在多個(gè)進(jìn)程需要共享數(shù)據(jù)的場景下,就可以使用共享映射模式來實(shí)現(xiàn)高效的數(shù)據(jù)共享和通信。
brk 則是一種更為傳統(tǒng)的內(nèi)存分配方式,它通過調(diào)整進(jìn)程數(shù)據(jù)段(data segment)的結(jié)束地址(_edata)來擴(kuò)展或收縮堆(heap)空間 ,就像是在自己的倉庫中,通過移動(dòng)倉庫的邊界來擴(kuò)大或縮小存儲貨物的空間。當(dāng)進(jìn)程需要更多的堆內(nèi)存時(shí),調(diào)用 brk 函數(shù)將_edata 指針向上移動(dòng),從而擴(kuò)大堆區(qū)的大小 ;當(dāng)不再需要這些內(nèi)存時(shí),將_edata 指針向下移動(dòng),縮小堆區(qū)大小,釋放內(nèi)存 ,這個(gè)過程就像我們根據(jù)貨物的多少,調(diào)整倉庫的大小。brk 分配的內(nèi)存是連續(xù)的 ,這在某些對內(nèi)存連續(xù)性要求較高的應(yīng)用場景中非常重要,因?yàn)檫B續(xù)內(nèi)存有利于提高緩存命中率 ,就像把相似的貨物放在相鄰的位置,方便快速取用。
不過,brk 也存在一些局限性 。由于它只能分配連續(xù)的內(nèi)存塊,隨著頻繁的內(nèi)存分配和釋放,容易導(dǎo)致內(nèi)存碎片化問題 ,即大量的小塊內(nèi)存碎片散布在內(nèi)存空間中,使得后續(xù)無法滿足大塊內(nèi)存的需求 ,就像倉庫中貨物擺放雜亂,出現(xiàn)了很多小的空閑區(qū)域,無法存放大型貨物。而且 brk 是 Linux 特有的系統(tǒng)調(diào)用,并不保證在所有操作系統(tǒng)上都有相同的接口和行為 ,因此在跨平臺應(yīng)用開發(fā)時(shí)需要謹(jǐn)慎使用 ,就像不同的倉庫管理規(guī)則不同,在多個(gè)倉庫之間搬運(yùn)貨物時(shí)需要注意規(guī)則的差異。
mmap 和 brk 在內(nèi)存分配和管理方面各有優(yōu)劣 。mmap 靈活性高,適用于大文件 I/O、共享內(nèi)存、進(jìn)程間通信等場景 ,能夠充分利用現(xiàn)代操作系統(tǒng)的特性來優(yōu)化性能 ;而 brk 則簡單高效,適合頻繁分配和釋放小內(nèi)存的場景 ,如字符串、結(jié)構(gòu)體等小型數(shù)據(jù)結(jié)構(gòu)的內(nèi)存管理 ,在追求極致效率的簡單內(nèi)部對象存儲場景中具有一定的優(yōu)勢 。在實(shí)際的編程實(shí)踐中,開發(fā)者需要根據(jù)具體的應(yīng)用需求和場景來選擇合適的內(nèi)存分配方式 ,以實(shí)現(xiàn)最佳的性能和資源利用效率 ,就像在不同的工作場景中,選擇合適的工具才能更好地完成任務(wù)。
2.4 slab 分配器
在 Linux 內(nèi)核的內(nèi)存管理體系中,slab 分配器就像是一位專門負(fù)責(zé)小內(nèi)存管理的 “專家”,它主要用于高效管理小塊內(nèi)存的分配,在頻繁分配和釋放小對象的場景中發(fā)揮著至關(guān)重要的作用 。全面解析請參考這篇文章《解密Slab分配器:內(nèi)存管理的高效武器》。
slab 分配器的工作原理基于對象復(fù)用和緩存機(jī)制 。它會為每種類型的對象創(chuàng)建一個(gè)緩存(Cache) ,每個(gè)緩存存儲相同大小的對象集合 ,就像是為不同類型的小零件分別準(zhǔn)備了專門的收納盒,每個(gè)收納盒里只存放相同規(guī)格的小零件。當(dāng)有內(nèi)存分配請求時(shí),slab 分配器會首先從對應(yīng)的緩存中查找空閑對象 ,如果找到,就直接將其返回給請求者 ,這就好比在收納盒中尋找空閑的小零件,找到了就直接拿出來使用,避免了頻繁地向系統(tǒng)申請新的內(nèi)存 ,大大提高了內(nèi)存分配的效率 。只有當(dāng)緩存中沒有空閑對象時(shí),slab 分配器才會向底層的伙伴系統(tǒng)(Buddy System)申請新的內(nèi)存塊 ,并將其劃分為多個(gè)小對象放入緩存中 ,就像收納盒空了,再去大倉庫領(lǐng)取一批小零件,放入收納盒中備用。
當(dāng)對象不再使用時(shí),它并不會被立即釋放回系統(tǒng) ,而是被標(biāo)記為空閑,放回對應(yīng)的緩存中 ,等待下一次分配 ,這就像小零件用完后,不扔掉,而是放回收納盒中,下次還能繼續(xù)使用 ,這種對象復(fù)用的方式有效地減少了內(nèi)存碎片的產(chǎn)生 ,提高了內(nèi)存的利用率 。slab 分配器還支持緩存著色(cache coloring)等優(yōu)化技術(shù) ,通過將對象分布在不同的緩存行中 ,避免緩存行沖突 ,從而提高 CPU 緩存的命中率 ,進(jìn)一步提升了系統(tǒng)性能 ,就像合理地?cái)[放小零件,讓它們更容易被快速找到和使用。
slab 分配器在 Linux 內(nèi)核的眾多子系統(tǒng)中得到了廣泛應(yīng)用 。在網(wǎng)絡(luò)緩沖區(qū)管理中,網(wǎng)絡(luò)數(shù)據(jù)包的收發(fā)需要頻繁地分配和釋放小內(nèi)存塊來存儲數(shù)據(jù)包 ,slab 分配器能夠快速響應(yīng)這些請求 ,確保網(wǎng)絡(luò)通信的高效進(jìn)行 ;在文件系統(tǒng)緩存中,文件的讀寫操作也涉及到大量小內(nèi)存對象的管理 ,slab 分配器可以有效地管理這些緩存對象 ,提高文件系統(tǒng)的性能 ;在進(jìn)程控制塊(PCB)的管理中,每個(gè)進(jìn)程都有一個(gè)對應(yīng)的 PCB ,用于存儲進(jìn)程的相關(guān)信息 ,slab 分配器可以為 PCB 的分配和釋放提供高效的支持 ,保障進(jìn)程的正常運(yùn)行 ??梢哉f,slab 分配器是 Linux 內(nèi)核實(shí)現(xiàn)高效內(nèi)存管理的關(guān)鍵組件之一 ,為整個(gè)系統(tǒng)的穩(wěn)定運(yùn)行和高性能表現(xiàn)提供了有力的支持。
三、動(dòng)態(tài)內(nèi)存分配與釋放過程
3.1 malloc 函數(shù)剖析
在 C 語言的標(biāo)準(zhǔn)庫中,malloc函數(shù)是實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存分配的關(guān)鍵工具,它的定義為void *malloc(size_t size);,接受一個(gè)參數(shù)size,用于指定需要分配的內(nèi)存字節(jié)數(shù),返回值是一個(gè)指向所分配內(nèi)存起始地址的指針,類型為void*,這意味著它可以被轉(zhuǎn)換為任何類型的指針,以適應(yīng)不同的數(shù)據(jù)存儲需求。
在 Linux 系統(tǒng)中,malloc函數(shù)并非直接與操作系統(tǒng)的底層內(nèi)存管理機(jī)制打交道,而是通過glibc(GNU C Library)來實(shí)現(xiàn)的 。當(dāng)程序調(diào)用malloc函數(shù)時(shí),它首先會在glibc維護(hù)的內(nèi)存池中查找是否有足夠的空閑內(nèi)存來滿足請求 。如果內(nèi)存池中有足夠的空閑內(nèi)存,malloc函數(shù)會直接從內(nèi)存池中分配內(nèi)存,并返回相應(yīng)的指針 。這樣做可以減少系統(tǒng)調(diào)用的開銷,提高內(nèi)存分配的效率,因?yàn)橄到y(tǒng)調(diào)用涉及到用戶態(tài)和內(nèi)核態(tài)的切換,這種切換會帶來一定的性能損耗。
若內(nèi)存池中的空閑內(nèi)存不足以滿足請求,malloc函數(shù)就需要借助系統(tǒng)調(diào)用與操作系統(tǒng)進(jìn)行交互 。在 Linux 系統(tǒng)中,主要涉及到兩個(gè)系統(tǒng)調(diào)用:brk和mmap 。brk系統(tǒng)調(diào)用通過移動(dòng)程序數(shù)據(jù)段的結(jié)束地址(即 “堆頂” 指針)來增加堆的大小,從而分配新的內(nèi)存 ;而mmap系統(tǒng)調(diào)用則是通過在文件映射區(qū)域分配一塊內(nèi)存來滿足請求 。通常情況下,當(dāng)請求的內(nèi)存大小小于一定閾值(在大多數(shù)系統(tǒng)中,這個(gè)閾值通常為 128KB )時(shí),malloc函數(shù)會優(yōu)先使用brk系統(tǒng)調(diào)用來分配內(nèi)存;當(dāng)請求的內(nèi)存大小大于這個(gè)閾值時(shí),則會使用mmap系統(tǒng)調(diào)用。
malloc函數(shù)背后還依靠空閑鏈表來管理內(nèi)存 。當(dāng)調(diào)用malloc函數(shù)時(shí),它會沿著空閑鏈表查找滿足用戶請求大小的內(nèi)存塊 。若鏈表上存在多個(gè)不同大小的空閑內(nèi)存塊,它會依次遍歷這些塊來找到合適的那一個(gè) 。找到合適的內(nèi)存塊后,如果這個(gè)內(nèi)存塊比用戶請求的大小要大,就會按需將其分割成兩部分,一部分的大小剛好與用戶請求的大小相等,這部分分配給用戶使用,而剩下的那部分則放回空閑鏈表中,等待后續(xù)其他的內(nèi)存分配請求再進(jìn)行分配 。例如,空閑鏈表中有一個(gè) 50 字節(jié)的空閑塊,而用戶請求分配 20 字節(jié)的內(nèi)存,這時(shí)malloc就會把這個(gè) 50 字節(jié)的塊分成 20 字節(jié)(分配給用戶)和 30 字節(jié)(放回空閑鏈表)兩塊。
隨著程序不斷地分配和釋放內(nèi)存,空閑鏈表有可能會被切成很多的小內(nèi)存片段 。要是后續(xù)用戶申請一個(gè)較大的內(nèi)存片段時(shí),空閑鏈表上可能暫時(shí)沒有可以滿足要求的片段了,這時(shí)malloc函數(shù)可能就需要進(jìn)行一些整理操作,比如對這些小的空閑塊嘗試合并等,以便能滿足較大內(nèi)存請求的情況。
3.2 free 函數(shù)解析
free函數(shù)是 C 語言標(biāo)準(zhǔn)庫中用于釋放動(dòng)態(tài)分配內(nèi)存的重要函數(shù),其原型定義在<stdlib.h>頭文件中:void free(void *ptr); ,參數(shù)ptr指向已經(jīng)動(dòng)態(tài)分配內(nèi)存塊的指針,這個(gè)內(nèi)存塊應(yīng)該由malloc、calloc或realloc等函數(shù)返回 ,free函數(shù)沒有返回值,它的作用是將ptr指向的內(nèi)存塊歸還給系統(tǒng)進(jìn)行管理。
當(dāng)調(diào)用free函數(shù)釋放內(nèi)存時(shí),相應(yīng)被釋放的內(nèi)存塊并不會立即返還給操作系統(tǒng) ,而是返還給 C/C++ 的運(yùn)行時(shí)庫 。運(yùn)行時(shí)庫會將其標(biāo)記為空閑,并重新連接到空閑鏈表上 ,等待下一次內(nèi)存分配請求時(shí)再次被使用 。這就好比在一個(gè)圖書館借書系統(tǒng)中,讀者歸還的書籍并不會立即被送回總倉庫,而是先放在圖書館的歸還書架上,等待其他讀者借閱 。只有當(dāng)運(yùn)行時(shí)庫認(rèn)為有必要時(shí),比如一大塊位于堆頂部的連續(xù)內(nèi)存被釋放,且空閑內(nèi)存達(dá)到一定的閾值,運(yùn)行時(shí)庫才會考慮將其真正地歸還給操作系統(tǒng) ,以釋放物理內(nèi)存資源,供其他進(jìn)程使用。
在實(shí)際使用free函數(shù)時(shí),需要特別注意避免一些常見的錯(cuò)誤 。比如雙重釋放(Double Free),即對同一塊內(nèi)存進(jìn)行兩次free調(diào)用,這會導(dǎo)致程序崩潰或內(nèi)存破壞 。為了防止雙重釋放,應(yīng)在釋放內(nèi)存后將指針設(shè)為NULL,這樣可以避免第二次調(diào)用free時(shí)出錯(cuò) 。釋放空指針是安全的,free函數(shù)會忽略NULL指針,因此無需擔(dān)心釋放空指針帶來的問題 。釋放內(nèi)存后,原指針指向的內(nèi)存不再有效,繼續(xù)訪問該內(nèi)存區(qū)域會導(dǎo)致未定義行為,即訪問已釋放的內(nèi)存(懸空指針),為避免懸空指針,應(yīng)該在free后立即將指針設(shè)為NULL。
四、內(nèi)存回收機(jī)制
4.1 內(nèi)存回收的觸發(fā)條件
在 Linux 系統(tǒng)運(yùn)行過程中,內(nèi)存回收機(jī)制是保障系統(tǒng)穩(wěn)定運(yùn)行的重要防線,它會在多種情況下被觸發(fā),以應(yīng)對內(nèi)存資源緊張的局面。
當(dāng)系統(tǒng)內(nèi)存使用率持續(xù)攀升,達(dá)到一定閾值,可用內(nèi)存量急劇減少,接近或低于系統(tǒng)設(shè)定的警戒水位時(shí),內(nèi)存回收機(jī)制就會被緊急啟動(dòng) 。這就好比一個(gè)水庫,當(dāng)水位持續(xù)下降,接近警戒線時(shí),就需要采取措施來補(bǔ)充水量,以確保水庫的正常運(yùn)行。例如,當(dāng)系統(tǒng)中同時(shí)運(yùn)行多個(gè)大型程序,如大型數(shù)據(jù)庫管理系統(tǒng)、圖形渲染軟件等,它們對內(nèi)存的需求量巨大,會迅速消耗系統(tǒng)的內(nèi)存資源,導(dǎo)致可用內(nèi)存不足,此時(shí)內(nèi)存回收機(jī)制就會介入,回收一些暫時(shí)不用的內(nèi)存,以滿足這些程序的運(yùn)行需求。
當(dāng)有新的內(nèi)存分配請求出現(xiàn),而當(dāng)前系統(tǒng)的剩余內(nèi)存無法滿足這一請求時(shí),為了滿足新的內(nèi)存需求,系統(tǒng)會立即觸發(fā)內(nèi)存回收操作 。這就像是在一個(gè)資源有限的倉庫中,當(dāng)有新的貨物需要存放,而倉庫空間不足時(shí),就需要清理出一些空間來存放新貨物。比如,在運(yùn)行一個(gè)需要大量內(nèi)存的機(jī)器學(xué)習(xí)訓(xùn)練任務(wù)時(shí),程序在運(yùn)行過程中可能會動(dòng)態(tài)申請更多的內(nèi)存來存儲中間數(shù)據(jù),如果此時(shí)系統(tǒng)內(nèi)存不足,就會觸發(fā)內(nèi)存回收,從其他進(jìn)程或緩存中回收內(nèi)存,以保證機(jī)器學(xué)習(xí)任務(wù)的順利進(jìn)行。
除了內(nèi)存緊張的情況外,當(dāng)進(jìn)程結(jié)束運(yùn)行時(shí),其占用的內(nèi)存資源也會被回收 。這就好比一個(gè)租客退租后,其所占用的房屋空間會被房東收回,以便重新分配給其他租客。進(jìn)程結(jié)束時(shí),操作系統(tǒng)會自動(dòng)回收該進(jìn)程所占用的用戶空間內(nèi)存,包括堆內(nèi)存、棧內(nèi)存等 ,同時(shí)也會清理該進(jìn)程在內(nèi)核空間中占用的相關(guān)資源,如文件描述符、進(jìn)程控制塊等 ,將這些內(nèi)存和資源重新歸還給系統(tǒng)內(nèi)存池,供其他進(jìn)程使用。
4.2 匿名頁與文件頁的回收策略
在 Linux 系統(tǒng)的內(nèi)存回收體系中,匿名頁和文件頁由于其特性的差異,采用了不同的回收策略。
匿名頁,是指那些沒有與任何文件建立映射關(guān)系的內(nèi)存頁,通常用于進(jìn)程的堆內(nèi)存和棧內(nèi)存等動(dòng)態(tài)分配的內(nèi)存區(qū)域 。當(dāng)內(nèi)存緊張需要回收匿名頁時(shí),如果系統(tǒng)配置了交換空間(swap space),那么系統(tǒng)會優(yōu)先將那些近期最少使用(Least Recently Used,LRU)的匿名頁換出到交換空間中存儲 。這就好比在一個(gè)倉庫中,將那些長時(shí)間沒有被使用的貨物暫時(shí)搬到倉庫外的臨時(shí)存儲區(qū)存放。
當(dāng)進(jìn)程再次訪問這些被換出的匿名頁時(shí),系統(tǒng)會將其從交換空間重新?lián)Q入到物理內(nèi)存中 ,就像從臨時(shí)存儲區(qū)把貨物重新搬回倉庫。這個(gè)過程雖然解決了內(nèi)存緊張的問題,但由于磁盤的讀寫速度遠(yuǎn)遠(yuǎn)低于物理內(nèi)存,頻繁的換入換出操作會導(dǎo)致系統(tǒng)性能大幅下降,就像頻繁地在倉庫和臨時(shí)存儲區(qū)之間搬運(yùn)貨物,會耗費(fèi)大量的時(shí)間和精力。
文件頁,是指那些與文件系統(tǒng)中的文件建立了映射關(guān)系的內(nèi)存頁 。對于文件頁的回收,系統(tǒng)會首先判斷其是否為臟頁(dirty page) 。臟頁是指內(nèi)存中的數(shù)據(jù)與磁盤上的文件數(shù)據(jù)不一致的頁面,即內(nèi)存中的數(shù)據(jù)被修改后還沒有同步到磁盤上 。如果文件頁是干凈頁(clean page),即內(nèi)存中的數(shù)據(jù)與磁盤上的文件數(shù)據(jù)一致,那么系統(tǒng)可以直接將其釋放,因?yàn)楫?dāng)需要再次訪問這些數(shù)據(jù)時(shí),可以直接從磁盤中讀取 ,這就好比在一個(gè)圖書館中,如果一本書的內(nèi)容在書架上和圖書館的數(shù)據(jù)庫中是一致的,那么當(dāng)書架空間緊張時(shí),可以直接把這本書從書架上拿走,需要時(shí)再從數(shù)據(jù)庫中調(diào)取。
而對于臟頁,系統(tǒng)需要先將其數(shù)據(jù)寫回到磁盤上,使內(nèi)存中的數(shù)據(jù)與磁盤上的數(shù)據(jù)保持一致,然后才能將其釋放 ,就像在圖書館中,如果一本書的內(nèi)容被修改了,需要先把修改后的內(nèi)容保存到數(shù)據(jù)庫中,然后才能把這本書從書架上拿走 。這個(gè)過程涉及到磁盤 I/O 操作,雖然也會對系統(tǒng)性能產(chǎn)生一定影響,但相較于匿名頁的換入換出,其影響相對較小。
4.3 swap 空間的作用與原理
swap 空間,也被稱為交換空間,在 Linux 系統(tǒng)的內(nèi)存管理中扮演著至關(guān)重要的角色,它就像是系統(tǒng)內(nèi)存的 “后備倉庫” ,主要用于在物理內(nèi)存不足時(shí),暫時(shí)存儲那些被換出的內(nèi)存頁數(shù)據(jù),以緩解內(nèi)存緊張的局面 ,保障系統(tǒng)的穩(wěn)定運(yùn)行。
swap 空間的工作原理基于數(shù)據(jù)交換機(jī)制 。當(dāng)系統(tǒng)的物理內(nèi)存資源緊張,無法滿足所有進(jìn)程的內(nèi)存需求時(shí),操作系統(tǒng)會將一些暫時(shí)不使用或近期最少使用的內(nèi)存頁數(shù)據(jù)從物理內(nèi)存中換出(swap out) ,寫入到 swap 空間中存儲 ,就像把倉庫中暫時(shí)不用的貨物搬到臨時(shí)存儲區(qū)存放。這樣,物理內(nèi)存中就騰出了空間,可以用于滿足其他更急需內(nèi)存的進(jìn)程的需求 。當(dāng)被換出的內(nèi)存頁數(shù)據(jù)再次被進(jìn)程訪問時(shí),操作系統(tǒng)會將其從 swap 空間中換入(swap in) ,重新加載回物理內(nèi)存中 ,就像從臨時(shí)存儲區(qū)把貨物重新搬回倉庫使用。
在這個(gè)數(shù)據(jù)交換過程中,內(nèi)核通過維護(hù)一張內(nèi)存映射表來記錄每個(gè)內(nèi)存頁的狀態(tài)和位置信息 。當(dāng)內(nèi)存頁被換出到 swap 空間時(shí),內(nèi)核會在內(nèi)存映射表中更新該內(nèi)存頁的映射關(guān)系,將其指向 swap 空間中的相應(yīng)位置 ;當(dāng)內(nèi)存頁需要被換入時(shí),內(nèi)核根據(jù)內(nèi)存映射表中的信息,準(zhǔn)確地從 swap 空間中找到對應(yīng)的內(nèi)存頁數(shù)據(jù),并將其重新映射回物理內(nèi)存 ,確保進(jìn)程能夠正確地訪問到所需的數(shù)據(jù)。
swap 空間的大小需要根據(jù)系統(tǒng)的實(shí)際需求進(jìn)行合理配置 。如果 swap 空間設(shè)置過小,當(dāng)物理內(nèi)存嚴(yán)重不足時(shí),可能無法提供足夠的空間來存儲被換出的內(nèi)存頁數(shù)據(jù),導(dǎo)致系統(tǒng)內(nèi)存耗盡,出現(xiàn)內(nèi)存溢出(Out Of Memory,OOM)錯(cuò)誤 ,進(jìn)而引發(fā)系統(tǒng)崩潰 ;而如果 swap 空間設(shè)置過大,又會浪費(fèi)磁盤空間資源,因?yàn)榇疟P空間也是有限的 ,而且過多的 swap 空間使用可能會導(dǎo)致系統(tǒng)性能下降,因?yàn)轭l繁的磁盤 I/O 操作會增加系統(tǒng)的開銷。
一般來說,對于桌面系統(tǒng),swap 空間的大小可以設(shè)置為物理內(nèi)存的 1 - 2 倍 ;對于服務(wù)器系統(tǒng),需要根據(jù)服務(wù)器的具體應(yīng)用場景和內(nèi)存使用情況進(jìn)行更細(xì)致的評估和調(diào)整 ,例如對于內(nèi)存需求較大的數(shù)據(jù)庫服務(wù)器,可能需要設(shè)置更大的 swap 空間 ,以應(yīng)對內(nèi)存突發(fā)緊張的情況。
五、Linux 動(dòng)態(tài)內(nèi)存管理機(jī)制的特點(diǎn)與優(yōu)勢
5.1 靈活性與高效性
Linux 動(dòng)態(tài)內(nèi)存管理機(jī)制的靈活性和高效性體現(xiàn)在多個(gè)關(guān)鍵方面,為系統(tǒng)的穩(wěn)定運(yùn)行和高性能表現(xiàn)提供了有力支撐。
從動(dòng)態(tài)分配與回收策略來看,Linux 能夠在程序運(yùn)行時(shí),根據(jù)實(shí)際需求動(dòng)態(tài)地分配和回收內(nèi)存 。當(dāng)程序需要新的內(nèi)存空間時(shí),內(nèi)存管理機(jī)制可以迅速響應(yīng),從內(nèi)存池中分配合適大小的內(nèi)存塊給程序使用 ;當(dāng)程序不再需要某些內(nèi)存時(shí),又能及時(shí)將其回收,重新納入內(nèi)存池,供其他程序或本程序后續(xù)使用 。這種根據(jù)需求實(shí)時(shí)調(diào)整內(nèi)存分配的方式,避免了靜態(tài)內(nèi)存分配中可能出現(xiàn)的內(nèi)存浪費(fèi)或不足問題 。例如,在一個(gè)實(shí)時(shí)數(shù)據(jù)處理程序中,隨著數(shù)據(jù)量的動(dòng)態(tài)變化,程序?qū)?nèi)存的需求也會不斷改變 。Linux 動(dòng)態(tài)內(nèi)存管理機(jī)制可以在數(shù)據(jù)量增大時(shí),及時(shí)為程序分配更多的內(nèi)存來存儲和處理數(shù)據(jù) ;當(dāng)數(shù)據(jù)處理完成,數(shù)據(jù)量減少時(shí),又能回收多余的內(nèi)存,使得這些內(nèi)存可以被其他更需要的程序使用 ,大大提高了內(nèi)存的使用效率,確保系統(tǒng)資源得到充分利用。
在內(nèi)存分配粒度上,Linux 支持多種不同大小的內(nèi)存分配 。無論是極小的內(nèi)存塊,用于存儲簡單的變量或小型結(jié)構(gòu)體 ,還是極大的內(nèi)存塊,滿足大型數(shù)據(jù)庫、圖形渲染等對內(nèi)存需求巨大的應(yīng)用場景 ,Linux 都能靈活應(yīng)對 。通過分頁機(jī)制、伙伴系統(tǒng)以及 slab 分配器等多種內(nèi)存管理組件的協(xié)同工作 ,Linux 可以精確地分配和管理不同粒度的內(nèi)存 。比如,slab 分配器專門用于管理小內(nèi)存對象的分配,能夠高效地為頻繁分配和釋放小對象的場景提供支持 ;而伙伴系統(tǒng)則主要負(fù)責(zé)較大內(nèi)存塊的分配和回收,通過合理的內(nèi)存塊劃分和合并策略,減少內(nèi)存碎片的產(chǎn)生,提高內(nèi)存分配的效率 。這種粗細(xì)結(jié)合的內(nèi)存分配粒度,使得 Linux 能夠適應(yīng)各種不同類型應(yīng)用程序的內(nèi)存需求 ,為多樣化的計(jì)算任務(wù)提供了良好的支持。
5.2 內(nèi)存碎片處理策略
在內(nèi)存管理過程中,內(nèi)存碎片是一個(gè)不可避免的問題,它會嚴(yán)重影響內(nèi)存的分配效率和系統(tǒng)性能 。Linux 系統(tǒng)采用了一系列行之有效的策略來應(yīng)對內(nèi)存碎片問題,最大限度地減少其對內(nèi)存分配的負(fù)面影響。
伙伴系統(tǒng)(Buddy System)是 Linux 解決內(nèi)存碎片問題的重要手段之一 ?;锇橄到y(tǒng)將物理內(nèi)存劃分為不同大小的塊,這些塊的大小按照 2 的冪次方進(jìn)行組織 ,如 4KB、8KB、16KB 等 。當(dāng)有內(nèi)存分配請求時(shí),伙伴系統(tǒng)會首先尋找大小最接近且能滿足請求的內(nèi)存塊 。如果找不到合適大小的內(nèi)存塊,它會將更大的內(nèi)存塊分裂成兩個(gè)大小相等的 “伙伴” 塊 ,直到找到滿足需求的內(nèi)存塊 。例如,當(dāng)一個(gè)程序請求分配 8KB 的內(nèi)存時(shí),伙伴系統(tǒng)會先檢查是否有 8KB 大小的空閑內(nèi)存塊 。如果沒有,它會查看 16KB 的內(nèi)存塊是否空閑。
若有空閑的 16KB 內(nèi)存塊,就將其分裂成兩個(gè) 8KB 的內(nèi)存塊,一個(gè)分配給程序,另一個(gè)作為空閑塊保留在系統(tǒng)中 。當(dāng)內(nèi)存塊被釋放時(shí),伙伴系統(tǒng)會檢查其相鄰的內(nèi)存塊是否也是空閑的 。如果相鄰的內(nèi)存塊是空閑的,且大小相同,就將它們合并成一個(gè)更大的內(nèi)存塊 。這種分配和合并機(jī)制有效地減少了外部碎片的產(chǎn)生 ,因?yàn)樗軌蚴箍臻e內(nèi)存盡量集中,便于后續(xù)的內(nèi)存分配。
slab 分配器在減少內(nèi)存碎片方面也發(fā)揮著重要作用 。如前所述,slab 分配器主要用于管理內(nèi)核對象的小內(nèi)存分配 。它通過為每種類型的對象創(chuàng)建專門的緩存(Cache) ,預(yù)先分配固定大小的內(nèi)存塊來存儲這些對象 。當(dāng)有對象分配請求時(shí),直接從相應(yīng)的緩存中獲取空閑對象 ;當(dāng)對象被釋放時(shí),將其重新放回緩存,而不是立即返回給伙伴系統(tǒng) 。這種方式避免了頻繁地向伙伴系統(tǒng)申請和釋放小內(nèi)存塊,減少了內(nèi)存碎片的產(chǎn)生。
以進(jìn)程描述符(task_struct)為例,它是內(nèi)核中頻繁使用的小對象 。slab 分配器會為進(jìn)程描述符創(chuàng)建一個(gè)緩存,當(dāng)有新的進(jìn)程創(chuàng)建時(shí),直接從該緩存中分配一個(gè)進(jìn)程描述符對象 ;當(dāng)進(jìn)程結(jié)束時(shí),將該進(jìn)程描述符對象放回緩存 。由于緩存中的對象大小固定且一致,不會產(chǎn)生內(nèi)部碎片 ,同時(shí)也減少了對伙伴系統(tǒng)的頻繁訪問,降低了外部碎片產(chǎn)生的可能性。
此外,Linux 還引入了頁遷移(Page Migration)和內(nèi)存碎片整理(Memory Compaction)技術(shù) 。頁遷移技術(shù)允許內(nèi)核將已分配的物理頁從一個(gè)位置移動(dòng)到另一個(gè)位置 ,通過這種方式,可以將分散的內(nèi)存頁重新整理到一起,合并出連續(xù)的空閑區(qū)域 ,從而減少內(nèi)存碎片 。這就好比在一個(gè)書架上,將零散放置的書籍重新整理排列,使空出的空間能夠集中起來,便于放置新的書籍 。內(nèi)存碎片整理則是內(nèi)核定期或按需執(zhí)行的一項(xiàng)操作,它會將已使用的頁移動(dòng)到一起,騰出連續(xù)的大塊空間 ,主要用于解決外部碎片問題 ,確保系統(tǒng)在需要分配大塊內(nèi)存時(shí)能夠成功分配 。這些技術(shù)的結(jié)合使用,使得 Linux 在面對內(nèi)存碎片問題時(shí)具有更強(qiáng)的應(yīng)對能力 ,有效提高了內(nèi)存的利用率和系統(tǒng)的穩(wěn)定性。























