成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux內(nèi)核內(nèi)存管理:核心技術(shù)與優(yōu)化策略

系統(tǒng) Linux
THP(Transparent Huge Page,透明大頁)是 Linux 內(nèi)核中的一項(xiàng)內(nèi)存管理優(yōu)化技術(shù),它主要用于處理大內(nèi)存頁面的分配和管理,旨在提高內(nèi)存訪問效率,特別是對(duì)于那些需要頻繁訪問大量內(nèi)存的應(yīng)用程序 。

在 Linux 系統(tǒng)中,內(nèi)存管理堪稱內(nèi)核的核心功能之一,其運(yùn)作機(jī)制復(fù)雜且精妙。Linux 采用虛擬內(nèi)存技術(shù),為進(jìn)程構(gòu)建獨(dú)立的地址空間,借內(nèi)存管理單元(MMU)將虛擬地址精準(zhǔn)映射至物理地址,既保障進(jìn)程間內(nèi)存隔離,又防止相互干擾。物理內(nèi)存管理上,Linux 以分頁機(jī)制為基,將內(nèi)存切為固定大小頁(常見 4KB) ,由伙伴系統(tǒng)算法主導(dǎo)分配與回收。通過合并、分割內(nèi)存頁,伙伴系統(tǒng)有效減少內(nèi)存碎片,提升內(nèi)存利用率。同時(shí),slab 分配器專注于小內(nèi)存塊分配,進(jìn)一步優(yōu)化內(nèi)存使用。

當(dāng)物理內(nèi)存捉襟見肘,頁面替換算法粉墨登場。Linux 常用 “最近最少使用”(LRU)算法,依據(jù)程序訪問局部性原理,淘汰長時(shí)間未訪問頁面,保障系統(tǒng)關(guān)鍵數(shù)據(jù)駐留內(nèi)存;在優(yōu)化策略方面,調(diào)整內(nèi)核參數(shù)如vm.swappiness,可調(diào)控系統(tǒng)對(duì)交換空間的依賴程度;啟用大頁技術(shù),能減少頁表項(xiàng)數(shù)量,降低內(nèi)存管理開銷,大幅提升內(nèi)存訪問效率,尤其適用于對(duì)內(nèi)存要求嚴(yán)苛的應(yīng)用程序。

一、Linux內(nèi)存管理概述

Linux 內(nèi)存管理機(jī)制的設(shè)計(jì)遵循著一系列理念,這些理念是其高效運(yùn)行和廣泛適應(yīng)性的基石,涵蓋了效率、公平性、可擴(kuò)展性等多個(gè)關(guān)鍵方面。

在效率層面,Linux 內(nèi)存管理致力于實(shí)現(xiàn)快速的內(nèi)存分配與釋放。以伙伴系統(tǒng)(Buddy System)為例,這是 Linux 內(nèi)核用于管理物理內(nèi)存的一種重要機(jī)制。它將內(nèi)存按照 2 的冪次方大小進(jìn)行劃分,形成不同大小的內(nèi)存塊。當(dāng)一個(gè)進(jìn)程請(qǐng)求內(nèi)存時(shí),伙伴系統(tǒng)能迅速找到合適大小的內(nèi)存塊并分配給它;當(dāng)內(nèi)存被釋放時(shí),伙伴系統(tǒng)又能高效地將相鄰的空閑內(nèi)存塊合并成更大的內(nèi)存塊,從而減少內(nèi)存碎片的產(chǎn)生。這種基于 2 的冪次方的分配策略,就像一個(gè)精密的齒輪系統(tǒng),各個(gè)部分緊密配合,大大提高了內(nèi)存分配和回收的效率。

再比如,在內(nèi)存訪問的優(yōu)化上,Linux 采用了高速緩存機(jī)制。高速緩存(Cache)就像是一個(gè)靠近 CPU 的小型快速存儲(chǔ)器,它存儲(chǔ)了頻繁訪問的內(nèi)存數(shù)據(jù)和指令。當(dāng) CPU 需要訪問內(nèi)存時(shí),首先會(huì)在高速緩存中查找,如果找到所需數(shù)據(jù),就可以直接讀取,大大縮短了訪問時(shí)間。這就好比我們在閱讀一本厚厚的書籍時(shí),如果將經(jīng)常查閱的內(nèi)容記錄在一個(gè)便于翻閱的筆記本上,每次查找時(shí)就無需在整本書中逐頁尋找,從而節(jié)省了大量時(shí)間。Linux 通過合理地組織高速緩存,使得內(nèi)存訪問的命中率大幅提高,進(jìn)而提升了系統(tǒng)整體的運(yùn)行效率。

公平性也是 Linux 內(nèi)存管理設(shè)計(jì)中不可或缺的一環(huán)。在多任務(wù)環(huán)境下,每個(gè)進(jìn)程都需要獲得合理的內(nèi)存資源,以保證它們能夠正常運(yùn)行。Linux 通過內(nèi)存分配策略來確保公平性。例如,在進(jìn)程啟動(dòng)時(shí),系統(tǒng)會(huì)根據(jù)其需求為其分配一定的內(nèi)存空間,并且在運(yùn)行過程中,會(huì)根據(jù)進(jìn)程的優(yōu)先級(jí)和實(shí)際內(nèi)存使用情況,動(dòng)態(tài)地調(diào)整內(nèi)存分配。這就如同在一場比賽中,每個(gè)選手都有公平的機(jī)會(huì)獲得比賽資源,而裁判會(huì)根據(jù)選手的表現(xiàn)和需求,合理地分配比賽時(shí)間和場地等資源。

Linux 內(nèi)存管理還注重對(duì)不同類型進(jìn)程的公平對(duì)待。無論是前臺(tái)交互進(jìn)程,還是后臺(tái)服務(wù)進(jìn)程,都能在內(nèi)存分配上得到合理的保障。前臺(tái)交互進(jìn)程通常對(duì)響應(yīng)速度要求較高,因?yàn)樗鼈冎苯优c用戶進(jìn)行交互,如用戶在使用圖形界面的應(yīng)用程序時(shí),希望操作能夠得到即時(shí)反饋。Linux 會(huì)優(yōu)先為這類進(jìn)程分配足夠的內(nèi)存,以確保它們能夠快速響應(yīng)用戶的操作。而后臺(tái)服務(wù)進(jìn)程雖然對(duì)響應(yīng)速度的要求相對(duì)較低,但它們在系統(tǒng)中承擔(dān)著重要的服務(wù)任務(wù),如網(wǎng)絡(luò)服務(wù)、文件服務(wù)等。Linux 也會(huì)根據(jù)它們的實(shí)際需求,為其分配穩(wěn)定的內(nèi)存資源,保證這些服務(wù)的正常運(yùn)行。

隨著計(jì)算機(jī)技術(shù)的不斷發(fā)展,系統(tǒng)的規(guī)模和應(yīng)用場景日益復(fù)雜,可擴(kuò)展性成為 Linux 內(nèi)存管理設(shè)計(jì)必須考慮的因素。Linux 內(nèi)存管理機(jī)制具備良好的可擴(kuò)展性,能夠適應(yīng)不同硬件平臺(tái)和應(yīng)用需求的變化。在硬件平臺(tái)方面,無論是小型嵌入式設(shè)備,還是大型服務(wù)器集群,Linux 都能有效地管理內(nèi)存。對(duì)于嵌入式設(shè)備,由于其硬件資源有限,內(nèi)存管理需要更加精細(xì)和高效,以充分利用有限的內(nèi)存空間。Linux 通過精簡內(nèi)存管理算法和數(shù)據(jù)結(jié)構(gòu),減少內(nèi)存開銷,滿足嵌入式設(shè)備的需求。而對(duì)于大型服務(wù)器集群,內(nèi)存管理需要處理海量的內(nèi)存資源和高并發(fā)的內(nèi)存訪問請(qǐng)求。Linux 采用分布式內(nèi)存管理策略,將內(nèi)存管理任務(wù)分布到各個(gè)節(jié)點(diǎn)上,提高系統(tǒng)的整體性能和可擴(kuò)展性。

在面對(duì)不斷涌現(xiàn)的新應(yīng)用需求時(shí),Linux 內(nèi)存管理也能夠靈活調(diào)整。例如,隨著大數(shù)據(jù)和人工智能技術(shù)的發(fā)展,應(yīng)用程序?qū)?nèi)存的需求呈現(xiàn)出多樣化和動(dòng)態(tài)化的特點(diǎn)。一些大數(shù)據(jù)處理應(yīng)用需要處理海量的數(shù)據(jù),這就要求內(nèi)存管理能夠支持大規(guī)模內(nèi)存的分配和高效利用。Linux 通過引入新的內(nèi)存分配算法和技術(shù),如大頁內(nèi)存(Huge Page)機(jī)制,滿足這類應(yīng)用對(duì)內(nèi)存的特殊需求。大頁內(nèi)存可以提供更大的內(nèi)存頁,減少頁表項(xiàng)的數(shù)量,從而降低內(nèi)存管理的開銷,提高大數(shù)據(jù)處理的效率。

二、物理內(nèi)存與虛擬內(nèi)存

2.1物理內(nèi)存的管理

物理內(nèi)存是計(jì)算機(jī)系統(tǒng)中實(shí)際存在的內(nèi)存,它由計(jì)算機(jī)硬件直接管理,通常由 DRAM 芯片組成,是計(jì)算機(jī)系統(tǒng)中最快的存儲(chǔ)器 ,其大小通常是固定的,取決于計(jì)算機(jī)硬件的配置。在 Linux 內(nèi)核中,物理內(nèi)存被劃分為一個(gè)個(gè)固定大小的頁框(Page Frame),每個(gè)頁框通常為 4KB(也有其他大小,如 2MB、1GB 的大頁)。內(nèi)核通過頁框號(hào)(Page Frame Number,PFN)來管理這些頁框,就像給圖書館的每一本書都編上了唯一的編號(hào),方便查找和管理。

物理內(nèi)存的作用至關(guān)重要,它是程序運(yùn)行和數(shù)據(jù)存儲(chǔ)的直接場所。當(dāng)程序運(yùn)行時(shí),其代碼和數(shù)據(jù)會(huì)被加載到物理內(nèi)存中,CPU 直接從物理內(nèi)存中讀取指令和數(shù)據(jù)進(jìn)行處理。比如,當(dāng)我們啟動(dòng)一個(gè)文本編輯器時(shí),編輯器的程序代碼會(huì)被加載到物理內(nèi)存的某個(gè)區(qū)域,我們在編輯器中輸入的文本數(shù)據(jù)也會(huì)存儲(chǔ)在物理內(nèi)存中,這樣 CPU 才能快速地對(duì)這些數(shù)據(jù)進(jìn)行處理,實(shí)現(xiàn)文本的編輯、保存等操作。

2.2虛擬內(nèi)存的奧秘

虛擬內(nèi)存,簡單來說,是一種內(nèi)存管理技術(shù),它為每個(gè)進(jìn)程提供了一個(gè)獨(dú)立的、連續(xù)的地址空間,讓進(jìn)程誤以為自己擁有一塊完整且足夠大的內(nèi)存空間 ,而無需關(guān)心實(shí)際物理內(nèi)存的具體布局和大小限制。這就好比你擁有一個(gè)超大的虛擬倉庫,你可以隨意規(guī)劃貨物的擺放位置,而不用擔(dān)心倉庫空間不夠。

虛擬內(nèi)存的主要作用之一是實(shí)現(xiàn)內(nèi)存地址轉(zhuǎn)換。在 Linux 系統(tǒng)中,每個(gè)進(jìn)程都有自己的虛擬地址空間,這個(gè)空間通過頁表(Page Table)與物理內(nèi)存進(jìn)行映射。頁表就像是一本地址翻譯字典,負(fù)責(zé)將進(jìn)程使用的虛擬地址翻譯成實(shí)際的物理地址。

當(dāng)程序運(yùn)行時(shí),它所訪問的內(nèi)存地址都是虛擬地址。例如,當(dāng)程序需要讀取某個(gè)變量的值時(shí),它會(huì)給出一個(gè)虛擬地址。CPU 首先會(huì)根據(jù)這個(gè)虛擬地址中的頁號(hào)(Page Number)在頁表中查找對(duì)應(yīng)的物理頁框號(hào)(Page Frame Number)。如果頁表中存在這個(gè)映射關(guān)系(即頁表項(xiàng)有效),CPU 就可以通過物理頁框號(hào)和虛擬地址中的頁內(nèi)偏移(Offset)計(jì)算出實(shí)際的物理地址,從而訪問到物理內(nèi)存中的數(shù)據(jù)。

但如果頁表中沒有找到對(duì)應(yīng)的映射關(guān)系(即發(fā)生缺頁異常,Page Fault),系統(tǒng)會(huì)認(rèn)為這個(gè)虛擬頁還沒有被加載到物理內(nèi)存中。此時(shí),操作系統(tǒng)會(huì)介入,從磁盤的交換區(qū)(Swap Area)或者文件系統(tǒng)中找到對(duì)應(yīng)的物理頁,并將其加載到物理內(nèi)存中,同時(shí)更新頁表,建立虛擬地址與物理地址的映射關(guān)系。之后,程序就可以通過新建立的映射關(guān)系訪問到數(shù)據(jù)了。

為了更直觀地理解,我們可以把虛擬內(nèi)存想象成一個(gè)圖書館的目錄系統(tǒng)。每個(gè)進(jìn)程就像是一個(gè)讀者,擁有自己的目錄(虛擬地址空間)。當(dāng)讀者想要查找某本書(訪問數(shù)據(jù))時(shí),會(huì)先在自己的目錄中找到對(duì)應(yīng)的條目(虛擬地址),然后通過這個(gè)條目去書架(物理內(nèi)存)上找到實(shí)際的書。如果書架上沒有這本書(缺頁異常),圖書館管理員(操作系統(tǒng))就會(huì)從倉庫(磁盤)中把書取出來放到書架上,并更新目錄(頁表),以便下次讀者能更快地找到這本書。

例如:對(duì)于程序計(jì)數(shù)器位數(shù)為32位的處理器來說,他的地址發(fā)生器所能發(fā)出的地址數(shù)目為2^32=4G個(gè),于是這個(gè)處理器所能訪問的最大內(nèi)存空間就是4G。在計(jì)算機(jī)技術(shù)中,這個(gè)值就叫做處理器的尋址空間或?qū)ぶ纺芰Α?/span>

照理說,為了充分利用處理器的尋址空間,就應(yīng)按照處理器的最大尋址來為其分配系統(tǒng)的內(nèi)存。如果處理器具有32位程序計(jì)數(shù)器,那么就應(yīng)該按照下圖的方式,為其配備4G的內(nèi)存:

這樣,處理器所發(fā)出的每一個(gè)地址都會(huì)有一個(gè)真實(shí)的物理存儲(chǔ)單元與之對(duì)應(yīng);同時(shí),每一個(gè)物理存儲(chǔ)單元都有唯一的地址與之對(duì)應(yīng)。這顯然是一種最理想的情況。

但遺憾的是,實(shí)際上計(jì)算機(jī)所配置內(nèi)存的實(shí)際空間常常小于處理器的尋址范圍,這是就會(huì)因處理器的一部分尋址空間沒有對(duì)應(yīng)的物理存儲(chǔ)單元,從而導(dǎo)致處理器尋址能力的浪費(fèi)。例如:如下圖的系統(tǒng)中,具有32位尋址能力的處理器只配置了256M的內(nèi)存儲(chǔ)器,這就會(huì)造成大量的浪費(fèi):

另外,還有一些處理器因外部地址線的根數(shù)小于處理器程序計(jì)數(shù)器的位數(shù),而使地址總線的根數(shù)不滿足處理器的尋址范圍,從而處理器的其余尋址能力也就被浪費(fèi)了。例如:Intel8086處理器的程序計(jì)數(shù)器位32位,而處理器芯片的外部地址總線只有20根,所以它所能配置的最大內(nèi)存為1MB:

在實(shí)際的應(yīng)用中,如果需要運(yùn)行的應(yīng)用程序比較小,所需內(nèi)存容量小于計(jì)算機(jī)實(shí)際所配置的內(nèi)存空間,自然不會(huì)出什么問題。但是,目前很多的應(yīng)用程序都比較大,計(jì)算機(jī)實(shí)際所配置的內(nèi)存空間無法滿足。

實(shí)踐和研究都證明:一個(gè)應(yīng)用程序總是逐段被運(yùn)行的,而且在一段時(shí)間內(nèi)會(huì)穩(wěn)定運(yùn)行在某一段程序里。

這也就出現(xiàn)了一個(gè)方法:如下圖所示,把要運(yùn)行的那一段程序自輔存復(fù)制到內(nèi)存中來運(yùn)行,而其他暫時(shí)不運(yùn)行的程序段就讓它仍然留在輔存。

當(dāng)需要執(zhí)行另一端尚未在內(nèi)存的程序段(如程序段2),如下圖所示,就可以把內(nèi)存中程序段1的副本復(fù)制回輔存,在內(nèi)存騰出必要的空間后,再把輔存中的程序段2復(fù)制到內(nèi)存空間來執(zhí)行即可:

在計(jì)算機(jī)技術(shù)中,把內(nèi)存中的程序段復(fù)制回輔存的做法叫做“換出”,而把輔存中程序段映射到內(nèi)存的做法叫做“換入”。經(jīng)過不斷有目的的換入和換出,處理器就可以運(yùn)行一個(gè)大于實(shí)際物理內(nèi)存的應(yīng)用程序了。或者說,處理器似乎是擁有了一個(gè)大于實(shí)際物理內(nèi)存的內(nèi)存空間。于是,這個(gè)存儲(chǔ)空間叫做虛擬內(nèi)存空間,而把真正的內(nèi)存叫做實(shí)際物理內(nèi)存,或簡稱為物理內(nèi)存。

那么對(duì)于一臺(tái)真實(shí)的計(jì)算機(jī)來說,它的虛擬內(nèi)存空間又有多大呢?計(jì)算機(jī)虛擬內(nèi)存空間的大小是由程序計(jì)數(shù)器的尋址能力來決定的。例如:在程序計(jì)數(shù)器的位數(shù)為32的處理器中,它的虛擬內(nèi)存空間就為4GB。

可見,如果一個(gè)系統(tǒng)采用了虛擬內(nèi)存技術(shù),那么它就存在著兩個(gè)內(nèi)存空間:虛擬內(nèi)存空間和物理內(nèi)存空間。虛擬內(nèi)存空間中的地址叫做“虛擬地址”;而實(shí)際物理內(nèi)存空間中的地址叫做“實(shí)際物理地址”或“物理地址”。處理器運(yùn)算器和應(yīng)用程序設(shè)計(jì)人員看到的只是虛擬內(nèi)存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。

由于存在兩個(gè)內(nèi)存地址,因此一個(gè)應(yīng)用程序從編寫到被執(zhí)行,需要進(jìn)行兩次映射。第一次是映射到虛擬內(nèi)存空間,第二次時(shí)映射到物理內(nèi)存空間。在計(jì)算機(jī)系統(tǒng)中,第兩次映射的工作是由硬件和軟件共同來完成的。承擔(dān)這個(gè)任務(wù)的硬件部分叫做存儲(chǔ)管理單元MMU,軟件部分就是操作系統(tǒng)的內(nèi)存管理模塊了。

在映射工作中,為了記錄程序段占用物理內(nèi)存的情況,操作系統(tǒng)的內(nèi)存管理模塊需要建立一個(gè)表格,該表格以虛擬地址為索引,記錄了程序段所占用的物理內(nèi)存的物理地址。這個(gè)虛擬地址/物理地址記錄表便是存儲(chǔ)管理單元MMU把虛擬地址轉(zhuǎn)化為實(shí)際物理地址的依據(jù),記錄表與存儲(chǔ)管理單元MMU的作用如下圖所示:

綜上所述,虛擬內(nèi)存技術(shù)的實(shí)現(xiàn),是建立在應(yīng)用程序可以分成段,并且具有“在任何時(shí)候正在使用的信息總是所有存儲(chǔ)信息的一小部分”的局部特性基礎(chǔ)上的。它是通過用輔存空間模擬RAM來實(shí)現(xiàn)的一種使機(jī)器的作業(yè)地址空間大于實(shí)際內(nèi)存的技術(shù)。

從處理器運(yùn)算裝置和程序設(shè)計(jì)人員的角度來看,它面對(duì)的是一個(gè)用MMU、映射記錄表和物理內(nèi)存封裝起來的一個(gè)虛擬內(nèi)存空間,這個(gè)存儲(chǔ)空間的大小取決于處理器程序計(jì)數(shù)器的尋址空間。

可見,程序映射表是實(shí)現(xiàn)虛擬內(nèi)存的技術(shù)關(guān)鍵,它可給系統(tǒng)帶來如下特點(diǎn):

  • 系統(tǒng)中每一個(gè)程序各自都有一個(gè)大小與處理器尋址空間相等的虛擬內(nèi)存空間;
  • 在一個(gè)具體時(shí)刻,處理器只能使用其中一個(gè)程序的映射記錄表,因此它只看到多個(gè)程序虛存空間中的一個(gè),這樣就保證了各個(gè)程序的虛存空間時(shí)互不相擾、各自獨(dú)立的;
  • 使用程序映射表可方便地實(shí)現(xiàn)物理內(nèi)存的共享。

三、深度剖析核心技術(shù)

3.1分頁:虛擬內(nèi)存的基石

在 Linux 的世界里,內(nèi)存分頁機(jī)制就像是一位有條不紊的大管家,精心管理著系統(tǒng)的內(nèi)存資源。簡單來說,內(nèi)存分頁機(jī)制就是把物理內(nèi)存和虛擬內(nèi)存分割成固定大小的小塊,這些小塊被稱作 “頁” ,每個(gè)頁的大小一般為 4KB 或者 8KB。就好比你有一個(gè)巨大的倉庫(內(nèi)存),為了更好地管理里面的貨物(數(shù)據(jù)),你把倉庫劃分成了一個(gè)個(gè)大小相同的小隔間(頁)。

(1)什么是分頁機(jī)制

分頁機(jī)制是 80x86 內(nèi)存管理機(jī)制的第二部分。它在分段機(jī)制的基礎(chǔ)上完成虛擬地址到物理地址的轉(zhuǎn)換過程。分段機(jī)制把邏輯地址轉(zhuǎn)換成線性地址,而分頁機(jī)制則把線性地址轉(zhuǎn)換成物理地址。分頁機(jī)制可用于任何一種分段模型。處理器分頁機(jī)制會(huì)把線性地址空間劃分成頁面,然后這些線性地址空間頁面被映射到物理地址空間的頁面上。分頁機(jī)制的幾種頁面級(jí)保護(hù)措施,可和分段機(jī)制保護(hù)措施或用或替代分段機(jī)制的保護(hù)措施。

(2)分頁機(jī)制如何啟用

在我們進(jìn)行程序開發(fā)的時(shí)候,一般情況下,是不需要管理內(nèi)存的,也不需要操心內(nèi)存夠不夠用,其實(shí),這就是分頁機(jī)制給我們帶來的好處。它是實(shí)現(xiàn)虛擬存儲(chǔ)的關(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)椋喂芾頇C(jī)制對(duì)于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í),開啟分頁。

(3)分頁機(jī)制線性地址到物理地址轉(zhuǎn)換過程

80x86使用 4K 字節(jié)固定大小的頁面,每個(gè)頁面均是 4KB,并且對(duì)其于 4K 地址邊界處。這表示分頁機(jī)制把 2^32字節(jié)(4GB)的線性地址空間劃分成 2^20(1M = 1048576)個(gè)頁面。分頁機(jī)制通過把線性地址空間中的頁面重新定位到物理地址空間中進(jìn)行操作。由于 4K 大小的頁面作為一個(gè)單元進(jìn)行映射,并且對(duì)其于 4K 邊界,因此線性地址的低 12 位可做為頁內(nèi)偏移地量直接作為物理地址的低 12 位。分頁機(jī)制執(zhí)行的重定向功能可以看作是把線性地址的高 20 位轉(zhuǎn)換到對(duì)應(yīng)物理地址的高 20 位。

線性到物理地址轉(zhuǎn)換功能,被擴(kuò)展成允許一個(gè)線性地址被標(biāo)注為無效的,而非要讓其產(chǎn)生一個(gè)物理地址。以下兩種情況一個(gè)頁面可以被標(biāo)注為無效的:

  • 操作系統(tǒng)不支持的線性地址。
  • 對(duì)應(yīng)的虛擬內(nèi)存系統(tǒng)中的頁面在磁盤上而非在物理內(nèi)存中。

在第一中情況下,產(chǎn)生無效地址的程序必須被終止,在第二種情況下,該無效地址實(shí)際上是請(qǐng)求 操作系統(tǒng)虛擬內(nèi)存管理器 把對(duì)應(yīng)的頁面從磁盤加載到物理內(nèi)存中,以供程序訪問。因?yàn)闊o效頁面通常與虛擬存儲(chǔ)系統(tǒng)相關(guān),因此它們被稱為不存在頁面,由頁表中稱為存在的屬性來確定。

當(dāng)使用分頁時(shí),處理器會(huì)把線性地址空間劃分成固定大小的頁面(4KB),這些頁面可以映射到物理內(nèi)存中或磁盤存儲(chǔ)空間中,當(dāng)一個(gè)程序引用內(nèi)存中的邏輯地址時(shí),處理器會(huì)把該邏輯地址轉(zhuǎn)換成一個(gè)線性地址,然后使用分頁機(jī)制把該線性地址轉(zhuǎn)換成對(duì)應(yīng)的物理地址。

如果包含線性地址的頁面不在當(dāng)前物理內(nèi)存中,處理器就會(huì)產(chǎn)生一個(gè)頁錯(cuò)誤異常。頁錯(cuò)誤異常處理程序就會(huì)讓操作系統(tǒng)從磁盤中把相應(yīng)頁面加載到物理內(nèi)存中(操作過程中可能會(huì)把物理內(nèi)存中不同的頁面寫到磁盤上)。當(dāng)頁面加載到物理內(nèi)存之后,從異常處理過程的返回操作會(huì)使異常的指令被重新執(zhí)行。處理器把用于線性地址轉(zhuǎn)換成物理地址和用于產(chǎn)生頁錯(cuò)誤的信息包含在存儲(chǔ)與內(nèi)存中的頁目錄與頁表中。

(4)分頁機(jī)制與分段機(jī)制的不同

分頁與分段的最大的不同之處在于分頁使用了固定長度的頁面。段的長度通常與存放在其中的代碼或數(shù)據(jù)結(jié)構(gòu)有相同的長度。與段不同,頁面有固定的長度。如果僅使用分段地址轉(zhuǎn)換,那么存儲(chǔ)在物理內(nèi)存中的一個(gè)數(shù)據(jù)結(jié)構(gòu)將包含其所有的部分。如果使用了分頁,那么一個(gè)數(shù)據(jù)結(jié)構(gòu)就可以一部分存儲(chǔ)與物理內(nèi)存中,而另一部分保存在磁盤中。

為了減少地址轉(zhuǎn)換所要求的總線周期數(shù)量,最近訪問的頁目錄和頁表會(huì)被存放在處理器的一個(gè)叫做轉(zhuǎn)換查找緩沖區(qū)(TLB)的緩沖器件中。TLB 可以滿足大多數(shù)讀頁目錄和頁表的請(qǐng)求而無需使用總線周期。只有當(dāng) TLB 中不包含所要求的頁表項(xiàng)是才會(huì)出現(xiàn)使用額外的總線周期從內(nèi)存讀取頁表項(xiàng)。通常在一個(gè)頁表項(xiàng)很長時(shí)間沒有訪問過時(shí)才會(huì)出現(xiàn)這種情況。

3.2交換空間:內(nèi)存不足時(shí)的后盾

首先呢,提一個(gè)概念,交換空間(swap space),這個(gè)大家應(yīng)該不陌生,在重裝系統(tǒng)的時(shí)候,會(huì)讓你選擇磁盤分區(qū),就比如說一個(gè)硬盤分幾個(gè)部分去管理。其中就會(huì)分一部分磁盤空間用作交換,叫做swap space。其實(shí)就是一段臨時(shí)存儲(chǔ)空間,內(nèi)存不夠用的時(shí)候就用它了,雖然它也在磁盤中,但省去了很多的查找時(shí)間啊。當(dāng)發(fā)生進(jìn)程切換的時(shí)候,內(nèi)存與交換空間就要發(fā)生數(shù)據(jù)交換一滿足需求。所以啊,進(jìn)程的切換消耗是很大的,這也說明了為什么自旋鎖比信號(hào)量效率高的原因。

那么我們的程序里申請(qǐng)的內(nèi)存的時(shí)候,linux內(nèi)核其實(shí)只分配一個(gè)虛擬內(nèi)存( 線性地址),并沒有分配實(shí)際的物理內(nèi)存。只有當(dāng)程序真正使用這塊內(nèi)存時(shí),才會(huì)分配物理內(nèi)存。這就叫做延遲分配和請(qǐng)頁機(jī)制。釋放內(nèi)存時(shí),先釋放線性區(qū)對(duì)應(yīng)的物理內(nèi)存,然后釋放線性區(qū);"請(qǐng)頁機(jī)制"將物理內(nèi)存的分配延后了,這樣是充分利用了程序的局部性原來,節(jié)約內(nèi)存空間,提高系統(tǒng)吞吐;就是說一個(gè)函數(shù)可能只在物理內(nèi)存中呆了一會(huì),用完了就被清除出去了,雖然在虛擬地址空間還在。(不過虛擬地址空間不是事實(shí)上的存儲(chǔ),所以只能說這個(gè)函數(shù)占據(jù)了一段虛擬地址空間,當(dāng)你訪問這段地址時(shí),就會(huì)產(chǎn)生缺頁處理,從交換區(qū)把對(duì)應(yīng)的代碼搬到物理內(nèi)存上來)

Swap 交換機(jī)制是 Linux 虛擬內(nèi)存管理的另一個(gè)重要組成部分。簡單來說,Swap 是磁盤上的一塊區(qū)域,當(dāng)物理內(nèi)存不足時(shí),系統(tǒng)會(huì)將一部分暫時(shí)不用的內(nèi)存頁面(Page)交換到 Swap 空間中,騰出物理內(nèi)存給更需要的進(jìn)程使用 。當(dāng)被交換出去的頁面再次被訪問時(shí),系統(tǒng)會(huì)將其從 Swap 空間換回到物理內(nèi)存中。

Swap 交換機(jī)制的工作原理涉及到內(nèi)存回收和頁面置換算法。當(dāng)系統(tǒng)內(nèi)存緊張時(shí),內(nèi)核會(huì)啟動(dòng)內(nèi)存回收機(jī)制,掃描內(nèi)存中的頁面,選擇一些不常用或最近最少使用的頁面進(jìn)行回收。如果這些頁面是匿名頁面(沒有關(guān)聯(lián)到文件的內(nèi)存頁面,如進(jìn)程的堆和棧空間),就會(huì)被交換到 Swap 空間中;如果是文件映射頁面(關(guān)聯(lián)到文件的內(nèi)存頁面,如共享庫、文件緩存等),則會(huì)根據(jù)情況進(jìn)行處理,臟頁面(被修改過的頁面)會(huì)被寫回文件,干凈頁面(未被修改過的頁面)可以直接釋放。

Swap 交換機(jī)制對(duì)系統(tǒng)性能有著重要的影響。當(dāng) Swap 使用頻繁時(shí),說明物理內(nèi)存不足,系統(tǒng)需要頻繁地在物理內(nèi)存和 Swap 空間之間交換頁面,這會(huì)導(dǎo)致磁盤 I/O 增加,系統(tǒng)性能下降 。因?yàn)榇疟P的讀寫速度遠(yuǎn)遠(yuǎn)低于內(nèi)存,過多的 Swap 操作會(huì)使系統(tǒng)變得遲緩。因此,在實(shí)際應(yīng)用中,需要合理配置 Swap 空間的大小,并密切關(guān)注系統(tǒng)的內(nèi)存使用情況,避免 Swap 過度使用。

例如,可以通過調(diào)整/proc/sys/vm/swappiness參數(shù)來控制系統(tǒng)對(duì) Swap 的使用傾向,swappiness的值范圍是 0 - 100,表示系統(tǒng)將內(nèi)存頁面交換到 Swap 空間的傾向程度,值越大表示越傾向于使用 Swap 。一般來說,對(duì)于內(nèi)存充足的系統(tǒng),可以將swappiness設(shè)置為較低的值,如 10 或 20,以減少不必要的 Swap 操作;對(duì)于內(nèi)存緊張的系統(tǒng),可以適當(dāng)提高swappiness的值,但也要注意不要過高,以免嚴(yán)重影響性能。

3.3內(nèi)存映射:高效 I/O 的秘訣

內(nèi)存映射,英文名為 Memory - mapped I/O,從字面意思理解,就是將磁盤文件的數(shù)據(jù)映射到內(nèi)存中。在 Linux 系統(tǒng)中,這一機(jī)制允許進(jìn)程把一個(gè)文件或者設(shè)備的數(shù)據(jù)關(guān)聯(lián)到內(nèi)存地址空間,使得進(jìn)程能夠像訪問內(nèi)存一樣對(duì)文件進(jìn)行操作 。

舉個(gè)簡單的例子,假設(shè)有一個(gè)文本文件,通常我們讀取它時(shí),會(huì)使用read函數(shù),數(shù)據(jù)從磁盤先讀取到內(nèi)核緩沖區(qū),再拷貝到用戶空間。而內(nèi)存映射則直接在進(jìn)程的虛擬地址空間中為這個(gè)文件創(chuàng)建一個(gè)映射區(qū)域,進(jìn)程可以直接通過指針訪問這個(gè)映射區(qū)域,就好像文件數(shù)據(jù)已經(jīng)在內(nèi)存中一樣,大大簡化了文件操作的流程 。

內(nèi)存映射的工作原理涉及到虛擬內(nèi)存、頁表以及文件系統(tǒng)等多個(gè)方面的知識(shí)。當(dāng)進(jìn)程調(diào)用mmap函數(shù)進(jìn)行內(nèi)存映射時(shí),大致會(huì)經(jīng)歷以下幾個(gè)關(guān)鍵步驟 :

  1. 虛擬內(nèi)存區(qū)域創(chuàng)建:系統(tǒng)首先在進(jìn)程的虛擬地址空間中尋找一段滿足要求的連續(xù)空閑虛擬地址,然后為這段虛擬地址分配一個(gè)vm_area_struct結(jié)構(gòu),這個(gè)結(jié)構(gòu)用于描述虛擬內(nèi)存區(qū)域的各種屬性,如起始地址、結(jié)束地址、權(quán)限等,并將其插入到進(jìn)程的虛擬地址區(qū)域鏈表或樹中 。就好比在一片空地上,規(guī)劃出一塊特定大小和用途的區(qū)域,并做好標(biāo)記。
  2. 地址映射建立:通過待映射的文件指針,找到對(duì)應(yīng)的文件描述符,進(jìn)而鏈接到內(nèi)核 “已打開文件集” 中該文件的文件結(jié)構(gòu)體。再通過這個(gè)文件結(jié)構(gòu)體,調(diào)用內(nèi)核函數(shù)mmap,定位到文件磁盤物理地址,然后通過remap_pfn_range函數(shù)建立頁表,實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系 。這一步就像是在規(guī)劃好的區(qū)域和實(shí)際的文件存儲(chǔ)位置之間建立起一條通道,讓數(shù)據(jù)能夠順利流通。不過,此時(shí)只是建立了地址映射,真正的數(shù)據(jù)還沒有拷貝到內(nèi)存中 。
  3. 數(shù)據(jù)加載(缺頁異常處理):當(dāng)進(jìn)程首次訪問映射區(qū)域中的數(shù)據(jù)時(shí),由于數(shù)據(jù)還未在物理內(nèi)存中,會(huì)觸發(fā)缺頁異常。內(nèi)核會(huì)捕獲這個(gè)異常,然后在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁,如果沒有找到,則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中 。這個(gè)過程就像是當(dāng)你需要使用某個(gè)物品,但它不在身邊,你就需要去存放它的地方把它取回來。之后,進(jìn)程就可以對(duì)這片主存進(jìn)行正常的讀或?qū)懖僮鳎绻麑懖僮鞲淖兞藬?shù)據(jù)內(nèi)容,系統(tǒng)會(huì)在一定時(shí)間后自動(dòng)將臟頁面回寫臟頁面到對(duì)應(yīng)磁盤地址,完成寫入到文件的過程 。當(dāng)然,也可以調(diào)用msync函數(shù)來強(qiáng)制同步,讓數(shù)據(jù)立即保存到文件里 。

mmap內(nèi)存映射的實(shí)現(xiàn)過程,總的來說可以分為三個(gè)階段:

①進(jìn)程啟動(dòng)映射過程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域

  • 進(jìn)程在用戶空間調(diào)用庫函數(shù)mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • 在當(dāng)前進(jìn)程的虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)的虛擬地址
  • 為此虛擬區(qū)分配一個(gè)vm_area_struct結(jié)構(gòu),接著對(duì)這個(gè)結(jié)構(gòu)的各個(gè)域進(jìn)行了初始化
  • 將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進(jìn)程的虛擬地址區(qū)域鏈表或樹中

②調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù)),實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系

  • 為映射分配了新的虛擬地址區(qū)域后,通過待映射的文件指針,在文件描述符表中找到對(duì)應(yīng)的文件描述符,通過文件描述符,鏈接到內(nèi)核“已打開文件集”中該文件的文件結(jié)構(gòu)體(struct file),每個(gè)文件結(jié)構(gòu)體維護(hù)著和這個(gè)已打開文件相關(guān)各項(xiàng)信息。
  • 通過該文件的文件結(jié)構(gòu)體,鏈接到file_operations模塊,調(diào)用內(nèi)核函數(shù)mmap,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用戶空間庫函數(shù)。
  • 內(nèi)核mmap函數(shù)通過虛擬文件系統(tǒng)inode模塊定位到文件磁盤物理地址。
  • 通過remap_pfn_range函數(shù)建立頁表,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射關(guān)系。此時(shí),這片虛擬地址并沒有任何數(shù)據(jù)關(guān)聯(lián)到主存中。

③進(jìn)程發(fā)起對(duì)這片映射空間的訪問,引發(fā)缺頁異常,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝

注:前兩個(gè)階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射,但是并沒有將任何文件數(shù)據(jù)的拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或?qū)懖僮鲿r(shí)。

  • 進(jìn)程的讀或?qū)懖僮髟L問虛擬地址空間這一段映射地址,通過查詢頁表,發(fā)現(xiàn)這一段地址并不在物理頁面上。因?yàn)槟壳爸唤⒘说刂酚成洌嬲挠脖P數(shù)據(jù)還沒有拷貝到內(nèi)存中,因此引發(fā)缺頁異常。
  • 缺頁異常進(jìn)行一系列判斷,確定無非法操作后,內(nèi)核發(fā)起請(qǐng)求調(diào)頁過程。
  • 調(diào)頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中。
  • 之后進(jìn)程即可對(duì)這片主存進(jìn)行讀或者寫的操作,如果寫操作改變了其內(nèi)容,一定時(shí)間后系統(tǒng)會(huì)自動(dòng)回寫臟頁面到對(duì)應(yīng)磁盤地址,也即完成了寫入到文件的過程。
  • 注:修改過的臟頁面并不會(huì)立即更新回文件中,而是有一段時(shí)間的延遲,可以調(diào)用msync()來強(qiáng)制同步, 這樣所寫的內(nèi)容就能立即保存到文件里了。

四、內(nèi)存管理機(jī)制的具體實(shí)現(xiàn)

4.1slab 分配器:小內(nèi)存的管理者

slab分配器是Linux內(nèi)核中用于管理小內(nèi)存對(duì)象的一種高效內(nèi)存分配機(jī)制,主要用于管理那些頻繁分配和釋放、大小相對(duì)固定的小內(nèi)存對(duì)象,其設(shè)計(jì)目的是為了解決伙伴系統(tǒng)在分配小內(nèi)存時(shí)存在的內(nèi)存浪費(fèi)和分配效率低的問題 。

slab 分配器的工作機(jī)制基于對(duì)象復(fù)用和緩存技術(shù)。它預(yù)先分配一組相同大小的內(nèi)存塊,將這些內(nèi)存塊組成一個(gè)緩存(Cache),每個(gè)緩存專門用于存儲(chǔ)特定類型的對(duì)象。當(dāng)內(nèi)核需要分配一個(gè)小內(nèi)存對(duì)象時(shí),首先會(huì)在對(duì)應(yīng)的緩存中查找空閑的內(nèi)存塊。如果緩存中有空閑塊,就直接從緩存中分配,避免了通過伙伴系統(tǒng)進(jìn)行內(nèi)存分配的開銷。當(dāng)對(duì)象不再使用時(shí),將其釋放回緩存中,而不是立即歸還給伙伴系統(tǒng),以便后續(xù)再次使用。

例如,在 Linux 內(nèi)核中,進(jìn)程描述符(struct task_struct)是一種頻繁使用的小內(nèi)存對(duì)象。slab 分配器會(huì)為進(jìn)程描述符創(chuàng)建一個(gè)專門的緩存,在系統(tǒng)啟動(dòng)時(shí),預(yù)先分配一定數(shù)量的內(nèi)存塊,這些內(nèi)存塊的大小剛好適合存儲(chǔ)進(jìn)程描述符。當(dāng)創(chuàng)建一個(gè)新進(jìn)程時(shí),內(nèi)核直接從這個(gè)緩存中獲取一個(gè)空閑的內(nèi)存塊,用于存儲(chǔ)新進(jìn)程的描述符。當(dāng)進(jìn)程結(jié)束時(shí),對(duì)應(yīng)的內(nèi)存塊被釋放回緩存中,等待下一次使用。

①創(chuàng)建slab緩存區(qū)

該函數(shù)創(chuàng)建一個(gè)slab緩存(后備高速緩沖區(qū)),它是一個(gè)可以駐留任意數(shù)目全部同樣大小的后備緩存。其原型如下:

struct kmem_cache *kmem_cache_create(const char *name, size_t size, \
                   size_t align, unsigned long flags,\
                   void (*ctor)(void *, struct kmem_cache *, unsigned long),\
                   void (*dtor)(void *, struct kmem_cache *, unsigned ong)));

其中:

  • name:創(chuàng)建的緩存名;
  • size:可容納的緩存塊個(gè)數(shù);
  • align:后備高速緩沖區(qū)中第一個(gè)內(nèi)存塊的偏移量(一般置為0);
  • flags:控制如何進(jìn)行分配的位掩碼,包括 SLAB_NO_REAP(即使內(nèi)存緊缺也不自動(dòng)收縮這塊緩存)、SLAB_HWCACHE_ALIGN ( 每 個(gè) 數(shù) 據(jù) 對(duì) 象 被 對(duì) 齊 到 一 個(gè) 緩 存 行 )、SLAB_CACHE_DMA(要求數(shù)據(jù)對(duì)象在 DMA 內(nèi)存區(qū)分配)等);
  • ctor:是可選的內(nèi)存塊對(duì)象構(gòu)造函數(shù)(初始化函數(shù));
  • dtor:是可選的內(nèi)存對(duì)象塊析構(gòu)函數(shù)(釋放函數(shù))。

②分配slab緩存函數(shù)

一旦創(chuàng)建完后備高速緩沖區(qū)后,就可以調(diào)用kmem_cache_alloc()在緩存區(qū)分配一個(gè)內(nèi)存塊對(duì)象了,其原型如下:

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

cachep指向開始分配的后備高速緩存,flags與傳給kmalloc函數(shù)的參數(shù)相同,一般為GFP_KERNEL。

③釋放slab緩存

該函數(shù)釋放一個(gè)內(nèi)存塊對(duì)象:

void *kmem_cache_free(struct kmem_cache *cachep, void *objp);

④銷毀slab緩存

與kmem_cache_create對(duì)應(yīng)的是銷毀函數(shù),釋放一個(gè)后備高速緩存:

int kmem_cache_destroy(struct kmem_cache *cachep);

它必須等待所有已經(jīng)分配的內(nèi)存塊對(duì)象被釋放后才能釋放后備高速緩存區(qū)。

⑤slab緩存使用舉例

創(chuàng)建一個(gè)存放線程結(jié)構(gòu)體(struct thread_info)的后備高速緩存,因?yàn)樵趌inux中涉及頻繁的線程創(chuàng)建與釋放,如果使用__get_free_page()函數(shù)會(huì)造成內(nèi)存的大量浪費(fèi),效率也不高。所以在linux內(nèi)核的初始化階段就創(chuàng)建了一個(gè)名為thread_info的后備高速緩存,代碼如下:

/* 創(chuàng)建slab緩存 */
static struct kmem_cache *thread_info_cache;
thread_info_cache = kmem_cache_create("thread_info", sizeof(struct thread_info), \
                    SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);

/* 分配slab緩存 */
struct thread_info *ti;
ti = kmem_cache_alloc(thread_info_cache, GFP_KERNEL);

/* 使用slab緩存 */
...
/* 釋放slab緩存 */
kmem_cache_free(thread_info_cache, ti);
kmem_cache_destroy(thread_info_cache);

這種機(jī)制在管理小對(duì)象時(shí)具有明顯的優(yōu)勢。一方面,它減少了內(nèi)存碎片的產(chǎn)生。由于緩存中的內(nèi)存塊大小固定,且專門用于存儲(chǔ)特定類型的對(duì)象,避免了因不同大小的內(nèi)存需求而導(dǎo)致的內(nèi)存碎片化問題。另一方面,通過對(duì)象復(fù)用,減少了內(nèi)存分配和釋放的次數(shù),從而提高了內(nèi)存分配的效率。因?yàn)閺木彺嬷蟹峙浜歪尫艃?nèi)存塊的操作比通過伙伴系統(tǒng)進(jìn)行內(nèi)存分配和釋放要快得多,減少了系統(tǒng)調(diào)用和內(nèi)存管理的開銷,使得內(nèi)核在處理大量小內(nèi)存對(duì)象的分配和釋放時(shí)能夠更加高效地運(yùn)行 。

4.2buddy 系統(tǒng):大塊內(nèi)存的管家

buddy 系統(tǒng)是 Linux 內(nèi)核中用于管理物理內(nèi)存的一種重要機(jī)制,主要負(fù)責(zé)大塊連續(xù)內(nèi)存的分配和回收,其目標(biāo)是高效地分配和回收連續(xù)的物理內(nèi)存頁,減少外部碎片的產(chǎn)生,確保系統(tǒng)能夠滿足對(duì)大塊連續(xù)內(nèi)存的需求 。

Linux 便是采用這著名的伙伴系統(tǒng)算法來解決外部碎片的問題。把所有的空閑頁框分組為 11 塊鏈表,每一塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512 和 1024 個(gè)連續(xù)的頁框。對(duì)1024 個(gè)頁框的最大請(qǐng)求對(duì)應(yīng)著 4MB 大小的連續(xù)RAM 塊。每一塊的第一個(gè)頁框的物理地址是該塊大小的整數(shù)倍。例如,大小為 16個(gè)頁框的塊,其起始地址是 16 * 2^12 (2^12 = 4096,這是一個(gè)常規(guī)頁的大小)的倍數(shù)。

下面通過一個(gè)簡單的例子來說明該算法的工作原理:

假設(shè)要請(qǐng)求一個(gè)256(129~256)個(gè)頁框的塊。算法先在256個(gè)頁框的鏈表中檢查是否有一個(gè)空閑塊。如果沒有這樣的塊,算法會(huì)查找下一個(gè)更大的頁塊,也就是,在512個(gè)頁框的鏈表中找一個(gè)空閑塊。如果存在這樣的塊,內(nèi)核就把512的頁框分成兩等分,一般用作滿足需求,另一半則插入到256個(gè)頁框的鏈表中。

如果在512個(gè)頁框的塊鏈表中也沒找到空閑塊,就繼續(xù)找更大的塊——1024個(gè)頁框的塊。如果這樣的塊存在,內(nèi)核就把1024個(gè)頁框塊的256個(gè)頁框用作請(qǐng)求,然后剩余的768個(gè)頁框中拿512個(gè)插入到512個(gè)頁框的鏈表中,再把最后的256個(gè)插入到256個(gè)頁框的鏈表中。如果1024個(gè)頁框的鏈表還是空的,算法就放棄并發(fā)出錯(cuò)誤信號(hào)。

①相關(guān)數(shù)據(jù)結(jié)構(gòu)

#define MAX_ORDER 11

struct zone {
  ……
	struct free_area	free_area[MAX_ORDER];
	……
} 
struct free_area {
	struct list_head	free_list;
	unsigned long		nr_free;//該組類別塊空閑的個(gè)數(shù)
};

Zone結(jié)構(gòu)體中的free_area數(shù)組的第k個(gè)元素,它保存了所有連續(xù)大小為2^k的空閑塊,具體是通過將連續(xù)頁的第一個(gè)頁插入到free_list中實(shí)現(xiàn)的,連續(xù)頁的第一個(gè)頁的頁描述符的private字段表明改部分連續(xù)頁屬于哪一個(gè)order鏈表。

②伙伴算法系統(tǒng)初始化

Linux內(nèi)核啟動(dòng)時(shí),伙伴算法還不可用,linux是通過bootmem來管理內(nèi)存,在mem_init中會(huì)把bootmem位圖中空閑的內(nèi)存塊插入到伙伴算法系統(tǒng)的free_list中。

調(diào)用流程如下:

mem_init----->__free_all_bootmem()—>free_all_bootmem()>free_all_bootmem_core(NODE_DATA(0))–>free_all_bootmem_core(pgdat)

//利用free_page 將頁面分給伙伴管理器
free_all_bootmem
    return(free_all_bootmem_core(NODE_DATA(0)));  //#define NODE_DATA(nid)		(&contig_page_data)
        bootmem_data_t *bdata = pgdat->bdata;
        page = virt_to_page(phys_to_virt(bdata->node_boot_start));
		idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);
		map = bdata->node_bootmem_map;

		for (i = 0; i < idx; ) 
			unsigned long v = ~map[i / BITS_PER_LONG];
			//如果32個(gè)頁都是空閑的
			if (gofast && v == ~0UL)
				count += BITS_PER_LONG;
				__ClearPageReserved(page);
				order = ffs(BITS_PER_LONG) - 1;
				//設(shè)置32個(gè)頁的引用計(jì)數(shù)為1
				set_page_refs(page, order)

				//一次性釋放32個(gè)頁到空閑鏈表
				__free_pages(page, order);
									__free_pages_ok(page, order);
					list_add(&page->lru, &list);
					//page_zone定義如下return zone_table[page->flags >> NODEZONE_SHIFT];
					//接收一個(gè)頁描述符的地址作為它的參數(shù),它讀取頁描述符的flags字段的高位,并通過zone_table數(shù)組來確定相應(yīng)管理區(qū)描述符的地址,最終將頁框回收到對(duì)應(yīng)的管理區(qū)中
					free_pages_bulk(page_zone(page), 1, &list, order);
				i += BITS_PER_LONG;
				page += BITS_PER_LONG;

			//這32個(gè)頁中,只有部分是空閑的	
			else if (v)
				for (m = 1; m && i < idx; m<<=1, page++, i++)
					if (v & m)
						count++;
						__ClearPageReserved(page);
						set_page_refs(page, 0);
						//釋放單個(gè)頁
						__free_page(page);
			else
				i+=BITS_PER_LONG;
				page += BITS_PER_LONG;

		//釋放內(nèi)存分配位圖本身		
		page = virt_to_page(bdata->node_bootmem_map);
		for (i = 0; i < ((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE; i++,page++)
			__ClearPageReserved(page);
			set_page_count(page, 1);
			__free_page(page);

③伙伴算法系統(tǒng)分配空間

page = __rmqueue(zone, order);
	//從所請(qǐng)求的order開始,掃描每個(gè)可用塊鏈表進(jìn)行循環(huán)搜索。
    for (current_order = order; current_order < MAX_ORDER; ++current_order)
        area = zone->free_area + current_order;
        if (list_empty(&area->free_list))
            continue; 
        page = list_entry(area->free_list.next, struct page, lru);  
		//首先在空閑塊鏈表中刪除第一個(gè)頁框描述符。
        list_del(&page->lru);
		//清楚第一個(gè)頁框描述符的private字段,該字段表示連續(xù)頁框?qū)儆谀囊粋€(gè)大小的鏈表
        rmv_page_order(page);
        area->nr_free--;
        zone->free_pages -= 1UL << order;
		//如果是從更大的order鏈表中申請(qǐng)的,則剩下的要重新插入到鏈表中
        return expand(zone, page, order, current_order, area);
            unsigned long size = 1 << high;
            while (high > low)
                area--;
                high--;
                size >>= 1; 
				//該部分連續(xù)頁面插入到對(duì)應(yīng)的free_list中
                list_add(&page[size].lru, &area->free_list); 
                area->nr_free++;
				//設(shè)置該部分連續(xù)頁面的order
                set_page_order(&page[size], high);
                    page->private = order;
                    __SetPagePrivate(page);
                         __set_bit(PG_private, &(page)->flags)
            return page;

④伙伴算法系統(tǒng)回收空間

free_pages_bulk
	//linux內(nèi)核將空間分為三個(gè)區(qū),分別是ZONE_DMA、ZONE_NORMAL、ZONR_HIGH,zone_mem_map字段就是指向該區(qū)域第一個(gè)頁描述符
    struct page *base = zone->zone_mem_map;
    while (!list_empty(list) && count--)  
        page = list_entry(list->prev, struct page, lru);
        list_del(&page->lru);
        __free_pages_bulk
            int order_size = 1 << order;
			//該段空間的第一個(gè)頁的下標(biāo)
            page_idx = page - base; 
            zone->free_pages += order_size;
			//最多循環(huán)10 - order次。每次都將一個(gè)塊和它的伙伴進(jìn)行合并。
            while (order < MAX_ORDER-1)
				//尋找伙伴,如果page_idx=128,order=4,則buddy_idx=144
                buddy_idx = (page_idx ^ (1 << order)); 
                buddy = base + buddy_idx;
                /**
                 * 判斷伙伴塊是否是大小為order的空閑頁框的第一個(gè)頁。
                 * 首先,伙伴的第一個(gè)頁必須是空閑的(_count == -1)
                 * 同時(shí),必須屬于動(dòng)態(tài)內(nèi)存(PG_reserved被清0,PG_reserved為1表示留給內(nèi)核或者沒有使用)
                 * 最后,其private字段必須是order
                 */
                if (!page_is_buddy(buddy, order))
                    break;
                list_del(&buddy->lru);
                area = zone->free_area + order;
				//原先所在的區(qū)域空閑頁減少
                area->nr_free--;   
                rmv_page_order(buddy);
                    __ClearPagePrivate(page);
                    page->private = 0;
                page_idx &= buddy_idx;
                order++;

			/**
			 * 伙伴不能與當(dāng)前塊合并。
			 * 將塊插入適當(dāng)?shù)逆湵恚⒁詨K大小的order更新第一個(gè)頁框的private字段。
			 */
            coalesced = base + page_idx;
            set_page_order(coalesced, order);
            list_add(&coalesced->lru, &zone->free_area[order].free_list);
            zone->free_area[order].nr_free++;

4.3THP:大內(nèi)存頁面的優(yōu)化器

THP(Transparent Huge Page,透明大頁)是 Linux 內(nèi)核中的一項(xiàng)內(nèi)存管理優(yōu)化技術(shù),它主要用于處理大內(nèi)存頁面的分配和管理,旨在提高內(nèi)存訪問效率,特別是對(duì)于那些需要頻繁訪問大量內(nèi)存的應(yīng)用程序 。

THP 的原理是將多個(gè)連續(xù)的物理內(nèi)存頁合并成一個(gè)大的內(nèi)存頁,通常大小為 2MB 或 1GB,而不是使用傳統(tǒng)的 4KB 小頁。這樣做的好處是減少了頁表項(xiàng)的數(shù)量,因?yàn)橐粋€(gè)大頁只需要一個(gè)頁表項(xiàng)來映射,而多個(gè)小頁則需要多個(gè)頁表項(xiàng)。減少頁表項(xiàng)不僅節(jié)省了內(nèi)存空間,還提高了地址轉(zhuǎn)換的速度,因?yàn)樵谶M(jìn)行虛擬地址到物理地址的轉(zhuǎn)換時(shí),查找一個(gè)頁表項(xiàng)比查找多個(gè)頁表項(xiàng)要快。此外,大頁還能提高 TLB(Translation Lookaside Buffer,轉(zhuǎn)換后備緩沖器)的命中率,TLB 是一種高速緩存,用于存儲(chǔ)最近訪問的頁表項(xiàng)。由于大頁減少了頁表項(xiàng)的數(shù)量,使得 TLB 能夠緩存更多的有效映射,從而減少了 TLB 缺失的次數(shù),進(jìn)一步提高了內(nèi)存訪問的速度 。

例如,對(duì)于一個(gè)需要頻繁訪問大量內(nèi)存的數(shù)據(jù)庫應(yīng)用程序,使用 THP 可以顯著提高其性能。假設(shè)該數(shù)據(jù)庫應(yīng)用程序需要訪問 1GB 的內(nèi)存,如果使用傳統(tǒng)的 4KB 小頁,需要 262144 個(gè)頁表項(xiàng)來映射;而使用 2MB 的大頁,只需要 512 個(gè)頁表項(xiàng)。這樣,不僅減少了頁表項(xiàng)占用的內(nèi)存空間,還加快了地址轉(zhuǎn)換的速度,提高了數(shù)據(jù)庫的讀寫性能 。

THP 適用于那些具有良好內(nèi)存訪問局部性的應(yīng)用程序,即應(yīng)用程序在一段時(shí)間內(nèi)主要訪問內(nèi)存的某個(gè)局部區(qū)域。對(duì)于這類應(yīng)用程序,使用大頁可以充分發(fā)揮 THP 的優(yōu)勢,提高內(nèi)存訪問效率。然而,對(duì)于內(nèi)存訪問比較分散的應(yīng)用程序,THP 可能并不適用,因?yàn)榇箜摰姆峙淇赡軙?huì)導(dǎo)致內(nèi)存碎片問題,反而降低系統(tǒng)性能 。

// mm/khugepaged.c
static void collapse_huge_page(struct mm_struct *mm, unsigned long address)
{
    // 1. 檢查是否可合并
    if (!khugepaged_scan_pmd(mm, vma, addr))
        return;

    // 2. 分配2MB大頁
    huge_page = alloc_pages(HPAGE_PMD_ORDER, ...);

    // 3. 復(fù)制小頁內(nèi)容
    copy_page_to_huge_page(huge_page, pages, address);

    // 4. 替換頁表項(xiàng)
    set_pmd_at(mm, address, pmd, pmd_mkhuge(pfn_pmd(pfn, prot)));
}

大頁的三大陷阱:

內(nèi)存浪費(fèi):小內(nèi)存應(yīng)用被迫使用2MB大頁

# 查看大頁內(nèi)存碎片
$ grep Huge /proc/meminfo
AnonHugePages: 102400 kB
HugePages_Free: 512  # 空閑大頁

延遲波動(dòng):khugepaged合并操作引入不可預(yù)測延遲

// 合并操作可能阻塞
down_write(&mm->mmap_sem); // 獲取寫鎖

OOM風(fēng)險(xiǎn):大頁不可交換,加劇內(nèi)存壓力

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-01-02 11:06:22

2023-05-08 12:03:14

Linux內(nèi)核進(jìn)程

2011-06-15 14:45:26

上網(wǎng)行為管理技術(shù)

2024-03-08 10:50:44

Spring技術(shù)應(yīng)用程序

2022-05-07 14:31:46

物聯(lián)網(wǎng)

2025-04-25 08:05:00

IP地址CIDRVLSM

2023-02-26 23:13:24

存儲(chǔ)LinuxRAID

2025-04-03 07:00:00

2020-11-08 16:16:12

Linux硬盤RAID

2018-05-18 09:07:43

Linux內(nèi)核內(nèi)存

2017-03-08 10:06:11

Java技術(shù)點(diǎn)注解

2009-06-26 16:01:39

EJB組織開發(fā)EJB容器EJB

2023-06-14 08:49:22

PodKubernetes

2016-11-15 14:33:05

Flink大數(shù)據(jù)

2023-10-12 19:41:55

2010-09-02 10:56:37

IOS軟件備份

2009-06-15 17:54:50

Java核心技術(shù)

2022-05-09 08:21:29

Spring微服務(wù)Sentinel

2020-01-15 10:29:29

區(qū)塊鏈架構(gòu)模型

2025-01-06 08:00:09

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产精品久久久久久久久久不蜜臀 | 亚洲精品国产a久久久久久 中文字幕一区二区三区四区五区 | 成人二区 | 九九热在线视频 | 久久国产高清 | 国产精品成人久久久久a级 久久蜜桃av一区二区天堂 | 免费看大片bbbb欧美 | 国产精品黄色 | 欧美一级小视频 | 九九热在线视频 | 久久99精品久久久久蜜桃tv | 日韩精品一区二区三区在线观看 | 麻豆久久久久久久 | yiren22 亚洲综合 | 91精品国产综合久久香蕉麻豆 | 美国一级黄色片 | 久久久久91| 国产欧美一区二区三区另类精品 | 成人激情视频网 | 2023亚洲天堂 | 欧美久久精品一级黑人c片 91免费在线视频 | 亚洲第一网站 | 亚洲婷婷六月天 | 国产精品美女久久久久aⅴ国产馆 | 在线中文字幕国产 | 国产精品视频一 | 亚洲国产精品视频 | 日韩三级电影一区二区 | 久久精品免费 | 在线观看国产wwwa级羞羞视频 | 涩涩视频在线播放 | av网站免费观看 | 国产一区www | 亚洲成人精品在线观看 | 国产一区二区在线免费观看 | www312aⅴ欧美在线看 | 国产精品综合一区二区 | 日韩av大片免费看 | 日韩精品在线一区二区 | 国产www.| 日韩成人免费视频 |