10 張圖解再談 Linux 物理內存和虛擬內存

2021-02-07 Linux愛好者

(給Linux愛好者加星標,提升Linux技能)

轉自:LemonCoder

今天繼續來學習Linux內存管理,什麼?你更想學時間管理,我不配,抱個西瓜去微博學吧。

img

言歸正傳,上一篇文章 別再說你不懂Linux內存管理了,10張圖給你安排的明明白白! 分析了 Linux 內存管理機制,如果已經忘了的同學還可以回頭看下,並且也強烈建議先閱讀那一篇再來看這一篇。限於篇幅,上一篇沒有深入學習物理內存管理和虛擬內存分配,今天就來學習一下。

通過前面的學習我們知道,程序可沒這麼好騙,任你內存管理把虛擬地址空間玩出花來,到最後還是要給程序實實在在的物理內存,不然程序就要罷工了。

所以物理內存這麼重要的資源一定要好好管理起來使用(物理內存,就是你實實在在的內存條),那麼內核是如何管理物理內存的呢?

物理內存管理

在Linux系統中通過分段和分頁機制,把物理內存劃分 4K 大小的內存頁 Page(也稱作頁框Page Frame),物理內存的分配和回收都是基於內存頁進行,把物理內存分頁管理的好處大大的。

假如系統請求小塊內存,可以預先分配一頁給它,避免了反覆的申請和釋放小塊內存帶來頻繁的系統開銷。

假如系統需要大塊內存,則可以用多頁內存拼湊,而不必要求大塊連續內存。你看不管內存大小都能收放自如,分頁機制多麼完美的解決方案!

But,理想很豐滿,現實很骨感。如果就直接這樣把內存分頁使用,不再加額外的管理還是存在一些問題,下面我們來看下,系統在多次分配和釋放物理頁的時候會遇到哪些問題。

物理頁管理面臨問題

物理內存頁分配會出現外部碎片和內部碎片問題,所謂的「內部」和「外部」是針對「頁框內外」而言,一個頁框內的內存碎片是內部碎片,多個頁框間的碎片是外部碎片。

外部碎片

當需要分配大塊內存的時候,要用好幾頁組合起來才夠,而系統分配物理內存頁的時候會儘量分配連續的內存頁面,頻繁的分配與回收物理頁導致大量的小塊內存夾雜在已分配頁面中間,形成外部碎片,舉個例子:

外部碎片內部碎片

物理內存是按頁來分配的,這樣當實際只需要很小內存的時候,也會分配至少是 4K 大小的頁面,而內核中有很多需要以字節為單位分配內存的場景,這樣本來只想要幾個字節而已卻不得不分配一頁內存,除去用掉的字節剩下的就形成了內部碎片。

內部碎片頁面管理算法

方法總比困難多,因為存在上面的這些問題,聰明的程式設計師靈機一動,引入了頁面管理算法來解決上述的碎片問題。

Buddy(夥伴)分配算法

Linux 內核引入了夥伴系統算法(Buddy system),什麼意思呢?就是把相同大小的頁框塊用鍊表串起來,頁框塊就像手拉手的好夥伴,也是這個算法名字的由來。

具體的,所有的空閒頁框分組為11個塊鍊表,每個塊鍊表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可以申請1024個連續頁框,對應4MB大小的連續內存。

夥伴系統

因為任何正整數都可以由 2^n 的和組成,所以總能找到合適大小的內存塊分配出去,減少了外部碎片產生 。

分配實例

比如:我需要申請4個頁框,但是長度為4個連續頁框塊鍊表沒有空閒的頁框塊,夥伴系統會從連續8個頁框塊的鍊表獲取一個,並將其拆分為兩個連續4個頁框塊,取其中一個,另外一個放入連續4個頁框塊的空閒鍊表中。釋放的時候會檢查,釋放的這幾個頁框前後的頁框是否空閒,能否組成下一級長度的塊。

命令查看
[lemon]]# cat /proc/buddyinfo Node 0, zone      DMA      1      0      0      0      2      1      1      0      1      1      3 Node 0, zone    DMA32   3198   4108   4940   4773   4030   2184    891    180     67     32    330 Node 0, zone   Normal  42438  37404  16035   4386    610    121     22      3      0      0      1

slab分配器

看到這裡你可能會想,有了夥伴系統這下總可以管理好物理內存了吧?不,還不夠,否則就沒有slab分配器什麼事了。

那什麼是slab分配器呢?

一般來說,內核對象的生命周期是這樣的:分配內存-初始化-釋放內存,內核中有大量的小對象,比如文件描述結構對象、任務描述結構對象,如果按照夥伴系統按頁分配和釋放內存,對小對象頻繁的執行「分配內存-初始化-釋放內存」會非常消耗性能。

夥伴系統分配出去的內存還是以頁框為單位,而對於內核的很多場景都是分配小片內存,遠用不到一頁內存大小的空間。slab分配器,「通過將內存按使用對象不同再劃分成不同大小的空間」,應用於內核對象的緩存。

夥伴系統和slab不是二選一的關係,slab 內存分配器是對夥伴分配算法的補充。

大白話說原理

對於每個內核中的相同類型的對象,如:task_struct、file_struct 等需要重複使用的小型內核數據對象,都會有個 slab 緩存池,緩存住大量常用的「已經初始化」的對象,每當要申請這種類型的對象時,就從緩存池的slab 列表中分配一個出去;而當要釋放時,將其重新保存在該列表中,而不是直接返回給夥伴系統,從而避免內部碎片,同時也大大提高了內存分配性能。

主要優點slab 內存管理基於內核小對象,不用每次都分配一頁內存,充分利用內存空間,避免內部碎片。slab 對內核中頻繁創建和釋放的小對象做緩存,重複利用一些相同的對象,減少內存分配次數。數據結構slab分配器

kmem_cache 是一個cache_chain 的鍊表組成節點,代表的是一個內核中的相同類型的「對象高速緩存」,每個kmem_cache 通常是一段連續的內存塊,包含了三種類型的 slabs 鍊表:

slabs_full (完全分配的 slab 鍊表)slabs_partial (部分分配的slab 鍊表)slabs_empty ( 沒有被分配對象的slab 鍊表)

kmem_cache 中有個重要的結構體 kmem_list3 包含了以上三個數據結構的聲明。

kmem_list3 內核源碼

slab 是slab 分配器的最小單位,在實現上一個 slab 由一個或多個連續的物理頁組成(通常只有一頁)。單個slab可以在 slab 鍊表之間移動,例如如果一個「半滿slabs_partial鍊表」被分配了對象後變滿了,就要從 slabs_partial 中刪除,同時插入到「全滿slabs_full鍊表」中去。內核slab對象的分配過程是這樣的:

如果slabs_partial鍊表還有未分配的空間,分配對象,若分配之後變滿,移動 slab 到slabs_full 鍊表如果slabs_partial鍊表沒有未分配的空間,進入下一步如果slabs_empty 鍊表還有未分配的空間,分配對象,同時移動slab進入slabs_partial鍊表如果slabs_empty為空,請求夥伴系統分頁,創建一個新的空閒slab, 按步驟 3 分配對象slab分配圖解命令查看

上面說的都是理論,比較抽象,動動手來康康系統中的 slab 吧!你可以通過 cat /proc/slabinfo 命令,實際查看系統中slab 信息。

slabinfo查詢

slabtop  實時顯示內核 slab 內存緩存信息。

slabtop查詢slab高速緩存的分類

slab高速緩存分為兩大類,「通用高速緩存」和「專用高速緩存」。

通用高速緩存

slab分配器中用 kmem_cache 來描述高速緩存的結構,它本身也需要 slab 分配器對其進行高速緩存。cache_cache 保存著對「高速緩存描述符的高速緩存」,是一種通用高速緩存,保存在cache_chain 鍊表中的第一個元素。

另外,slab 分配器所提供的小塊連續內存的分配,也是通用高速緩存實現的。通用高速緩存所提供的對象具有幾何分布的大小,範圍為32到131072位元組。內核中提供了 kmalloc() 和 kfree() 兩個接口分別進行內存的申請和釋放。

專用高速緩存

內核為專用高速緩存的申請和釋放提供了一套完整的接口,根據所傳入的參數為指定的對象分配slab緩存。

專用高速緩存的申請和釋放

kmem_cache_create() 用於對一個指定的對象創建高速緩存。它從 cache_cache 普通高速緩存中為新的專有緩存分配一個高速緩存描述符,並把這個描述符插入到高速緩存描述符形成的 cache_chain 鍊表中。kmem_cache_destory() 用於撤消和從 cache_chain 鍊表上刪除高速緩存。

slab的申請和釋放

slab 數據結構在內核中的定義,如下:

slab結構體內核代碼

kmem_cache_alloc() 在其參數所指定的高速緩存中分配一個slab,對應的 kmem_cache_free() 在其參數所指定的高速緩存中釋放一個slab。

虛擬內存分配

前面討論的都是對物理內存的管理,Linux 通過虛擬內存管理,欺騙了用戶程序假裝每個程序都有 4G 的虛擬內存尋址空間(如果這裡不懂我說啥,建議回頭看下 別再說你不懂Linux內存管理了,10張圖給你安排的明明白白!)。

所以我們來研究下虛擬內存的分配,這裡包括用戶空間虛擬內存和內核空間虛擬內存。

注意,分配的虛擬內存還沒有映射到物理內存,只有當訪問申請的虛擬內存時,才會發生缺頁異常,再通過上面介紹的夥伴系統和 slab 分配器申請物理內存。

用戶空間內存分配malloc

malloc 用於申請用戶空間的虛擬內存,當申請小於 128KB 小內存的時,malloc使用  sbrk或brk 分配內存;當申請大於 128KB 的內存時,使用 mmap 函數申請內存;

存在問題

由於 brk/sbrk/mmap 屬於系統調用,如果每次申請內存都要產生系統調用開銷,cpu 在用戶態和內核態之間頻繁切換,非常影響性能。

而且,堆是從低地址往高地址增長,如果低地址的內存沒有被釋放,高地址的內存就不能被回收,容易產生內存碎片。

解決

因此,malloc採用的是內存池的實現方式,先申請一大塊內存,然後將內存分成不同大小的內存塊,然後用戶申請內存時,直接從內存池中選擇一塊相近的內存塊分配出去。

內核空間內存分配

在講內核空間內存分配之前,先來回顧一下內核地址空間。kmalloc 和 vmalloc 分別用於分配不同映射區的虛擬內存,看這張上次畫的圖:

內核空間細分區域kmalloc

kmalloc() 分配的虛擬地址範圍在內核空間的「直接內存映射區」。

按字節為單位虛擬內存,一般用於分配小塊內存,釋放內存對應於 kfree ,可以分配連續的物理內存。函數原型在 <linux/kmalloc.h> 中聲明,一般情況下在驅動程序中都是調用 kmalloc() 來給數據結構分配內存 。

還記得前面說的 slab 嗎?kmalloc 是基於slab 分配器的 ,同樣可以用cat /proc/slabinfo 命令,查看 kmalloc 相關 slab   對象信息,下面的 kmalloc-8、kmalloc-16 等等就是基於slab分配的 kmalloc 高速緩存。

slabinfo-kmallocvmalloc

vmalloc 分配的虛擬地址區間,位於 vmalloc_start 與vmalloc_end 之間的「動態內存映射區」。

一般用分配大塊內存,釋放內存對應於 vfree,分配的虛擬內存地址連續,物理地址上不一定連續。函數原型在 <linux/vmalloc.h> 中聲明。一般用在為活動的交換區分配數據結構,為某些 I/O 驅動程序分配緩衝區,或為內核模塊分配空間。

下面的圖總結了上述兩種內核空間虛擬內存分配方式。

總結一下

這是Linux內存管理系列文章的下篇,強烈建議閱讀過程中有不清楚的同學,先去看看我之前寫的 別再說你不懂Linux內存管理了,10張圖給你安排的明明白白!,寫到這裡Linux 內存管理專題告一段落,我分享的這些知識很基礎,基礎到日常開發工作幾乎用不上,但我認為每個在Linux下開發人員都應該了解。


看完本文有收穫?請分享給更多人

關注「Linux 愛好者」加星標,提升Linux技能

好文章,我在看❤️

相關焦點

  • 探討Linux作業系統虛擬內存和物理內存的關係
    探討Linux作業系統虛擬內存和物理內存的關係 TOMORROW 星辰 發表於 2020-12-10 16:12:20 為了高效、準確測試出該系統下,單個進程能夠申請到的最大虛存空間
  • 嵌入式Linux內存管理的一些知識點總結
    回答:系統初始化過程中,主動往物理內存填寫頁表信息。這就是我們作業系統需要做的事情,填寫和修改表的程序代碼的編寫。線性地址轉物理地址是誰來做呢?1. 回答:設置好頁表信息後,CPU每次訪問內存都通過MMU來查表並轉換出物理地址。是硬體自動操作。所謂的內存(物理內存,或物理地址,或物理空間)是指?1.
  • 別再說你不懂Linux內存管理了,30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 別再說你不懂Linux內存管理了,30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • Linux內核中的內存管理
    本文對Linux內存管理使用到的一些數據結構和函數作了簡要描述,而不深入到它們的內部。對這些數據結構和函數有了一個總體上的了解後,再針對各項分別作深入了解的時候,也許會簡單一些。Linux內存訪問限制(僅針對32位系統)默認情況下Linux的內核空間映射到4G虛擬地址的最高1G(即0xC0000000 - 0xFFFFFFF)。
  • 萬字長文,別再說你不懂Linux內存管理了(合輯),30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 別再說你不懂 Linux 內存管理了
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 系統內存/進程內存知識掃盲
    1、核心概念耳熟能詳的字眼有虛擬內存、共享內存、物理內存,這裡簡要說明,更多複雜的linux內存管理機制,有興趣的同學可以自己深入了解,說實話我也沒有很深入學習。物理內存:就是系統硬體提供的內存大小,是真正的內存,一般叫做內存條,是與CPU直接交換數據的內部存儲器,也叫主存(內存)。
  • Linux下找出吃內存的方法總結
    現在想要查詢該進程佔用的內存大小。linux命令行下有很多的工具進行查看,現總結常見的幾種方式。用戶 nobody虛擬內存 428M物理內存 110M 110*1024= 112640 「和前面計算出來的值基本一致」共享內存 93M進程使用的物理內存和總內存的百分比 1.9 %PID:進程的IDUSER:進程所有者PR:進程的優先級別,越小越優先被執行NInice:值VIRT:進程佔用的虛擬內存
  • Linux內存初始化(上)
    有了armv8架構訪問內存的理解,我們來看下linux在內存這塊的初始化就更容易理解了。創建啟動頁表:在彙編代碼階段的head.S文件中,負責創建映射關係的函數是create_page_tables。create_page_tables函數負責identity mapping和kernel image mapping。identity map:是指把idmap_text區域的物理地址映射到相等的虛擬地址上,這種映射完成後,其虛擬地址等於物理地址。idmap_text區域都是一些打開MMU相關的代碼。
  • Linux內存、Swap、Cache、Buffer
    used:已經使用的內存大小(這裡面包含cached和buffers和shared部分)。  free:空閒的內存大小。  shared:進程間共享內存(一般不會用,可以忽略)。  buffers:內存中寫完的東西緩存起來,這樣快速響應請求,後面數據再定期刷到磁碟上。
  • 每個程式設計師都該了解一點 Linux 內存管理知識
    ,創下了今年閱讀量最低記錄(不算圖片),為了挽回顏面,我決定再寫一篇,還是從案例入手,介紹 Linux 內存管理機制和一個內存溢出殺手的存在感。在內存極度緊張的時候, sshd 這種不怎麼佔內存的進程也無法倖免。覆巢之下,焉有完卵。OOM Killer 的全稱叫 Out Of Memory Killer,今天簡單給大家介紹下,它是 linux 內核的內存保護機制。當應用程式大量請求內存導致內存不足的時候,通常會觸發 OOM Killer,OOM Killer 會根據相對簡單粗暴的算法殺掉某個進程以解燃眉之急。
  • 電腦內存不足怎麼辦,虛擬內存能起到多大作用
    其實跟我們今天聊的話題很契合,物理內存和虛擬內存有什麼區別?那麼虛擬內存有什麼作用呢?下面一起看下吧。電腦內存不足怎麼辦?3、有時候硬體不兼容也會導致內存不足的現象,特別是目前雙內存卡槽,如果內存條的代數和頻率不一致都會導致系統無法讀取其中的內存。4、除了上述的原因,其實跟是否開啟虛擬內存有很大關係,那麼下面繼續深入看下虛擬內存能起到多大作用?
  • Linux 物理內存外碎片化淺析
    夥伴系統分配器的核心思路:將系統的空閒頁面分為11個塊鍊表,每個塊鍊表分別管理著1,2,4,8,16,32,64,128,256,512和1024個物理頁幀號連續的頁面。每個頁面大小為4K bytes,buddy管理的塊大小範圍從4K bytes到4M bytes,以2的倍數遞增。
  • Linux 內存尋址之分頁機制
    下面,我們就來看看更加重要和複雜的分頁機制。分頁機制在段機制之後進行,以完成線性—物理地址的轉換過程。段機制把邏輯地址轉換為線性地址,分頁機制進一步把該線性地址再轉換為物理地址。硬體中的分頁分頁機制由CR0中的PG位啟用。如PG=1,啟用分頁機制,並使用本節要描述的機制,把線性地址轉換為物理地址。
  • 如何設置電腦中的虛擬內存、增大虛擬內存及清空虛擬內存頁面文件
    大家好,我是波仔,非常高興今天又來跟大家一起分享與探討,今天我們來分享一下如何來設置我們電腦中的虛擬內存、增大虛擬內存以及清空虛擬內存頁面文件。設置虛擬內電腦中運行程序均需經由內存執行,若執行程序佔用的內存很大,會導致內存消耗殆盡,系統運行會越來越慢。這時可以設置一部分硬碟空間來充當內存使用,當內存耗盡時,電腦就會自動調用這部分硬碟空間來充當內存,以緩解內存的緊張問題,從而提高系統的運行速度。
  • 郭健:Linux內存管理系統參數配置之OOM(內存耗盡)
    按照慣例,最後一章是參考文獻,本文的參考文獻都是來自linux內核的Documentation目錄,該目錄下有大量的文檔可以參考,每一篇都值得細細品味。二、什麼是OOMOOM就是out of memory的縮寫,雖然linux kernel有很多的內存管理技巧(從cache中回收、swap out等)來滿足各種應用空間的vm內存需求,但是,當你的系統配置不合理,讓一匹小馬拉大車的時候,linux kernel會運行非常緩慢並且在某個時間點分配page frame的時候遇到內存耗盡、無法分配的狀況
  • Linux內存、Swap、Cache、Buffer詳解(三)
    其實也可以手動回收,怎麼操作,往下看:我們看到drop_caches這個文件默認值為0,對它進行一些寫操作就可以達到想要的效果,沒錯,就是這麼神奇,linux的原則是一切皆文件嘛。、Buffer詳解(一)和Linux內存、Swap、Cache、Buffer詳解(二),特別要注意這個available,目前是13263
  • Linux如何調試內存洩漏
    內存洩漏是指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存
  • 17 種查看 Linux 物理內存的方法 | Linux 中國
    大多數系統管理員在遇到性能問題時會檢查 CPU 和內存利用率。Linux 中有許多實用程序可以用於檢查物理內存。這些命令有助於我們檢查系統中存在的物理內存,還允許用戶檢查各種方面的內存利用率。我們大多數人只知道很少的命令,在本文中我們試圖包含所有可能的命令。你可能會想,為什麼我想知道所有這些命令,而不是知道一些特定的和例行的命令呢。