Linux 內存管理 | 物理內存、內存碎片、伙伴系統、SLAB分配器

文章目錄

  • 物理內存
  • 物理內存分配
    • 外部碎片
    • 內部碎片
    • 伙伴系統(buddy system)
    • slab分配器


物理內存

在Linux中,內核將物理內存劃分為三個區域。

在解釋DMA內存區域之前解釋一下什么是DMA:

DMA(直接存儲器訪問) 使用物理地址訪問內存,將數據從一個地址空間復制到另外一個地址空間,從而加快磁盤和內存之間數據的交換,不經過MMU(內存管理單元),這時CPU可以去干別的事,大大增加了效率。

  • DMA內存區域(ZONE_DMA): 包含 0M~16M 之內的內存頁框,該區域的物理頁面專門供I/O設備的DMA使用,DMA需要連續的緩沖區,為了能夠提供物理上連續的緩沖區,必須從物理地址空間專門劃分一段區域用于DMA。
  • 普通內存區域(ZONE_NORMAL): 包含 16MB~896M 以上的內存頁框,可以直接映射到內核空間中的直接映射區。
  • 高端內存區域(ZONE_HIGHMEM): 包含 896M 以上的內存頁框,不可以進行直接映射,可以通過 高端內存映射區中的永久內存映射區 以及 臨時內存映射區(固定內存映射區中的一部分) 來對這塊物理內存進行訪問。

內存分布如下圖:
在這里插入圖片描述


物理內存分配

在Linux中,通過分段和分頁的機制,將物理內存劃分為4k大小的內存頁(page),并且將作為物理內存分配與回收的基本單位。通過分頁機制我們可以靈活的對內存進行管理。

  • 如果用戶申請了小塊內存,我們可以直接分配一頁給它,就可以避免因為頻繁的申請、釋放小塊內存而發起的系統調用帶來的消耗。
  • 如果用戶申請了大塊內存,我們可以將多個頁框組合成一大塊內存后再進行分配,非常的靈活。

但是,這種直接的內存分配非常容易導致內存碎片的出現,下面就分別介紹內部碎片外部碎片這兩種內存碎片。

為了方便接下來的閱讀,這里科普一下 頁框

  • 分頁單元認為所有的RAM被分成了固定長度的 頁框 ,頁框是主存的一部分,是一個實際的存儲區域。
  • 是指一系列的線性地址和包含于其中的數據,每頁被視為一個數據塊。而存放數據塊的物理內存就是 頁框 ,也就是說一個 頁框 的長度和一個 的長度是一樣的, 可以存放在任何頁框或磁盤中。

外部碎片

當我們需要分配大塊內存時,操作系統會將連續的頁框組合起來,形成大塊內存,來將其分配給用戶。但是,頻繁的申請和釋放內存頁,就會帶來 內存外碎片 的問題,如下圖。
在這里插入圖片描述
假設我們這塊內存塊中有10個頁框,我們一開始先是分配了3個頁框給 進程A ,而后又分配了5個頁框給 進程B 。當進程A結束后,其釋放了申請的3個頁框,此時我們剩余空間就是內存塊起始位置的3個頁框,以及末尾位置的2個頁框。

假如此時我們運行了 進程C ,其需要5個頁框的內存,此時雖然這塊內存中還剩下5個頁框,但是由于我們頻繁的申請和釋放小塊空間導致內存碎片化,因此如果我們想申請5個頁框的空間,只能到其他的內存塊中申請,這塊內存的空閑頁框就被浪費了。


要想解決 外部碎片 的問題,無非就兩種方法:

  • 外碎片問題的本質就是 空閑頁框不連續 ,所以可以將 非連續的空閑頁框 映射到 連續的虛擬地址空間 ,如果 現存的空閑頁框總大小 滿足進程的需求,則允許將一個進程分散地分配到許多不相鄰的分區中,從而避免直接申請新的內存塊;
  • 記錄現存的 連續空閑頁框塊 的情況,如果有 能滿足的小塊內存需求 直接從記錄中分配 相等或大于 內存需求的 連續空閑頁框塊 ,從而避免直接申請新的內存塊。

第一種方法就是將上面舉例中的 C進程 一部分分配到前面的 3個頁框 , 另一部分分配到后面的 2個頁框 ,如此一來不用申請新的內存塊即可滿足C進程的需求,詳細內容將在分頁知識中講述。

第二種方法就是,雖然 C進程 要申請新的內存塊,但是如果接下來 A進程 又開始運行,那我們就將 B進程 所在的內存塊中 3塊連續空閑頁框塊 分配給 A進程 而不是直接申請新的 10塊連續頁框 分配給 A進程

Linux選擇了第二種方法,引入 伙伴系統算法 ,來解決 外部碎片 的問題。


內部碎片

內部碎片 是 的未被利用的空閑區域。一開始的時候也說了,由于頁是物理內存分配的基本單位,因此即使我們需求的內存很小,Linux也會至少給我們分配 4k 的內存頁,此時會造成內存浪費。

舉個例子:當一個進程需要 7K 大小的內存時,我們必須給他分配 2個頁框 以滿足需求,但是第 2 個頁框我們只使用了其中 3K 的內存,因此有 1K 的內存被浪費掉了。

在這里插入圖片描述

如上圖,倘若我們需求的只有幾個字節,那該內存頁中又有大量的空間未被使用,就造成了內存浪費的問題,而如果我們頻繁的進行小塊內存的申請,這種浪費現象就會愈發嚴重。

內碎片問題的本質就是 頁內空閑內存 無法被其他進程再次利用。而 SLAB分配器 就可以 對內部碎片進行再利用 ,從而解決內部碎片問題。


伙伴系統(buddy system)

什么是伙伴系統算法呢?其實就是 把相同大小的連續頁框塊用鏈表串起來 ,這使頁框之間看起來就像是手拉手的伙伴,這也就是其名字的由來。

伙伴系統將所有的空閑頁框分組為11塊鏈表,每個塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊,即2的0~10次方,最大可以申請 1024 個連續頁框,對應 4MB(最大連續頁框數 * 每個頁的大小 = 1024 * 4k) 大小的連續內存。每個頁框塊的第一個頁框的物理地址是該塊大小的整數倍。

在這里插入圖片描述

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

倘若我們需要分配1MB的空間,即256個頁框的塊,我們就會去查找在256個頁框的鏈表中是否存在一個空閑塊,如果沒有,則繼續往下查找更大的鏈表,如查找512個頁框的鏈表。如果存在空閑塊,則將其拆分為兩個256個頁框的塊,一個用來進行分配,另一個則放入256個頁框的鏈表中。

釋放時也同理,它會將多個連續且空閑的頁框塊進行合并為一個更大的頁框塊,放入更大的鏈表中。


slab分配器

雖然伙伴系統很好的解決了外部碎片的問題,但是它還是以頁作為內存分配和釋放的單位,而我們在實際的應用中則是以字節為單位,例如我們要申請2個字節的空間,其還是會向我們分配一頁,也就是 4096字節(4K) 的內存,因此其還是會存在內部碎片的問題。

為了解決這個問題,slab分配器就應運而生了。其以 字節 為基本單位,專門用于對 小塊內存 進行分配。slab分配器并未脫離伙伴系統,而是對伙伴系統的補充,它將伙伴系統分配的大內存進一步細化為小內存分配(對內部碎片的再利用)。

那么它的原理是什么呢?

對于內核對象,生命周期通常是這樣的: 分配內存->初始化->釋放內存 。而內核中如文件描述符、pcb等小對象又非常多,如果按照伙伴系統按頁分配和釋放內存,不僅存在大量的空間浪費,還會因為頻繁對小對象進行 分配-初始化-釋放 這些操作而導致性能的消耗。

所以為了解決這個問題,對于內核中這些需要重復使用的小型數據對象,slab通過一個緩存池來緩存這些常用的已初始化的對象

  • 當我們需要申請這些小對象時,就會直接從緩存池中的slab列表中分配一個出去。
  • 而當我們需要釋放時,我們不會將其返回給伙伴系統進行釋放,而是將其重新保存在緩存池的slab列表中。

通過這種方法,不僅避免了內部碎片的問題,還大大的提高了內存分配的性能。

PS:這里說的 緩存池 是對真正的緩存—— 硬件緩存(cache) 原理的一種模仿:

  • 硬件緩存是為了解決快速的CPU和速度較慢的內存之間速度不匹配的問題,CPU訪問cache的速度要快于內存,如果將常用的數據放到硬件緩存中,使用時CPU 直接訪問cache而不再訪問內存 ,從而提升系統速度。
  • 而這里的 緩存池 實際上使在內存中預先開辟一塊空間,使用時直接從這一塊空間中去取所需對象(訪問的是內存而不是cache),是SLAB分配器為了便于對小塊內存的管理而建立的。

下面就由大到小,來畫出底層的數據結構:

在這里插入圖片描述

slab 分配器把每一個 請求的內存 稱之為 對象 ,每種 對象 分配一個 高速緩存(kmem_cache) ,所有的 高速緩存 通過雙鏈表組織在一起,形成 高速緩存鏈表(cache_chain) ,每個 高速緩存 所占內存區被劃分為多個 slab ,這些 slab 都屬于一個 slab列表 ,每個 slab列表 是一段連續的內存塊,并包含3種類型的 slabs鏈表

  • slabs_full(完全分配的slab)
  • slabs_partial(部分分配的slab)
  • slabs_empty(空slab,或者沒有對象被分配)。

slab 是 slab分配器的 最小單位 ,在具體實現上一個 slab 由一個或者多個連續的物理頁組成(通常只有一頁)。單個 slab 可以在 slab鏈表 中進行移動,例如一個 未滿的slab節點 ,其原本在 slabs_partial 鏈表中,如果它由于分配對象而變滿,就需要從原先的 slabs_partial 中刪除,插入到完全分配的鏈表 slabs_full 中。

舉個具象的例子:

slab分配器 將進程描述符和索引節點對象放在一個 cache_chain ,該 cache_chain 下轄兩個 kmem_cache :一個 kmem_cache 用于存放進程描述符,而另一個 kmem_cache 存放索引節點對象,然后這些 kmem_cache 又被劃分為多個 slab ,每個 slab 都管轄著若干個對象(進程描述符/索引節點對象),而這些 slab 又根據狀態(已滿、半滿、全空)分布在3個 slabs鏈表 中,3個 slabs鏈表 共同構成一個 slab列表

舉個例子以說明slab的分配過程:

如果在 cache_chain 里有一個名叫 inode_cachepkmem_cache 節點,它存放了一些 inode 對象。當內核請求分配一個新的 inode 對象時,slab分配器 就開始工作了:

  1. 首先要查看 inode_cachepslabs_partial 鏈表,如果 slabs_partial 非空,就從中選中一個 slab返回一個指向已分配但未使用的inode結構的指針。 完事之后,如果這個 slab 滿了,就把它從 slabs_partial 中刪除,插入到 slabs_full 中去,結束;
  2. 如果 slabs_partial 為空,也就是沒有半滿的 slab ,就會到 slabs_empty 中尋找。如果 slabs_empty 非空,就選中一個 slab返回一個指向已分配但未使用的inode結構的指針 ,然后將這個 slabslabs_empty 中刪除,插入到 slabs_partial(或者 slab_full )中去,結束;
  3. 如果 slabs_empty 也為空,那么沒辦法,cache_chain 內存已經不足,只能新創建一個 slab 了。

內核中slab分配對象的全過程:

  1. 根據對象的類型找到 cache_chain 中對應的高速緩存 kmem_cache
  2. 如果 slabs_partial 鏈表非空,則選擇其中一個 slab ,將 slab 中一個未分配的對象分配給需求來源。如果分配之后這個 slab 已滿,則移動這個 slabslabs_full 鏈表
  3. 如果 slabs_partial 鏈表沒有未分配的空間,則去查看 slabs_empty 鏈表
  4. 如果 slabs_empty 非空,則選擇其中一個 slab ,將 slab 中一個未分配的對象分配給需求來源,同時移動 slab 進入 slabs_partial 鏈表中
  5. 如果 slabs_empty 也沒有未分配的空間,則說明此時空間不足,就會請求伙伴系統分頁,并創建新的空閑 slab 節點放入 slabs_empty 鏈表中,回到步驟3

從上面可以看出,slab分配器的本質其實就是 將內存按使用對象不同再劃分成不同大小的空間,即對內核對象的緩存操作

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443778.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443778.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443778.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux 內存管理 | 虛擬內存管理:虛擬內存空間、虛擬內存分配

文章目錄虛擬地址空間用戶空間內核空間用戶空間內存分配malloc內核空間內存分配kmallocvmalloc虛擬地址空間 在早期的計算機中,程序是直接運行在物理內存上的,而直接使用物理內存,通常都會面臨以下幾種問題: 內存缺乏訪問控制&a…

Linux | 編譯原理、gcc的命令參數、自動化構建工具 make/Makefile

文章目錄編譯原理預處理編譯匯編鏈接gcc的常用命令參數make 和 Makefile 的概念make的運行通配符自動化變量偽目標.PHONE:【命令】編譯原理 在解釋 makefile 前,首先解釋一下 .c 文件變成 .exe 文件要經過的四個步驟——預處理、編譯、匯編和鏈接(參考來…

全排列變種:限定 排列的差值范圍 及 排列中的元素個數

文章目錄題目描述思路代碼實現題目描述 詳細描述:字節跳動2019春招研發部分編程題——萬萬沒想到之抓捕孔連順 輸入描述: 第一行包含空格分隔的兩個數字 N和D(1?≤?N?≤?1000000; 1?≤?D?≤?1000000) 第二行包含N個整數(取值區間為…

Linux | 進程概念、進程狀態(僵尸進程、孤兒進程、守護進程)、進程地址空間

文章目錄進程和程序操作系統如何控制和調度程序進程控制塊–PCB子進程進程狀態僵尸進程孤兒進程守護進程(精靈進程)進程地址空間引言頁表進程和程序 程序: 一系列有序的指令集合(就是我們寫的代碼)。進程:…

Linux 進程控制 :進程創建,進程終止,進程等待,程序替換

文章目錄進程創建進程等待程序替換進程終止進程創建 fork函數: 操作系統提供的創建新進程的方法,父進程通過調用 fork函數 創建一個子進程,父子進程代碼共享,數據獨有。 當調用 fork函數 時,通過 寫時拷貝技術 來拷貝…

Linux 內存管理 | 連續分配方式 和 離散分配方式

文章目錄前言連續分配單一連續分配分區式分配固定分區分配動態分區分配可重定位分區分配離散分配分段分頁多級頁表快表(TLB)段頁式Linux前言 Linux 內存管理 | 虛擬內存管理:虛擬內存空間、虛擬內存分配 Linux 內存管理 | 物理內存、內存碎片、伙伴系統、SLAB分配器…

操作系統 | 用戶態和內核態的切換(中斷、系統調用與過程(庫函數)調用)

文章目錄中斷過程調用系統調用過程調用和系統調用的區別中斷 用戶態、內核態之間的切換是怎么實現的? 用戶態→內核態 是通過中斷實現的。并且 中斷是唯一途徑 。核心態→用戶態 的切換是通過執行一個特權指令,將程序狀態字 (PSW) 的標志位設置為 用戶態 。 中斷…

管道實現父子進程的信息傳遞(二)【標準流和其文件描述符、fwrite函數、perror函數】

文章目錄代碼實現標準流 和 標準流文件描述符代碼中用到的函數fwrite()perror()在復習進程間的通信方式時又寫了一遍,和 管道實現父子進程的信息傳遞(一)【fork函數、pipe函數、write/read操作、wait函數】 的區別不是特別大,只是…

JAVA隨機生成文件名:當前年月日時分秒+五位隨機數

代碼如下: package cn.gov.csrc.util;import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random;public class RandomUtil {/*** 生成隨機文件名:當前年月日時分秒五位隨機數* * return*/public static String getRandomFile…

命名管道實現進程的信息傳遞【mkfifo函數、open函數】

文章目錄代碼實現mkfifo函數open函數代碼實現 #include<fcntl.h> // open() #include<sys/wait.h> // wait() #include<sys/types.h> // mkfifo() #include<sys/stat.h> // mkfifo() #include<iostream> #include<unistd.h> // fork()usi…

Linux 進程 | 進程間的通信方式

文章目錄管道匿名管道 pipe命名管道 FIFO共享內存共享內存的使用流程&#xff1a;消息隊列信號量套接字在之前的博客中講過&#xff0c;虛擬空間出現的其中一個目的就是解決 進程沒有獨立性&#xff0c;可能訪問同一塊物理內存 的問題。因為這種獨立性&#xff0c;進程之間無法…

Linux網絡編程 | socket介紹、網絡字節序與主機字節序概念與兩者的轉換、TCP/UDP 連接中常用的 socket 接口

文章目錄套接字socket 地址通用 socket 地址專用 socket 地址網絡字節序與主機字節序地址轉換TCP/UDP 連接中常用的 socket 接口套接字 什么是套接字&#xff1f; 所謂 套接字 (Socket) &#xff0c;就是對網絡中 不同主機 上的應用進程之間進行雙向通信的端點的抽象。 UNIX/L…

網絡協議分析 | 傳輸層 :史上最全UDP、TCP協議詳解,一篇通~

文章目錄UDP概念格式UDP如何實現可靠傳輸基于UDP的應用層知名協議TCP概念格式保證TCP可靠性的八種機制確認應答、延時應答與捎帶應答超時重傳滑動窗口滑動窗口協議后退n協議選擇重傳協議流量控制擁塞控制發送窗口、接收窗口、擁塞窗口快速重傳和快速恢復連接管理機制三次握手連…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><舉報信息反饋><R index"1"><舉報編號>1</舉報編號><狀態>1</狀態><答復意見>填寫…

網絡協議分析 | 應用層:HTTP協議詳解、HTTP代理服務器

文章目錄概念URLHTTP協議的特點HTTP協議版本格式請求報文首行頭部空行正文響應報文首行頭部空行正文Cookie與SessionHTTP代理服務器正向代理服務器反向代理服務器透明代理服務器概念 先了解一下 因特網&#xff08;Internet&#xff09; 與 萬維網&#xff08;World Wide Web&…

MySQL命令(一)| 數據類型、常用命令一覽、庫的操作、表的操作

文章目錄數據類型數值類型字符串類型日期/時間類型常用命令一覽庫的操作顯示當前數據庫創建數據庫使用數據庫刪除數據庫表的操作創建表顯示當前庫中所有表查看表結構刪除表數據類型 mysql 的數據類型主要分為 數值類型、日期/時間類型、字符串類型 三種。 數值類型 數值類型可…

C++ 繼承 | 對象切割、菱形繼承、虛繼承、對象組合

文章目錄繼承繼承的概念繼承方式及權限using改變成員的訪問權限基類與派生類的賦值轉換回避虛函數機制派生類的默認成員函數友元與靜態成員多繼承菱形繼承虛繼承組合繼承 繼承的概念 繼承可以使得子類具有父類的屬性和方法或者重新定義、追加屬性和方法等。 當創建一個類時&…

博弈論 | 博弈論簡談、常見的博弈定律、巴什博弈

文章目錄博弈論什么是博弈論&#xff1f;博弈的前提博弈的要素博弈的分類非合作博弈——有限兩人博弈囚徒困境合作博弈——無限多人博弈囚徒困境常見的博弈定律零和博弈重復博弈智豬博弈斗雞博弈獵鹿博弈蜈蚣博弈酒吧博弈槍手博弈警匪博弈海盜分金巴什博弈博弈論 什么是博弈論…

MySQL命令(二)| 表的增刪查改、聚合函數(復合函數)、聯合查詢

文章目錄新增 (Create)全列插入指定列插入查詢 (Retrieve)全列查詢指定列查詢條件查詢關系元素運算符模糊查詢分頁查詢去重&#xff1a;DISTINCT別名&#xff1a;AS升序 or 降序更新 (Update)刪除 (Delete)分組&#xff08;GROUP BY&#xff09;聯合查詢內連接&#xff08;inne…

Spring3.1+Quertz1.8實現多個計劃任務

1.主要是配置文件&#xff1a;如下&#xff1a;(這里說明一下主要是看紅色部分的配置&#xff0c;其他的可以根據自己的實際情況修改&#xff0c;這里只是個思路。) <?xml version"1.0"?> <beans xmlns"http://www.springframework.org/schema/beans…