程序员内功修炼系列:10 张图解谈 Linux 物理内存和虚拟内存
來(lái)源 |?后端技術(shù)學(xué)堂
責(zé)編 | Carol
封圖 | CSDN 付費(fèi)下載于視覺(jué)中國(guó)
我們都知道,程序可沒(méi)這么好騙,任你內(nèi)存管理把虛擬地址空間玩出花來(lái),到最后還是要給程序?qū)崒?shí)在在的物理內(nèi)存,不然程序就要罷工了。
所以物理內(nèi)存這么重要的資源一定要好好管理起來(lái)使用(物理內(nèi)存,就是你實(shí)實(shí)在在的內(nèi)存條),那么內(nèi)核是如何管理物理內(nèi)存的呢?
物理內(nèi)存管理
在Linux系統(tǒng)中通過(guò)分段和分頁(yè)機(jī)制,把物理內(nèi)存劃分 4K 大小的內(nèi)存頁(yè) Page(也稱作頁(yè)框Page Frame),物理內(nèi)存的分配和回收都是基于內(nèi)存頁(yè)進(jìn)行,把物理內(nèi)存分頁(yè)管理的好處大大的。
假如系統(tǒng)請(qǐng)求小塊內(nèi)存,可以預(yù)先分配一頁(yè)給它,避免了反復(fù)的申請(qǐng)和釋放小塊內(nèi)存帶來(lái)頻繁的系統(tǒng)開銷。
假如系統(tǒng)需要大塊內(nèi)存,則可以用多頁(yè)內(nèi)存拼湊,而不必要求大塊連續(xù)內(nèi)存。你看不管內(nèi)存大小都能收放自如,分頁(yè)機(jī)制多么完美的解決方案!
But,理想很豐滿,現(xiàn)實(shí)很骨感。如果就直接這樣把內(nèi)存分頁(yè)使用,不再加額外的管理還是存在一些問(wèn)題,下面我們來(lái)看下,系統(tǒng)在多次分配和釋放物理頁(yè)的時(shí)候會(huì)遇到哪些問(wèn)題。
1、物理頁(yè)管理面臨問(wèn)題
物理內(nèi)存頁(yè)分配會(huì)出現(xiàn)外部碎片和內(nèi)部碎片問(wèn)題,所謂的「內(nèi)部」和「外部」是針對(duì)「頁(yè)框內(nèi)外」而言,一個(gè)頁(yè)框內(nèi)的內(nèi)存碎片是內(nèi)部碎片,多個(gè)頁(yè)框間的碎片是外部碎片。
2、外部碎片
當(dāng)需要分配大塊內(nèi)存的時(shí)候,要用好幾頁(yè)組合起來(lái)才夠,而系統(tǒng)分配物理內(nèi)存頁(yè)的時(shí)候會(huì)盡量分配連續(xù)的內(nèi)存頁(yè)面,頻繁的分配與回收物理頁(yè)導(dǎo)致大量的小塊內(nèi)存夾雜在已分配頁(yè)面中間,形成外部碎片,舉個(gè)例子:
外部碎片
3、內(nèi)部碎片
物理內(nèi)存是按頁(yè)來(lái)分配的,這樣當(dāng)實(shí)際只需要很小內(nèi)存的時(shí)候,也會(huì)分配至少是 4K 大小的頁(yè)面,而內(nèi)核中有很多需要以字節(jié)為單位分配內(nèi)存的場(chǎng)景,這樣本來(lái)只想要幾個(gè)字節(jié)而已卻不得不分配一頁(yè)內(nèi)存,除去用掉的字節(jié)剩下的就形成了內(nèi)部碎片。
內(nèi)部碎片
4、頁(yè)面管理算法
方法總比困難多,因?yàn)榇嬖谏厦娴倪@些問(wèn)題,聰明的程序員靈機(jī)一動(dòng),引入了頁(yè)面管理算法來(lái)解決上述的碎片問(wèn)題。
5、Buddy(伙伴)分配算法
Linux 內(nèi)核引入了伙伴系統(tǒng)算法(Buddy system),什么意思呢?就是把相同大小的頁(yè)框塊用鏈表串起來(lái),頁(yè)框塊就像手拉手的好伙伴,也是這個(gè)算法名字的由來(lái)。
具體的,所有的空閑頁(yè)框分組為11個(gè)塊鏈表,每個(gè)塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個(gè)連續(xù)頁(yè)框的頁(yè)框塊。最大可以申請(qǐng)1024個(gè)連續(xù)頁(yè)框,對(duì)應(yīng)4MB大小的連續(xù)內(nèi)存。
伙伴系統(tǒng)
因?yàn)槿魏握麛?shù)都可以由 2^n 的和組成,所以總能找到合適大小的內(nèi)存塊分配出去,減少了外部碎片產(chǎn)生 。
6、分配實(shí)例
比如:我需要申請(qǐng)4個(gè)頁(yè)框,但是長(zhǎng)度為4個(gè)連續(xù)頁(yè)框塊鏈表沒(méi)有空閑的頁(yè)框塊,伙伴系統(tǒng)會(huì)從連續(xù)8個(gè)頁(yè)框塊的鏈表獲取一個(gè),并將其拆分為兩個(gè)連續(xù)4個(gè)頁(yè)框塊,取其中一個(gè),另外一個(gè)放入連續(xù)4個(gè)頁(yè)框塊的空閑鏈表中。釋放的時(shí)候會(huì)檢查,釋放的這幾個(gè)頁(yè)框前后的頁(yè)框是否空閑,能否組成下一級(jí)長(zhǎng)度的塊。
7、命令查看
[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 br8、slab分配器
看到這里你可能會(huì)想,有了伙伴系統(tǒng)這下總可以管理好物理內(nèi)存了吧?不,還不夠,否則就沒(méi)有slab分配器什么事了。
9、那什么是slab分配器呢?
一般來(lái)說(shuō),內(nèi)核對(duì)象的生命周期是這樣的:分配內(nèi)存-初始化-釋放內(nèi)存,內(nèi)核中有大量的小對(duì)象,比如文件描述結(jié)構(gòu)對(duì)象、任務(wù)描述結(jié)構(gòu)對(duì)象,如果按照伙伴系統(tǒng)按頁(yè)分配和釋放內(nèi)存,對(duì)小對(duì)象頻繁的執(zhí)行「分配內(nèi)存-初始化-釋放內(nèi)存」會(huì)非常消耗性能。
伙伴系統(tǒng)分配出去的內(nèi)存還是以頁(yè)框?yàn)閱挝?#xff0c;而對(duì)于內(nèi)核的很多場(chǎng)景都是分配小片內(nèi)存,遠(yuǎn)用不到一頁(yè)內(nèi)存大小的空間。slab分配器,「通過(guò)將內(nèi)存按使用對(duì)象不同再劃分成不同大小的空間」,應(yīng)用于內(nèi)核對(duì)象的緩存。
伙伴系統(tǒng)和slab不是二選一的關(guān)系,slab 內(nèi)存分配器是對(duì)伙伴分配算法的補(bǔ)充。
10、大白話說(shuō)原理
對(duì)于每個(gè)內(nèi)核中的相同類型的對(duì)象,如:task_struct、file_struct 等需要重復(fù)使用的小型內(nèi)核數(shù)據(jù)對(duì)象,都會(huì)有個(gè) slab 緩存池,緩存住大量常用的「已經(jīng)初始化」的對(duì)象,每當(dāng)要申請(qǐng)這種類型的對(duì)象時(shí),就從緩存池的slab 列表中分配一個(gè)出去;而當(dāng)要釋放時(shí),將其重新保存在該列表中,而不是直接返回給伙伴系統(tǒng),從而避免內(nèi)部碎片,同時(shí)也大大提高了內(nèi)存分配性能。
11、主要優(yōu)點(diǎn)
slab 內(nèi)存管理基于內(nèi)核小對(duì)象,不用每次都分配一頁(yè)內(nèi)存,充分利用內(nèi)存空間,避免內(nèi)部碎片。
slab 對(duì)內(nèi)核中頻繁創(chuàng)建和釋放的小對(duì)象做緩存,重復(fù)利用一些相同的對(duì)象,減少內(nèi)存分配次數(shù)。
12、數(shù)據(jù)結(jié)構(gòu)
slab分配器
kmem_cache 是一個(gè)cache_chain 的鏈表組成節(jié)點(diǎn),代表的是一個(gè)內(nèi)核中的相同類型的「對(duì)象高速緩存」,每個(gè)kmem_cache 通常是一段連續(xù)的內(nèi)存塊,包含了三種類型的 slabs 鏈表:
slabs_full?(完全分配的?slab?鏈表)
slabs_partial?(部分分配的slab?鏈表)
slabs_empty?( 沒(méi)有被分配對(duì)象的slab?鏈表)
kmem_cache 中有個(gè)重要的結(jié)構(gòu)體 kmem_list3 包含了以上三個(gè)數(shù)據(jù)結(jié)構(gòu)的聲明。
kmem_list3 內(nèi)核源碼
slab 是slab 分配器的最小單位,在實(shí)現(xiàn)上一個(gè) slab 由一個(gè)或多個(gè)連續(xù)的物理頁(yè)組成(通常只有一頁(yè))。單個(gè)slab可以在 slab 鏈表之間移動(dòng),例如如果一個(gè)「半滿slabs_partial鏈表」被分配了對(duì)象后變滿了,就要從 slabs_partial 中刪除,同時(shí)插入到「全滿slabs_full鏈表」中去。內(nèi)核slab對(duì)象的分配過(guò)程是這樣的:
如果slabs_partial鏈表還有未分配的空間,分配對(duì)象,若分配之后變滿,移動(dòng)?slab?到slabs_full?鏈表
如果slabs_partial鏈表沒(méi)有未分配的空間,進(jìn)入下一步
如果slabs_empty?鏈表還有未分配的空間,分配對(duì)象,同時(shí)移動(dòng)slab進(jìn)入slabs_partial鏈表
如果slabs_empty為空,請(qǐng)求伙伴系統(tǒng)分頁(yè),創(chuàng)建一個(gè)新的空閑slab, 按步驟 3 分配對(duì)象
slab分配圖解
13、命令查看
上面說(shuō)的都是理論,比較抽象,動(dòng)動(dòng)手來(lái)康康系統(tǒng)中的 slab 吧!你可以通過(guò) cat /proc/slabinfo 命令,實(shí)際查看系統(tǒng)中slab 信息。
slabinfo查詢
slabtop ?實(shí)時(shí)顯示內(nèi)核 slab 內(nèi)存緩存信息。
slabtop查詢
14、slab高速緩存的分類
slab高速緩存分為兩大類,「通用高速緩存」和「專用高速緩存」。
15、通用高速緩存
slab分配器中用 kmem_cache 來(lái)描述高速緩存的結(jié)構(gòu),它本身也需要 slab 分配器對(duì)其進(jìn)行高速緩存。cache_cache 保存著對(duì)「高速緩存描述符的高速緩存」,是一種通用高速緩存,保存在cache_chain 鏈表中的第一個(gè)元素。
另外,slab 分配器所提供的小塊連續(xù)內(nèi)存的分配,也是通用高速緩存實(shí)現(xiàn)的。通用高速緩存所提供的對(duì)象具有幾何分布的大小,范圍為32到131072字節(jié)。內(nèi)核中提供了 kmalloc()和 kfree()?兩個(gè)接口分別進(jìn)行內(nèi)存的申請(qǐng)和釋放。
16、專用高速緩存
內(nèi)核為專用高速緩存的申請(qǐng)和釋放提供了一套完整的接口,根據(jù)所傳入的參數(shù)為指定的對(duì)象分配slab緩存。
17、專用高速緩存的申請(qǐng)和釋放
kmem_cache_create() 用于對(duì)一個(gè)指定的對(duì)象創(chuàng)建高速緩存。它從 cache_cache 普通高速緩存中為新的專有緩存分配一個(gè)高速緩存描述符,并把這個(gè)描述符插入到高速緩存描述符形成的 cache_chain 鏈表中。kmem_cache_destory() 用于撤消和從 cache_chain 鏈表上刪除高速緩存。
18、slab的申請(qǐng)和釋放
slab 數(shù)據(jù)結(jié)構(gòu)在內(nèi)核中的定義,如下:
20、slab結(jié)構(gòu)體內(nèi)核代碼
kmem_cache_alloc() 在其參數(shù)所指定的高速緩存中分配一個(gè)slab,對(duì)應(yīng)的 kmem_cache_free() 在其參數(shù)所指定的高速緩存中釋放一個(gè)slab。
虛擬內(nèi)存分配
前面討論的都是對(duì)物理內(nèi)存的管理,Linux 通過(guò)虛擬內(nèi)存管理,欺騙了用戶程序假裝每個(gè)程序都有 4G 的虛擬內(nèi)存尋址空間。
所以我們來(lái)研究下虛擬內(nèi)存的分配,這里包括用戶空間虛擬內(nèi)存和內(nèi)核空間虛擬內(nèi)存。
注意,分配的虛擬內(nèi)存還沒(méi)有映射到物理內(nèi)存,只有當(dāng)訪問(wèn)申請(qǐng)的虛擬內(nèi)存時(shí),才會(huì)發(fā)生缺頁(yè)異常,再通過(guò)上面介紹的伙伴系統(tǒng)和 slab 分配器申請(qǐng)物理內(nèi)存。
1、用戶空間內(nèi)存分配
malloc
malloc 用于申請(qǐng)用戶空間的虛擬內(nèi)存,當(dāng)申請(qǐng)小于 128KB 小內(nèi)存的時(shí),malloc使用 ?sbrk或brk 分配內(nèi)存;當(dāng)申請(qǐng)大于 128KB 的內(nèi)存時(shí),使用 mmap 函數(shù)申請(qǐng)內(nèi)存;
存在問(wèn)題
由于 brk/sbrk/mmap 屬于系統(tǒng)調(diào)用,如果每次申請(qǐng)內(nèi)存都要產(chǎn)生系統(tǒng)調(diào)用開銷,cpu在用戶態(tài)和內(nèi)核態(tài)之間頻繁切換,非常影響性能。
而且,堆是從低地址往高地址增長(zhǎng),如果低地址的內(nèi)存沒(méi)有被釋放,高地址的內(nèi)存就不能被回收,容易產(chǎn)生內(nèi)存碎片。
解決
因此,malloc采用的是內(nèi)存池的實(shí)現(xiàn)方式,先申請(qǐng)一大塊內(nèi)存,然后將內(nèi)存分成不同大小的內(nèi)存塊,然后用戶申請(qǐng)內(nèi)存時(shí),直接從內(nèi)存池中選擇一塊相近的內(nèi)存塊分配出去。
2、內(nèi)核空間內(nèi)存分配
在講內(nèi)核空間內(nèi)存分配之前,先來(lái)回顧一下內(nèi)核地址空間。kmalloc 和 vmalloc 分別用于分配不同映射區(qū)的虛擬內(nèi)存,看這張上次畫的圖:
內(nèi)核空間細(xì)分區(qū)域
kmalloc
kmalloc()?分配的虛擬地址范圍在內(nèi)核空間的「直接內(nèi)存映射區(qū)」。
按字節(jié)為單位虛擬內(nèi)存,一般用于分配小塊內(nèi)存,釋放內(nèi)存對(duì)應(yīng)于 kfree ,可以分配連續(xù)的物理內(nèi)存。函數(shù)原型在?<linux/kmalloc.h>?中聲明,一般情況下在驅(qū)動(dòng)程序中都是調(diào)用 kmalloc()?來(lái)給數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存 。
還記得前面說(shuō)的 slab 嗎?kmalloc 是基于slab 分配器的 ,同樣可以用cat /proc/slabinfo 命令,查看 kmalloc 相關(guān) slab ? 對(duì)象信息,下面的 kmalloc-8、kmalloc-16 等等就是基于slab分配的 kmalloc 高速緩存。
slabinfo-kmalloc
vmalloc
vmalloc 分配的虛擬地址區(qū)間,位于 vmalloc_start 與vmalloc_end 之間的「動(dòng)態(tài)內(nèi)存映射區(qū)」。
一般用分配大塊內(nèi)存,釋放內(nèi)存對(duì)應(yīng)于 vfree,分配的虛擬內(nèi)存地址連續(xù),物理地址上不一定連續(xù)。函數(shù)原型在?<linux/vmalloc.h>?中聲明。一般用在為活動(dòng)的交換區(qū)分配數(shù)據(jù)結(jié)構(gòu),為某些 I/O 驅(qū)動(dòng)程序分配緩沖區(qū),或?yàn)閮?nèi)核模塊分配空間。
下面的圖總結(jié)了上述兩種內(nèi)核空間虛擬內(nèi)存分配方式。
總結(jié)一下
作者分享的這些知識(shí)很基礎(chǔ),基礎(chǔ)到日常開發(fā)工作幾乎用不上,但我認(rèn)為每個(gè)在Linux下開發(fā)人員都應(yīng)該了解。
我知道有些面試官喜歡在面試的時(shí)候考察一下,或多或少反應(yīng)候選人基礎(chǔ)素養(yǎng),這兩篇文章的內(nèi)容也足夠應(yīng)付面試。還是那句話,Linxu 內(nèi)存管理太復(fù)雜,不是一兩篇文章能講的清楚,但至少要有宏觀意識(shí),不至于一問(wèn)三不知,如果你想深入了解原理,強(qiáng)烈建議從書中并結(jié)合內(nèi)核源碼學(xué)習(xí),每天進(jìn)步一點(diǎn)點(diǎn),我們的目標(biāo)是星辰大海。
本文創(chuàng)作過(guò)程我也畫了大量的示例圖解,可以作為知識(shí)索引,個(gè)人感覺(jué)看圖還是比看文字更清晰明了。
如果您對(duì)本文有什么想說(shuō)的,也歡迎在評(píng)論區(qū)告訴我們,一起探討學(xué)習(xí)!
推薦閱讀
一文帶你認(rèn)識(shí)keepalived,再帶你通關(guān)LVS+Keepalived!
那個(gè)分分鐘處理 10 億節(jié)點(diǎn)圖計(jì)算的 Plato,現(xiàn)在怎么樣了?
“谷歌殺手”發(fā)明者,科學(xué)天才 Wolfram
數(shù)據(jù)庫(kù)激蕩 40 年,深入解析 PostgreSQL、NewSQL 演進(jìn)歷程
超詳細(xì)!一文告訴你 SparkStreaming 如何整合 Kafka !附代碼可實(shí)踐
5分鐘!就能學(xué)會(huì)以太坊 JSON API 基礎(chǔ)知識(shí)!
真香,朕在看了!
總結(jié)
以上是生活随笔為你收集整理的程序员内功修炼系列:10 张图解谈 Linux 物理内存和虚拟内存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 果断拿下4000万美元D轮融资,Ranc
- 下一篇: 2020 AI 产业图谱启动,勾勒中国