本文是任督二脈之內(nèi)存管理課程第一節(jié)課的總結(jié)說明,由于水平有限,可能無法對(duì)宋老師所講完全理解通透,如有錯(cuò)誤,請(qǐng)及時(shí)指證。
本文從5個(gè)方面進(jìn)行說明:
1、 物理/虛擬/總線地址概念說明。
2、 MMU是什么,為什么,怎么做。
3、 內(nèi)存分區(qū)和內(nèi)存映射區(qū)。
4、 Buddy算法是個(gè)什么鬼。
5、 CMA的工作原理。
物理/虛擬/總線地址概念說明
所謂一花一世界,一葉一菩提,相同的事物在不同的角度可能會(huì)有不同的看法,對(duì)于物理地址,虛擬地址,總線地址的概念也是如此。
物理地址是MMU的視角所看到的內(nèi)存地址。
虛擬地址是存在MMU的前提下CPU所看到的內(nèi)存地址,當(dāng)然我們實(shí)際編程的時(shí)候操作的也是虛擬地址。
總線地址是設(shè)備的視角所看到的內(nèi)存地址。
比如一塊內(nèi)存,物理地址是0,在設(shè)備端看起來是0x80000000,而物理地址0又通常被映射為虛擬地址0xc0000000,從而同一地址就具備了三個(gè)身份,但他們?cè)谖锢砩现傅氖峭黄瑓^(qū)域。
歸根結(jié)底,不論是MMU,CPU或程序員,還是設(shè)備,他們的終極目的是操作內(nèi)存,至于怎么操作,它們又都有各自的比較舒服的操作方式,就是所謂的物理地址,虛擬地址和總線地址,至于為什么要通過這種方式操作內(nèi)存,請(qǐng)參考下一節(jié),MMU是什么,為什么,怎么做。
MMU是什么,為什么,怎么做
通常情況下,應(yīng)用程序并不需要關(guān)心內(nèi)存實(shí)際的物理地址,從應(yīng)用程序的角度,“我需要的時(shí)候你就要給我,至于你是如何分配的,還有多少空閑,我不管”,MMU使這種需求成為可能。
我們知道應(yīng)用程序的每一個(gè)進(jìn)程都有自己的一張頁(yè)表,通常0-3G為用戶空間,3-4G為內(nèi)核空間,每一個(gè)進(jìn)程都傻傻的以為自己獨(dú)自擁有4G的內(nèi)存空間,從而使得程序員在寫程序時(shí)不需要考慮計(jì)算機(jī)中物理內(nèi)存的實(shí)際容量,但是我們真的沒有這么大的內(nèi)存啊,怎么辦?沒關(guān)系,MMU可以解決。
MMU提供了虛擬地址和物理地址的映射功能,這個(gè)功能使每個(gè)進(jìn)程都擁有“4G獨(dú)立的內(nèi)存空間”成為可能。另外MMU還提供內(nèi)存權(quán)限保護(hù),用戶權(quán)限保護(hù)和Cache緩存控制等功能。
我們使用C語言定義一個(gè)const變量,MMU(應(yīng)該是內(nèi)核,而不是MMU)會(huì)標(biāo)記該變量所在的內(nèi)存區(qū)間為readonly,當(dāng)另外一個(gè)文件單元通過虛擬地址嘗試寫這個(gè)變量,MMU在把虛擬地址轉(zhuǎn)換為物理地址的過程中發(fā)現(xiàn),這段內(nèi)存區(qū)域是readonly的,那么,不好意思,你無權(quán)寫入,并產(chǎn)生一個(gè)fault,內(nèi)核收到這個(gè)fault向應(yīng)用程序發(fā)送一個(gè)SIGSEGV,應(yīng)用程序產(chǎn)生段錯(cuò)誤并結(jié)束。
用戶權(quán)限保護(hù),同理,MMU會(huì)標(biāo)記內(nèi)核空間的內(nèi)存,當(dāng)一個(gè)用戶程序嘗試訪問內(nèi)核空間內(nèi)存,也會(huì)被拒絕。
另外,MMU還提供Cache緩存控制功能。我們知道設(shè)備可通過DMA直接訪問內(nèi)存,而不需要CPU;另一方面讀寫內(nèi)存是非常耗時(shí)的(相對(duì)cache來說),如果我們的CPU存在高速緩存,把最近經(jīng)常使用的內(nèi)存緩沖的Cache可以大大的提高程序的效率。但是這時(shí)出現(xiàn)了一個(gè)問題,如何保證DMA和Cache的一致性問題?MMU提供了Cache是否命中的檢查,從而進(jìn)一步可以保證DMA與Cache的一致性。
那么MMU是如何實(shí)現(xiàn)物理地址到虛擬地址的映射的呢,請(qǐng)看下圖:
對(duì)于一個(gè)虛擬地址,0x12345670,地址的高20位表示頁(yè)表的物理地址,也就是0x12345(對(duì)應(yīng)圖中的p),通過這個(gè)地址找到頁(yè)表所在位置,讀取該位置中的數(shù)據(jù),這個(gè)數(shù)據(jù)指向物理地址的索引。虛擬地址的低12位表示物理地址索引的偏移值,也就是0x670(對(duì)應(yīng)圖中的d),我們現(xiàn)在有了物理地址的索引值和偏移值,自然就可以找到所對(duì)應(yīng)的物理內(nèi)存位置。
另外MMU比較重要的一個(gè)組成部分需要介紹一下,TLB(Translation Lookaside Buffer)轉(zhuǎn)換旁路緩存,顧名思義,他是一個(gè)物理地址和虛擬地址轉(zhuǎn)換關(guān)系的緩存,是上圖中Page table的cache,也被稱為快表。
最后,附上宋老師的總結(jié):http://mp.weixin.qq.com/s/SdsT6Is0VG84WlzcAkNCJA
內(nèi)存分區(qū)和內(nèi)存映射區(qū)
首先明確內(nèi)存分區(qū)和內(nèi)存映射區(qū)的區(qū)別,內(nèi)存分區(qū)指的是實(shí)際的物理內(nèi)存的分區(qū),內(nèi)存映射區(qū)指的是每一個(gè)進(jìn)程所擁有的虛擬地址空間的分區(qū)情況。
對(duì)于一個(gè)運(yùn)行了linux的設(shè)備,通常存在如下分區(qū):DMAzone、Normal zone、HighMem zone。
DMAzone存在的原因是有些設(shè)備存在硬件缺陷,無法通過DMA訪問全部的內(nèi)存空間,為了讓這些設(shè)備在需要內(nèi)存的時(shí)候能夠每次都申請(qǐng)到訪問能力范圍內(nèi)的內(nèi)存空間,內(nèi)核規(guī)定了一個(gè)DMA zone,當(dāng)這些設(shè)備申請(qǐng)內(nèi)存的時(shí)候,指定分配flag為GFP_DMA,它就可以拿到DMA zone的內(nèi)存。也就是說如果我們的設(shè)備不存在這樣的存在缺陷的設(shè)備,我們就不需要DMA zone,或者說整個(gè)Normal zone都是DMA zone。一般我們稱DMA zong + Normal zone為L(zhǎng)ow memory zone。
HighMemzone存在原因是,當(dāng)內(nèi)存較大時(shí),內(nèi)核空間的3-4G空間無法把所有物理內(nèi)存地址一一映射到內(nèi)核空間,只能映射一部分,那么無法映射的那部分就是高端內(nèi)存。
可以一一映射到內(nèi)核空間的那部分內(nèi)存,除去DMA zone就是Normal zone。
內(nèi)存映射區(qū)可以簡(jiǎn)單的理解為,每個(gè)進(jìn)程都擁有的4G空間。其中3-4G為內(nèi)核空間,這部分空間又被劃分為多個(gè)區(qū)域,其中與DMA zone和Normal zone存在一一映射關(guān)系的區(qū)域是DMA+常規(guī)內(nèi)存映射區(qū)或者low memory映射區(qū),同時(shí)也專門有個(gè)區(qū)域可以映射HighMem zone,但是并不存在一一映射的關(guān)系,這個(gè)區(qū)域是高端內(nèi)存映射區(qū)。
所謂的一一映射,是指虛擬地址和物理地址只是存在一個(gè)物理上的偏移。
在x86系統(tǒng)中,內(nèi)存分區(qū)和內(nèi)存映射區(qū)存在如下的關(guān)系:
在arm linux中,內(nèi)存分區(qū)和內(nèi)存映射區(qū)的關(guān)系請(qǐng)參考內(nèi)核文檔Documentation/arm/memory.txt
Buddy算法是個(gè)什么鬼
Linux的最底層的內(nèi)存分配算法叫做Buddy算法,它以2的n次方頁(yè)為單位對(duì)空閑內(nèi)存進(jìn)行管理。就是說不管我在應(yīng)用程序中分了多大的空間,1字節(jié),100KB,或者其它任意的大小,底層實(shí)際分配的是以2的n次方個(gè)頁(yè)對(duì)齊的空間,當(dāng)然并不是每一次用戶空間申請(qǐng)內(nèi)存都會(huì)引起底層的內(nèi)存分配,slab算法就可以在buddy算法的基礎(chǔ)上對(duì)內(nèi)存進(jìn)行二次管理,分配更小的內(nèi)存空間,當(dāng)然C庫(kù)也可以對(duì)分配的空間二次利用,比如指定mallopt函數(shù)的第一個(gè)參數(shù)為M_TRIM_THRESHOLD,并設(shè)置真正釋放內(nèi)存給系統(tǒng)的閥值。。
Buddy算法的優(yōu)點(diǎn)是避免了內(nèi)存的外部碎片,但是長(zhǎng)期運(yùn)行后,大片的內(nèi)存會(huì)比較少,而1頁(yè),2頁(yè),4頁(yè)這種內(nèi)存會(huì)非常多,當(dāng)我們分配大片連續(xù)內(nèi)存的時(shí)候就會(huì)出問題,具體解決辦法請(qǐng)參考下一節(jié)-CMA的工作原理。
在linux系統(tǒng)中,我們可以通過/proc/buddy文件來查看當(dāng)前系統(tǒng)空閑的連續(xù)內(nèi)存空間剩余情況。
CMA的工作原理
應(yīng)用程序中申請(qǐng)一塊內(nèi)存,在應(yīng)用程序看來是連續(xù)的,因?yàn)樘摂M地址本身是連續(xù)的,但實(shí)際的內(nèi)存空間中,所申請(qǐng)的這片內(nèi)存未必是連續(xù)的,不過這對(duì)應(yīng)用程序來說是沒關(guān)系的,因?yàn)閼?yīng)用程序不需要關(guān)心實(shí)際的內(nèi)存情況,只要MMU把物理地址映射成虛擬地址就好了。但是如果沒有MMU的情況呢,我們又需要一片連續(xù)的內(nèi)存空間,比如設(shè)備通過DMA直接訪問內(nèi)存,這種情況下應(yīng)該怎么辦呢?
CMA機(jī)制就是為了解決上面提到的問題而產(chǎn)生的。DMA zone并不是DMA專屬,其它的程序也可以申請(qǐng)?jiān)搝one的內(nèi)存,如果當(dāng)設(shè)備要申請(qǐng)DMA zone空間的一大片連續(xù)的內(nèi)存時(shí)候,已經(jīng)沒有連續(xù)的大片內(nèi)存了,只有1頁(yè),2頁(yè),4頁(yè)的這種連續(xù)的小內(nèi)存。解決辦法就是我們標(biāo)記某一片連續(xù)區(qū)域?yàn)镃MA區(qū)域,這部分區(qū)域在沒有大片連續(xù)內(nèi)存申請(qǐng)的時(shí)候只給moveable的程序使用,當(dāng)大片連續(xù)內(nèi)存請(qǐng)求來的時(shí)候,我們?nèi)ミ@片區(qū)域,把所有moveable的小片內(nèi)存移動(dòng)到其它的非CMA區(qū)域,更改對(duì)應(yīng)的程序的頁(yè)表,然后再把空出來的CMA區(qū)域給設(shè)備,從而實(shí)現(xiàn)了DMA大片連續(xù)內(nèi)存的分配。
CMA機(jī)制并不是單獨(dú)存在的,它通常服務(wù)于DMA設(shè)備,在設(shè)備調(diào)用dma_alloc_coherent函數(shù)申請(qǐng)一塊內(nèi)存后,為了得到一片連續(xù)的內(nèi)存,CMA機(jī)制被調(diào)用,它保證了申請(qǐng)的內(nèi)存的連續(xù)性。
另外CMA區(qū)域通常被分配在高端內(nèi)存。
任督二脈之內(nèi)存管理第二節(jié)課總結(jié)
本文是任督二脈之內(nèi)存管理課程第二節(jié)課的總結(jié)說明,由于水平有限,可能無法對(duì)宋老師所講完全理解通透,如有錯(cuò)誤,請(qǐng)及時(shí)指證。
本文從4個(gè)方面進(jìn)行說明:
1、 Slab的基本原理以及它的文件接口說明
2、 kmalloc、vmalloc、malloc比較
3、 OOM是什么,為什么,怎么做
4、 FAQ:群里經(jīng)常問到的,也是比較容易誤解的問題
slab的基本原理以及它的文件接口說明
在第一節(jié)課中,我們了解到,Linux的最底層,由Buddy算法管理著所有的空閑頁(yè)面,最小單位是2的0次方頁(yè),就是1頁(yè),4K,但是很多時(shí)候,我們?yōu)橐粋€(gè)結(jié)構(gòu)分配空間,也只需要幾十個(gè)字節(jié),按頁(yè)分配無疑是浪費(fèi)空間;另外,當(dāng)我們頻繁的分配和釋放一個(gè)結(jié)構(gòu),我們希望在釋放的時(shí)候,這部分內(nèi)存不要立刻還給Buddy,而是提供一種類似C庫(kù)的管理機(jī)制,在下一次在分配的時(shí)候還可以拿到同一塊內(nèi)存且保留著基本的數(shù)據(jù)結(jié)構(gòu)?;谏厦鎯牲c(diǎn),Slab應(yīng)運(yùn)而生。總結(jié)一下,Slab主要提供以下兩個(gè)功能:
A.對(duì)從Buddy拿到的內(nèi)存進(jìn)行二次管理,以更小的單位進(jìn)行分配和回收(注意,是回收而不是釋放),防止了空間的浪費(fèi)。
B. 讓頻繁使用的對(duì)象盡量分配在同一塊內(nèi)存區(qū)間并保留基本數(shù)據(jù)結(jié)構(gòu),提高程序效率。
那么,Slab是如何工作的呢?
如果某個(gè)結(jié)構(gòu)被頻繁的使用,內(nèi)核源碼就可以針對(duì)這個(gè)結(jié)構(gòu)建立一個(gè)或者多個(gè)Slab分區(qū)(姑且這么叫),每一個(gè)Slab分區(qū)從Buddy拿到1頁(yè)或者多頁(yè)內(nèi)存,并把這些內(nèi)存劃分為多個(gè)等分的這個(gè)結(jié)構(gòu)大小的小塊內(nèi)存,這些Slab分區(qū)只用于分配給這個(gè)結(jié)構(gòu),通常稱這個(gè)結(jié)構(gòu)為object,每一次當(dāng)有該object的分配請(qǐng)求,內(nèi)核就從對(duì)應(yīng)的Slab分區(qū)拿一小塊內(nèi)存給object,這樣就實(shí)現(xiàn)了在同一片內(nèi)存區(qū)間為頻繁使用的object分配內(nèi)存。請(qǐng)看下圖
黑框表示頻繁使用的結(jié)構(gòu);紅框表示slab分區(qū),一個(gè)結(jié)構(gòu)內(nèi)核可能為它分配一個(gè)或多個(gè)Slab;每個(gè)Slab分區(qū)有可能包含多個(gè)page,被分隔開的多個(gè)紅框表示Slab分區(qū)的多個(gè)pages;藍(lán)框表示Slab分區(qū)為對(duì)應(yīng)的Object劃分的一個(gè)一個(gè)的小內(nèi)存塊。填充黃色的框表示active的object,灰色填充的框表示未active的object,如果整個(gè)Slab分區(qū)的所有藍(lán)框都是灰色的,表示這個(gè)Slab分區(qū)是未active的。
Linux為用戶提供了Slab的文件查看接口,和命令接口。
文件接口:/proc/slabinfo
上圖所示為slabinfo文件的內(nèi)容,第一行為表頭:
Name:Object名字
Active_objs:已經(jīng)激活的投入使用的object個(gè)數(shù)
Num_objs:為這個(gè)object分配的小內(nèi)存塊個(gè)數(shù)
Objsize:每一個(gè)內(nèi)存塊的大小
Objperslab:每一個(gè)Slab分區(qū)包含的object個(gè)數(shù)
Pagesperslab:每個(gè)Slab分區(qū)包含的page的個(gè)數(shù)
Active_slabs:已經(jīng)激活的投入使用的Slab分區(qū)個(gè)數(shù)
Num_slabs:為這個(gè)object分配的Slab分區(qū)個(gè)數(shù)
我在查看Slabinfo文件的時(shí)候,發(fā)現(xiàn)有的Num_objs為0,正常Active_objs為0是可以理解的,但是總的object數(shù)不應(yīng)該為0啊,然后繼續(xù)看,Active_slabs和Num_slabs也都為0,也就是說,這個(gè)時(shí)候內(nèi)存還沒有為這個(gè)結(jié)構(gòu)分配Slab分區(qū),一切就都解釋的通了。
另外還有一部分slabinfo的內(nèi)容是這樣的:
就是說,除了經(jīng)常頻繁使用的結(jié)構(gòu),內(nèi)核為他們分配了slabs,還同時(shí)定義了一些特定的slabs供驅(qū)動(dòng)使用。
命令接口:slabtop
直接運(yùn)行slabtop命令(要加sudo,上面查看slabinfo文件同樣),內(nèi)容如下
有點(diǎn)類似top命令,按照使用內(nèi)存的多少進(jìn)行排序。
最后再說一句,slab只用于分配低端內(nèi)存,所分配的內(nèi)存也只會(huì)被映射到物理內(nèi)存映射區(qū),所以vmalloc跟slab一毛錢關(guān)系都沒有。
kmalloc、vmalloc、malloc比較
這部分內(nèi)容牽連太多,也不好區(qū)分,直接上圖:
(此圖有誤:highmem不是映射到vmalloc)
如上圖所示:
如上圖所示:
A.kmalloc函數(shù)是基于slab算法的,從物理內(nèi)存的low mem獲取內(nèi)存,并線性映射到物理內(nèi)存映射區(qū)(映射過程開機(jī)就已經(jīng)完成了),由于是線性映射,物理地址和虛擬地址存在簡(jiǎn)單的轉(zhuǎn)換關(guān)系(物理地址和虛擬地址的值只是相差了一個(gè)固定的偏移),所以使用kmalloc分配內(nèi)存是十分高效的。
B. vmalloc函數(shù)分配內(nèi)存的過程需要先通過alloc_pages函數(shù)獲取內(nèi)存(獲取范圍是整個(gè)內(nèi)存條),然后在通過復(fù)雜的邏輯轉(zhuǎn)換(注意,vmalloc并不是簡(jiǎn)單的線性映射,它獲取的內(nèi)存并不是連續(xù)的),把物理內(nèi)存映射到vmalloc映射區(qū)。這個(gè)過程比較復(fù)雜,所以,如果只是使用vmalloc分配很小的內(nèi)存空間是不合適的。
C. malloc是標(biāo)準(zhǔn)C庫(kù)的函數(shù),C庫(kù)對(duì)申請(qǐng)的內(nèi)存做二次管理,類似Slab。但是注意一點(diǎn),當(dāng)我們使用malloc函數(shù)申請(qǐng)一片內(nèi)存時(shí),實(shí)際上是從C庫(kù)獲取的內(nèi)存,就是說,調(diào)用malloc返回后,系統(tǒng)未必給你一片真正的內(nèi)存,分兩種情況
a. C庫(kù)還持有足夠的內(nèi)存,那么malloc就可以直接分配到C庫(kù)現(xiàn)有的內(nèi)存
b. C庫(kù)沒有足夠的內(nèi)存,malloc返回時(shí),系統(tǒng)只是把要申請(qǐng)的內(nèi)存大小的虛擬地址空間全部映射到同一塊已經(jīng)清零的物理內(nèi)存,當(dāng)我們實(shí)際要寫這片內(nèi)存的時(shí)候,才通過brk/mmap系統(tǒng)調(diào)用分配真實(shí)的物理內(nèi)存,并改寫進(jìn)程頁(yè)表。
D.另外有一點(diǎn)值得一提,vmalloc映射區(qū),除了vmalloc函數(shù)分配的內(nèi)存會(huì)映射在該區(qū)域,設(shè)備的寄存器也同樣會(huì)通過ioremap映射到該區(qū)域。
E. 根據(jù)上面要點(diǎn)C的描述,在編寫實(shí)時(shí)程序時(shí),我們可以通過下面這種方式,減少系統(tǒng)內(nèi)存的頻繁分配,而是基于C庫(kù)管理的內(nèi)存。
#include
#include
#define SOMESIZE (100*1024*1024) // 100MB
int main(int argc, char *argv[])
{
unsigned char *buffer;
int i;
if (!mlockall(MCL_CURRENT | MCL_FUTURE))//鎖定進(jìn)程當(dāng)前和將來所有的內(nèi)存
mallopt(M_TRIM_THRESHOLD, -1UL);//設(shè)置C庫(kù)釋放內(nèi)存的閥值為最大正整數(shù)
mallopt(M_MMAP_MAX, 0);
buffer = malloc(SOMESIZE);
if (!buffer)
exit(-1);
/*
*Touch each page in this piece of memory to get it
*mapped into RAM
*/
for (i = 0; i < SOMESIZE; i += 4 *1024)//由于COW,確保內(nèi)存被真實(shí)分配
buffer[i] = 0;
free(buffer);
/*
/* 接下來的所有內(nèi)存分配動(dòng)作都不是觸發(fā)系統(tǒng)內(nèi)存的真是分配,而是從C庫(kù)獲取
*大大提高程序的效率,確保程序?qū)崟r(shí)性。
*/
while(1);
return 0;
}
OOM是什么,為什么,怎么做
什么是OOM:上面提到,當(dāng)我們使用malloc分配內(nèi)存時(shí),系統(tǒng)并沒有真正的分配內(nèi)存,而是采用欺騙性的手段,拖延分配內(nèi)存的時(shí)機(jī),防止無謂的內(nèi)存消耗,只有在我們寫入的時(shí)候,產(chǎn)生page fault才會(huì)拿到真實(shí)的內(nèi)存,且是寫多少才分配多少,那么,問題來了,當(dāng)我們通過malloc獲取一片內(nèi)存并成功返回,然后開始逐步使用內(nèi)存,系統(tǒng)也逐步的分配真實(shí)的內(nèi)存給進(jìn)程,但這個(gè)過程中,另外一個(gè)耗內(nèi)存的程序快速的拿走所有的內(nèi)存,導(dǎo)致我的進(jìn)程在逐步寫入的過程發(fā)現(xiàn)剛剛說好給我的內(nèi)存現(xiàn)在沒有了,這種情況就是OOM,out of memory!
在Linux系統(tǒng),每一個(gè)進(jìn)程都有一個(gè)oomscore,這個(gè)數(shù)值越高,說明進(jìn)程消耗的內(nèi)存越多,在發(fā)生OOM的情況下,oom score越高的進(jìn)程就越有可能被系統(tǒng)干掉,從而緩解系統(tǒng)的內(nèi)存壓力。我們可以通過/proc/pid/oom_score文件查看進(jìn)程的oom score。
那么有沒有什么辦法可以調(diào)整進(jìn)程的oom score,就算這個(gè)進(jìn)程比較耗內(nèi)存,但是在OOM時(shí)候,這個(gè)進(jìn)程仍然不會(huì)干掉。系統(tǒng)提供兩個(gè)接口文件給用戶:
/proc/pid/oom_ adj:可配置范圍是-17到15,設(shè)置為15,oom score最大,最容易被干掉,設(shè)置-16,oom score最小,設(shè)置-17為禁止使用OOM殺死該進(jìn)程。
/proc/pid/oom_score_adj:oom score會(huì)加上這個(gè)值,也可以設(shè)置負(fù)數(shù),但如果負(fù)數(shù)的絕對(duì)值大于oom score,oom score最小為0。
FAQ
Q.kfree和free函數(shù)調(diào)用后,內(nèi)存是否還給了Buddy?
A.kmalloc分配的內(nèi)存是基于slab的,malloc分配的內(nèi)存是基于C庫(kù)的,slab和C庫(kù)都會(huì)對(duì)內(nèi)存進(jìn)行二次管理,實(shí)際到底有沒有被釋放,只有Slab和C庫(kù)他們自己知道。
Q.kmalloc,vmalloc,malloc他們從哪個(gè)zone申請(qǐng)物理內(nèi)存,然后映射到那個(gè)映射區(qū)?
A.kmalloc從low mem獲取物理內(nèi)存,然后映射到內(nèi)核空間的物理內(nèi)存映射區(qū),vmalloc和malloc都可以從整個(gè)內(nèi)存條獲取內(nèi)存,vmalloc申請(qǐng)的內(nèi)存映射到vmalloc映射區(qū),malloc申請(qǐng)的內(nèi)存映射到進(jìn)程的用戶空間。
寫到這里,群里已經(jīng)發(fā)出了第二節(jié)課問答集,其它更多內(nèi)容參考該文檔。
任督二脈之內(nèi)存管理第三節(jié)課總結(jié)
本文是任督二脈之內(nèi)存管理課程第三節(jié)課的總結(jié)說明,由于水平有限,可能無法對(duì)宋老師所講完全理解通透,如有錯(cuò)誤,請(qǐng)及時(shí)指證。
本文從7個(gè)方面進(jìn)行說明:
1、 VMA到底是個(gè)什么鬼?
2、 Linux提供的VMA文件接口和命令接口說明。
3、 Page fault的產(chǎn)生原因分析以及與VMA的關(guān)系。
4、 物理內(nèi)存、頁(yè)表、進(jìn)程之間的愛恨情仇。
5、 VSS、RSS、PSS、USS概念說明和實(shí)際的應(yīng)用場(chǎng)景
6、 進(jìn)程內(nèi)存使用情況命令接口smem。
7、 內(nèi)存泄漏的界定和監(jiān)測(cè)辦法。
VMA到底是個(gè)什么鬼?
VMA是Virtual MemoryAreas的縮寫,虛擬內(nèi)存區(qū)域,指的是用戶空間0-3G范圍內(nèi)進(jìn)程所擁有的多個(gè)全部零散分布的連續(xù)的虛擬內(nèi)存空間。
注意上面這句話的三個(gè)定語:
用戶空間0-3G范圍內(nèi)進(jìn)程所擁有的:VMA區(qū)域存在于用戶空間,當(dāng)然“所擁有”并不是獨(dú)占,也有可能是共享的。
多個(gè)全部零散分布的:進(jìn)程擁有多個(gè)VMA區(qū)域,但是零散分布在0-3G空間。
連續(xù)的:這里說的連續(xù),指的是單個(gè)VMA區(qū)域在虛擬地址空間是連續(xù)的。
看完上面的內(nèi)容基本知道VMA是個(gè)什么東西了,那么VMA產(chǎn)生的原因是什么,為什么要搞個(gè)這個(gè)概念出來,VMA區(qū)域是客觀存在的,你不定義VMA的struct,進(jìn)程的代碼段,數(shù)據(jù)段,堆,棧都是客觀存在的,進(jìn)程只要在代碼段按序執(zhí)行就好了,我管你叫什么名字,所以進(jìn)程并不需要關(guān)心這個(gè)概念,真正需要VMA概念的是內(nèi)核,通過VMA這個(gè)概念方便實(shí)現(xiàn)對(duì)所有進(jìn)程內(nèi)存空間的管理。
我們知道一個(gè)進(jìn)程被fork出來,內(nèi)核會(huì)維護(hù)一個(gè)taskstruct,這個(gè)結(jié)構(gòu)的mmap成員維護(hù)了一個(gè)vm_area_struct的鏈表,這就是VMA的結(jié)構(gòu),內(nèi)核通過維護(hù)這個(gè)結(jié)構(gòu)來實(shí)現(xiàn)對(duì)進(jìn)程的內(nèi)存資源的管理、隔離和共享。
舉個(gè)例子:進(jìn)程使用malloc分配內(nèi)存,這時(shí)C庫(kù)沒有足夠的內(nèi)存,內(nèi)核的Lazy機(jī)制采用欺騙性手段,拖延分配內(nèi)存的時(shí)機(jī),這時(shí),內(nèi)存并沒有被真正分配,內(nèi)核只是把所有要分配的頁(yè)表都映射到同一片已經(jīng)清零的物理地址,并標(biāo)記頁(yè)表權(quán)限為readonly,但是當(dāng)malloc返回的時(shí)候,對(duì)應(yīng)的heap的VMA區(qū)域已經(jīng)產(chǎn)生了,且已經(jīng)進(jìn)入內(nèi)核管理的vm_area_struct鏈表中了,且權(quán)限被標(biāo)記為讀寫權(quán)限,當(dāng)我們向這片內(nèi)存寫入的時(shí)候,MMU在虛擬地址轉(zhuǎn)換到物理地址的過程,發(fā)現(xiàn)頁(yè)表的權(quán)限標(biāo)記為readonly,而你要寫入,這是不被允許的,于是產(chǎn)生Page fault,內(nèi)核收到Page fault,查看對(duì)應(yīng)的VMA鏈表,發(fā)現(xiàn)進(jìn)程實(shí)際是有寫的權(quán)限,于是分配頁(yè)面,并改寫頁(yè)表,但是這整個(gè)過程,進(jìn)程是不知道的,進(jìn)程只是傻傻的以為,哈哈,老子又拿到了一片內(nèi)存!
Linux提供的VMA文件接口和命令接口
Linux為用戶提供了VMA查看的命令接口和文件接口。
命令接口:pmap
文件接口:/proc/pid/maps 、 /proc/pid/smaps
這些接口都可以看到進(jìn)程的VMA區(qū)域分布情況,占用空間大小,和相應(yīng)的權(quán)限
此處不做過多說明。
Page fault的產(chǎn)生原因分析以及與VMA的關(guān)系
其實(shí)上面的“VMA到底是個(gè)什么鬼”章節(jié),已經(jīng)介紹了一種Pagefault產(chǎn)生的原因,也說明了與VMA的關(guān)系。下面列舉產(chǎn)生Page fault的4中原因。
A.動(dòng)態(tài)分配內(nèi)存,第一次寫入,由于內(nèi)核的Lazy機(jī)制,頁(yè)表的權(quán)限為readonly,VMA權(quán)限為r+w,MMU產(chǎn)生Page fault,這種情況是真實(shí)的缺頁(yè),下面還會(huì)介紹并不是真實(shí)的缺頁(yè)的情況。這種缺頁(yè)也叫做Minor Page fault。
B. 進(jìn)程訪問自己的VMA區(qū)域以外的空間,這種行為被認(rèn)為是非法的,同樣會(huì)產(chǎn)生Page fault,但不會(huì)像A中一樣引起真正的內(nèi)存分配,反而會(huì)收到一個(gè)segv,程序被干掉。這種情況其實(shí)并不是真正的缺頁(yè)。
C. 進(jìn)程訪問自己的VMA區(qū)域,但是并沒有執(zhí)行該操作的權(quán)限,比如進(jìn)程嘗試寫代碼段或者跳轉(zhuǎn)到數(shù)據(jù)段執(zhí)行,這也被認(rèn)為是非法的,同樣收到segv,程序被干掉。這種情況也不是真正的缺頁(yè)。
D.進(jìn)程訪問自己的VMA區(qū)域,且權(quán)限正確,但是對(duì)應(yīng)的物理內(nèi)存內(nèi)容被swap到硬盤,這種情況毫無疑問才是徹徹底底的缺頁(yè),也會(huì)產(chǎn)生Page fault。這種缺頁(yè)也叫做Major Page fault。
物理內(nèi)存、頁(yè)表、進(jìn)程之間的愛恨情仇
此部分說起來比較復(fù)雜,直接上圖:
上圖中,process1044,1045,1054是進(jìn)程的虛擬地址空間,綠色框圖是他們各自的頁(yè)表,圖片最中間的是實(shí)際的物理內(nèi)存。進(jìn)程1044,1045,1054在虛擬地址空間各自擁有多個(gè)VMA區(qū)域,但是多個(gè)進(jìn)程的VMA可能通過各自頁(yè)表指向同一片內(nèi)存區(qū)域,比如上圖中l(wèi)ibc代碼段,三個(gè)進(jìn)程的libc的VMA區(qū)域都通過頁(yè)表指向同一片內(nèi)存,就是說這三個(gè)進(jìn)程共享這段內(nèi)存。當(dāng)然進(jìn)程的VMA通過頁(yè)表指向的內(nèi)存也有可能是被這個(gè)進(jìn)程獨(dú)占的,比如上面三個(gè)進(jìn)程的堆,都是各自獨(dú)占的。
舉個(gè)例子:假設(shè)上圖中的內(nèi)存是女神,女神為了襯托自己漂亮,身邊難免要有幾個(gè)丑女閨蜜,丑女閨蜜就是頁(yè)表,屌絲process 1044,1045,1054對(duì)女神愛慕已久,只是苦于女神高高在上,無法接近,那么怎么辦,曲線救國(guó),先接近她的閨蜜,三個(gè)屌絲通過女神的三個(gè)閨蜜都輕松的獲取到女神的基本愛好,喜歡吃什么,喜歡什么顏色,三個(gè)屌絲雖然都各自獲取到一份信息,但是這個(gè)信息是客觀存在的只有一份(這就是上面說的共享的情況),這時(shí),其中一個(gè)屌絲1044,辛苦加班12個(gè)月,攢錢給閨蜜買了個(gè)大金鏈子,這個(gè)大金鏈子就是屌絲1044跟女神所擁有的獨(dú)家美好記憶(這就是上面說的獨(dú)占的情況)。
VSS、RSS、PSS、USS概念說明和實(shí)際的應(yīng)用場(chǎng)景
話不多說,還是上圖吧:
如圖所示:
VSS是進(jìn)程看到的自己在虛擬內(nèi)存空間所占用的內(nèi)存。
RSS是進(jìn)程實(shí)際真正使用的內(nèi)存。
PSS是多進(jìn)程共享一片內(nèi)存的容量取平均數(shù),在加上自己獨(dú)占的內(nèi)存。
USS是進(jìn)程所獨(dú)占的內(nèi)存容量。
通常,VSS≥RSS>PSS>USS,VSS之所以大于等于RSS,是考慮內(nèi)核的Lazy機(jī)制并沒有真正的分配內(nèi)存以及內(nèi)存被換出等情況。
好!繼續(xù)舉例子:女神懷了高富帥的娃,苦于腹中胎兒一天天長(zhǎng)大,高富帥又不值得托付,所以決定在眾多屌絲中擇一名形象氣質(zhì)佳的男士作為配偶,屌絲1044,1045,1054踴躍報(bào)名,由于三人形象上都不分伯仲,所以女神的主要考察點(diǎn)改為:誰更在乎自己多一點(diǎn)。于是:
VSS:屌絲自以為對(duì)女神的在乎程度,知道女神的愛好,還給女神買大金鏈子都計(jì)算在內(nèi)。
RSS:每個(gè)人的感知程度不一樣,屌絲縱然萬般寵愛,可女神沒感覺到也是白搭,這個(gè)值是女神感受到的在乎程度。
PSS:請(qǐng)來裁判,考察屌絲日常的在乎程度,知道女神愛好+1分,送大金鏈子+3分,這個(gè)分?jǐn)?shù)是比較客觀的。
USS:?jiǎn)为?dú)考量每個(gè)屌絲送多少大金鏈子。
所以,綜上,我們知道PSS值是比較客觀的值,VSS是一個(gè)虛擬的值,RSS是一個(gè)實(shí)際的值,USS是獨(dú)占的值。
進(jìn)程內(nèi)存使用情況命令接口smem
Linux提供命令接口smem來查看系統(tǒng)中進(jìn)程的VSS、RSS、PSS、USS值。
還可以使用—pie選項(xiàng)和—bar選項(xiàng)進(jìn)程圖形化顯示,更加一目了然。
內(nèi)存泄漏的界定和監(jiān)測(cè)辦法
進(jìn)程運(yùn)行時(shí)申請(qǐng)的內(nèi)存,在進(jìn)程結(jié)束后會(huì)被全部釋放。內(nèi)存泄漏指的是運(yùn)行的程序,隨著時(shí)間的推移,占用的內(nèi)存容量呈現(xiàn)線性增長(zhǎng),原因是程序中的申請(qǐng)和釋放不成對(duì)。
如何監(jiān)測(cè)程序是否出現(xiàn)了內(nèi)存泄漏的情況,一方面我們可以通過上文提到的smem命令,連續(xù)的在多個(gè)時(shí)間點(diǎn)采樣,記錄USS的變化情況,如果這個(gè)值在連續(xù)的很長(zhǎng)時(shí)間里呈現(xiàn)出持續(xù)增長(zhǎng),基本就可以斷定程序存在內(nèi)存泄漏的情況,然后你就可以手動(dòng)去程序中查找泄漏位置。
另一方面,如果程序代碼量較大,不方便查找定位內(nèi)存泄漏點(diǎn),可以使用valgrind和addresssanitizer來查找程序的內(nèi)存泄漏。兩種方式各有優(yōu)劣,valgrind在虛擬機(jī)中運(yùn)行程序,所以程序運(yùn)行效率下降;addresssanitizer則需要改動(dòng)源碼,在源碼中包含sanitizer/lsan_interface.h文件,然后在需要檢查內(nèi)存泄漏的地方調(diào)用函數(shù)__lsan_do_leak_check。兩種方式都可以定位內(nèi)存泄漏的位置。
任督二脈之內(nèi)存管理第四節(jié)課總結(jié)
本文是任督二脈之內(nèi)存管理課程第四節(jié)課的總結(jié)說明,由于水平有限,可能無法對(duì)宋老師所講完全理解通透,如有錯(cuò)誤,請(qǐng)及時(shí)指證。
發(fā)了前幾天的總結(jié)后,有群里的朋友@jeff表示,我這樣大篇幅的文字描述,估計(jì)沒幾個(gè)人有耐心看下去,想想也是,內(nèi)存管理本身就比較復(fù)雜,枯燥,我聽了宋老師的課,了解個(gè)一知半解,轉(zhuǎn)述的過程可能也不到位。所以這一次盡量使用圖片進(jìn)行說明,然后逐步展開。話不多說,先上圖吧。
如圖所示:有兩條脈絡(luò),分別用黃線和藍(lán)線標(biāo)識(shí),黃線的脈絡(luò)為有文件背景的數(shù)據(jù)交換過程,藍(lán)線為無文件背景的數(shù)據(jù)交換過程。
那么何為有文件背景的頁(yè)File-backedpage,何為無文件背景的頁(yè),也就是匿名頁(yè)anonymous page,直接盜用宋老師課件圖片:
特別說明一下程序的代碼段,程序運(yùn)行的時(shí)候,實(shí)際上是把ELF文件的代碼段加載到物理內(nèi)存的page cache,然后映射到內(nèi)核空間的page cache頁(yè),所以程序的代碼段也是file backed的。
接下來分兩條脈絡(luò)來進(jìn)行說明和擴(kuò)展。
Filebacked
在硬盤中能對(duì)應(yīng)到實(shí)際的文件的,歸納為file backed,文件在open后,會(huì)被加載到物理內(nèi)存,我們稱這片內(nèi)存為page cache頁(yè),在3.14版本以前的內(nèi)核,page cache又被劃分為buffers和cache,3.14版本以后不做區(qū)分,全部看作page cache。Page cache被映射到內(nèi)核空間的虛擬地址,用戶通過兩種方式訪問磁盤上的文件,直接讀寫和mmap到用戶空間,可能觸發(fā)page cache頁(yè)面和磁盤數(shù)據(jù)交換的有LRU,手動(dòng)同步SYNC和內(nèi)存回收reclaim。
上面的一段話,描述了filebacked的整體脈絡(luò),有一下幾個(gè)問題需要單獨(dú)說明:
A.3.14版本以前的pagecache劃分為buffers和cache,他們有什么區(qū)別?
要說明這個(gè)問題,先明確一個(gè)概念,page cache作為磁盤的一個(gè)緩沖,它緩沖過來的文件內(nèi)容是可以被犧牲掉的,就是說我內(nèi)存空間不足的時(shí)候,我可以回收這部分內(nèi)存資源,所以在Linux操作系統(tǒng)看來,這部分內(nèi)存雖然被占用,但是仍然是available的。
通過free命令,可以看到3.13版本分別列出了buffers和cached的數(shù)值,buffers值是直接操作裸分區(qū)的pagecache的大小,比如我們通過dd命令讀寫SD卡;cached值是操作文件系統(tǒng)上的文件的page cache的大小,比如直接open dev/sda1/hello.c文件,這個(gè)文件的page cache被叫做cached。
看上圖,free命令的第三行,有一個(gè)-/+buffers/cache,這個(gè)值是如何計(jì)算的呢?
上面說過,page cache是可犧牲的,這個(gè)值就是page cache被回收后的內(nèi)存的used和free的值,盜用宋老師課件again。
其實(shí)這種劃分沒有什么特別的意義,他們的區(qū)別是各自的background不同而已,所以3.14版本之后的內(nèi)核不做buffers和cached的區(qū)分,free命令的-/+ buffers/cache這一行也不再需要,只是單獨(dú)搞出一個(gè)available值,用于表示當(dāng)前系統(tǒng)中包括已經(jīng)使用的page cache(可犧牲),到底有多少可利用的內(nèi)存。
原諒我的free還不夠給力,并沒有列出available值。
B.mmap和直接讀寫有什么區(qū)別?
Pagecache被映射到內(nèi)核空間后,用戶想要操作對(duì)應(yīng)的文件,實(shí)際上是對(duì)內(nèi)存里page cache的操作,系統(tǒng)會(huì)在合適的時(shí)機(jī)回寫到磁盤中對(duì)應(yīng)的文件。操作的方式有兩種,直接讀寫,和通過mmap把page cache在映射到用戶空間一份。
程序調(diào)用read、write函數(shù)的時(shí)候,陷入到內(nèi)核空間,實(shí)際調(diào)用的是file operation結(jié)構(gòu)的read、write接口,這兩個(gè)接口必然要做的一件事就是copy_from_user和copy_to_user,我們知道這兩個(gè)函數(shù)是會(huì)引起內(nèi)核空間與用戶空間的數(shù)據(jù)拷貝的,就是說每一次read和write都要進(jìn)行一次拷貝。
mmap則完全不同,它把pagecache直接映射到用戶空間,現(xiàn)在用戶空間和內(nèi)核空間的頁(yè)表都可以對(duì)應(yīng)到page cache物理內(nèi)存。然后通過mmap返回的指針來讀寫page cache,這個(gè)過程是沒有用戶空間和內(nèi)核空間的內(nèi)存拷貝的。
通常在操作顯存設(shè)備的時(shí)候會(huì)使用mmap,比如我可以通過mmap把/dev/fb0映射到用戶空間,通過讀寫mmap返回的指針來實(shí)現(xiàn)對(duì)屏幕的顯示。
C.LRU是個(gè)什么鬼?
LUR,LeastRecently Used,翻譯過來就是最近最少使用,顧名思義,內(nèi)核把最近最少使用的page cache內(nèi)容或者anonymous頁(yè)面交換出去。上圖,盜圖three
現(xiàn)在cache的大小是4頁(yè),前四次,1,2,3,4文件被一次使用,注意第七次,5文件被使用,系統(tǒng)評(píng)估最近最少被使用的文件是3,那么不好意思,3被swap出去,5加載進(jìn)來,依次類推。
所以LRU可能會(huì)觸發(fā)pagecache或者anonymous頁(yè)與對(duì)應(yīng)文件的數(shù)據(jù)交換。
D.SYNC指的什么,如何觸發(fā)pagecache與文件的數(shù)據(jù)交換?
系統(tǒng)為用戶提供了,數(shù)據(jù)從pagecache回寫到文件的接口,sync命令。只要運(yùn)行sync命令就可以觸發(fā)page cache與文件的數(shù)據(jù)交換。
另外,我們還可以通過向/proc/sys/vm/drop_caches寫入數(shù)字來清空對(duì)應(yīng)的caches,一般寫入之前,最好執(zhí)行一下sync命令來同步一下,以便釋放更多的空間,因?yàn)閐rop_caches只回收cleanpages,不回收dirtypages,所以如果想回收更多的cache,應(yīng)該在drop_caches之前先執(zhí)行"sync"命令,把dirtypages變成cleanpages。
echo 1 > /proc/sys/vm/drop_caches //清空 pagecache
echo 2 > /proc/sys/vm/drop_caches //清空 dentries 和 inodes
echo 3 > /proc/sys/vm/drop_caches //清空所有緩存(pagecache、dentries 和 inodes)
anonymous
在進(jìn)程中定義的堆,棧等沒有文件背景的頁(yè)面,如果系統(tǒng)沒有創(chuàng)建swap分區(qū)或者swap文件,那不好意思,匿名頁(yè)只能常駐內(nèi)存,直到程序退出,或者發(fā)生OOM程序被干掉。與file backed不同的是,anonymous本身就在內(nèi)存中,而file backed是磁盤中的文件,為了提高效率把內(nèi)存作為磁盤的緩沖區(qū),就是page cache,page cache可以往對(duì)應(yīng)文件交換,anonymous頁(yè)如果過大的話,可以往swap分區(qū)或者創(chuàng)建的swap文件中交換。要操作這些匿名頁(yè),直接在程序源碼中操作變量和動(dòng)態(tài)分配內(nèi)存的指針就好了。
那么,對(duì)于anonymous頁(yè),在什么情況下會(huì)觸發(fā)數(shù)據(jù)交換呢?
除了LRU會(huì)產(chǎn)生匿名頁(yè)的交換,內(nèi)存回收也會(huì)引發(fā)數(shù)據(jù)交換,reclaim,Linux有一個(gè)后臺(tái)進(jìn)程kswapd,負(fù)責(zé)回收page cache和匿名頁(yè),回收速度較慢,但是不會(huì)影響程序運(yùn)行,程序不會(huì)被delay,當(dāng)系統(tǒng)內(nèi)存資源異常緊張時(shí),會(huì)觸發(fā)Direct reclaim,這個(gè)過程回收速度較快,但是進(jìn)程會(huì)被直接delay,直到回收夠足夠的內(nèi)存。
至于何時(shí)kswapd開始回收內(nèi)存,何時(shí)Directreclaim,有三個(gè)門限值,min,low,hight,當(dāng)內(nèi)存的水位達(dá)到low,說明內(nèi)存緊張,這時(shí)kswapd開始工作,慢慢回收內(nèi)存,直到水位達(dá)到high,當(dāng)系統(tǒng)內(nèi)存異常緊張時(shí),達(dá)到min水位,Direct reclaim被觸發(fā)。
Swappiness
當(dāng)系統(tǒng)內(nèi)存不足時(shí),可以從filebacked pages或者anonymous pages回收內(nèi)存,不論哪個(gè)被回收,再次被加載進(jìn)內(nèi)存一定都會(huì)影響程序的效率。具體從哪里回收,Linux提供swappiness值作為衡量標(biāo)準(zhǔn)。
Swappiness越大,越傾向于回收匿名頁(yè);swappiness越小,越傾向于回收file-backed的頁(yè)面。當(dāng)然,它們的回收方法都是一樣的LRU算法。盜圖four。
順便附上一個(gè)參考鏈接:http://mp.weixin.qq.com/s/BixMISiPz3sR9FDNfVSJ6w
zRamswap
對(duì)于嵌入式設(shè)備,它的磁盤是SD卡,MMC,一方面速度較慢,另一方面,有使用壽命的問題,不太適合做swap分區(qū)。嵌入式設(shè)備一般會(huì)從內(nèi)存中拿出一小部分當(dāng)作虛擬內(nèi)存,這個(gè)就是zRam。但是這樣直接用又沒有什么意義,因?yàn)樘摂M內(nèi)存的目的就是在內(nèi)存不足時(shí)“擴(kuò)展內(nèi)存”,現(xiàn)在內(nèi)存還是那片內(nèi)存就是換了個(gè)說法,所以為了“擴(kuò)展內(nèi)存”,當(dāng)系統(tǒng)把內(nèi)存交換到這個(gè)虛擬內(nèi)存,通常是以壓縮的方式存儲(chǔ),當(dāng)swap in時(shí)在解壓。這樣就某種程度的“擴(kuò)展了內(nèi)存”,但是缺點(diǎn)是增加了CPU的壓力,需要進(jìn)行壓縮和解壓縮。
任督二脈之內(nèi)存管理第五節(jié)課總結(jié)
本文是任督二脈之內(nèi)存管理課程第五節(jié)課的總結(jié)說明,由于水平有限,可能無法對(duì)宋老師所講完全理解通透,如有錯(cuò)誤,請(qǐng)及時(shí)指證。
第五節(jié)課的內(nèi)容多且雜,其實(shí)完全可以合并到前四節(jié)課中。但考慮前四篇總結(jié)已經(jīng)完成,章節(jié)插入不方便,所以還是多寫一篇。
本文分成兩部分來論述
1、DMA與Cache一致性問題。
2、 常用的命令接口和文件接口簡(jiǎn)要說明。
DMA與Cache一致性問題
關(guān)于這一部分,宋老師的文章已經(jīng)講解的非常細(xì)致,我在寫也無非是畫蛇添足,所以此處只做簡(jiǎn)單總結(jié)。附上文章連接,http://mp.weixin.qq.com/s/5K7rlPXo2yIcoIXXgqqLfQ
而實(shí)際上,如果你不是在IC公司,大部分時(shí)候你只需要在驅(qū)動(dòng)程序中輕松敲下dma_alloc_coherent來獲取一片能確保DMA和cache一致性的內(nèi)存就可以了,具體實(shí)現(xiàn)細(xì)節(jié)對(duì)你來說可能并不重要。請(qǐng)看下圖:
A.DMA的內(nèi)存分配區(qū)域
不論是應(yīng)用程序還是驅(qū)動(dòng)程序,在獲取內(nèi)存時(shí)都需要獲得一片連續(xù)地址的內(nèi)存,但是由
于DMA設(shè)備訪問內(nèi)存不經(jīng)過MMU,所以也無法把不連續(xù)的物理地址映射為連續(xù)的虛擬地址,解決這個(gè)問題有兩種方式:
a.在CMA區(qū)域申請(qǐng)DMA內(nèi)存,因?yàn)镃MA本身是一片連續(xù)的物理內(nèi)存,CMA通常被分配在高端內(nèi)存,這個(gè)時(shí)候這片內(nèi)存會(huì)被映射到vmalloc映射區(qū),如果CMA在低端內(nèi)存,則不需要重新映射,因?yàn)榈投藘?nèi)存在開機(jī)時(shí)已經(jīng)與low Memory映射區(qū)建立了一一映射關(guān)系。
b.如果設(shè)備存在IOMMU,那么做DMA內(nèi)存分配時(shí)則不需要關(guān)心具體的內(nèi)存分配區(qū)域,IOMMU會(huì)讓設(shè)備看到一片連續(xù)的地址范圍,它的功能類似MMU,只不過MMU是把物理地址轉(zhuǎn)換為連續(xù)的虛擬地址供CPU使用,而IOMMU是把物理地址轉(zhuǎn)換為連續(xù)的總線地址供設(shè)備使用。
B.確保DMA和cache一致性的手段
確保DMA和cache一致性的手段有以下三種:
a.頁(yè)表設(shè)置uncache
要保證DMA和cache一致性最簡(jiǎn)單辦法,在申請(qǐng)到內(nèi)存后,修改對(duì)應(yīng)的頁(yè)表,將頁(yè)表的cache屬性改為uncache,這樣,當(dāng)CPU在訪問該片內(nèi)存時(shí)就不會(huì)從cache取數(shù)據(jù)。
b.硬件確保一致性
有的設(shè)備提供了確保一致性的硬件機(jī)制,這時(shí)我們申請(qǐng)內(nèi)存后則不需要修改頁(yè)表的cache屬性,一致性由硬件來保證。
c.代碼手動(dòng)同步
如果對(duì)應(yīng)的內(nèi)存區(qū)域已經(jīng)申請(qǐng)好了,設(shè)備直接使用,那么驅(qū)動(dòng)就無法更改對(duì)應(yīng)頁(yè)表的cache屬性,這時(shí)解決一致性的手段時(shí)在每次訪問這篇內(nèi)存前手動(dòng)同步cache內(nèi)容到內(nèi)存,同時(shí)禁止CPU對(duì)這片內(nèi)存的訪問,直到設(shè)備訪問完成。實(shí)際上下面提到的DMA streaming mapping就是通過這種方式實(shí)現(xiàn)的。
C.API接口
當(dāng)我們的驅(qū)動(dòng)自己獲取內(nèi)存,可以使用一致性DMA緩沖區(qū)API接口,就是
dam_alloc_coherent();如果對(duì)應(yīng)的內(nèi)存已經(jīng)被成功分配,我們?cè)谑褂们靶枰{(diào)用DMA流映射API接口,確保cache的內(nèi)容被成功flush到內(nèi)存,對(duì)應(yīng)的函數(shù)有dma_map_sg()和dma_map_single(),他們兩個(gè)的區(qū)別是,sg映射的內(nèi)存是分散/聚集的,分散在不同的位置,single映射的內(nèi)存是連續(xù)的一片內(nèi)存,通常是CMA。
常用的命令接口和文件接口簡(jiǎn)要說明
A.文件Dirty數(shù)據(jù)寫回配置接口
/proc/sys/vm/dirty_expire_centisecs:設(shè)置Dirty數(shù)據(jù)的寫回時(shí)間期限,超過這個(gè)時(shí)間,在flusher線程下次喚醒后,寫回這部分?jǐn)?shù)據(jù),單位是百分之一秒,厘秒。
/proc/sys/vm/dirty_writeback_centisecs:flusher線程周期性喚醒的時(shí)間,單位是厘秒,設(shè)置為0,表示禁止定期寫回。Flusher線程喚醒后會(huì)把超過期限的臟頁(yè)和進(jìn)程超過dirty_background_ratio值的臟頁(yè)寫回。
/proc/sys/vm/dirty_background_ratio:進(jìn)程持有的臟頁(yè)的個(gè)數(shù)閥值,單位是頁(yè),超過這個(gè)值,flusher線程在下次喚醒后會(huì)對(duì)臟頁(yè)進(jìn)行寫回。
/proc/sys/vm/dirty_ ratio:進(jìn)程持有的臟頁(yè)的個(gè)數(shù)閥值,單位是頁(yè),超過這個(gè)值,進(jìn)程delay,無法在進(jìn)行任何的寫操作,并且進(jìn)程自行完成臟頁(yè)的回寫。
B.Memory Cgroup的使用
控制group的最大使用內(nèi)存示例如下:
$:cd /sys/fs/cgroup/memory
$:mkdir A //創(chuàng)建一個(gè)分組
$:cd A/
$echo $((200*1024*1024)) >memory.limit_in_bytes //設(shè)置該組最大可使用內(nèi)存200M
$:cgexec –g memory:A ./a.out //將a.out添加到組A并執(zhí)行
C.內(nèi)存回收接口說明
在內(nèi)存管理四章節(jié)中提到內(nèi)存回收有三個(gè)水位,min,low和hight,當(dāng)內(nèi)存的水位達(dá)到low,說明內(nèi)存緊張,這時(shí)kswapd開始工作,在后臺(tái)慢慢回收內(nèi)存,直到水位達(dá)到high,當(dāng)系統(tǒng)內(nèi)存異常緊張時(shí),達(dá)到min水位,程序被堵住,Direct reclaim被觸發(fā)。具體相關(guān)接口如下:
/proc/zoneinfo //該文件可以查看到各個(gè)zone的情況,包括各個(gè)zone的三個(gè)水位設(shè)置
/proc/sys/vm/min_free_kbytes //可用于查看和設(shè)置min水位的值
其中l(wèi)ow水位和high水位沒有對(duì)應(yīng)的設(shè)置接口,是通過計(jì)算得來的。
Low = min*5/4
High = min*6/4
各個(gè)zone的水位標(biāo)準(zhǔn)是按總的水位標(biāo)準(zhǔn)等比例劃分的,比如normal zone是800M,內(nèi)存一共1G,min_free_kbytes被設(shè)置為30720,即30M,那么,normal zone的min水位就等于,800/1024 * 30720,就是24000
D.Swappiness接口
Swappiness越大,越傾向于回收匿名頁(yè);swappiness越小,越傾向于回收file-backed的頁(yè)面。當(dāng)然,它們的回收方法都是一樣的LRU算法。Swappiness的接口有兩個(gè),一個(gè)是cgroup里的swappiness,一個(gè)是/proc/sys/vm下的swappiness,他們的區(qū)別是作用域不同,cgroup里的swappiness只控制組內(nèi)程序的回收傾向,而/proc/sys/vm/swappiness控制當(dāng)前整個(gè)系統(tǒng),除了在cgroup被重新定義的程序。
E.Getdelays工具
要使用該工具需要打開內(nèi)核選項(xiàng)CONFIG_TASK_DELAY_ACCT和CONFIG_TASKSTATS。該工具的源文件在LinuxKernelSource/Documentation/accounting下。使用getdelays可以查看當(dāng)前系統(tǒng)或者某個(gè)進(jìn)程調(diào)度的延時(shí),IO的delay情況,swap和內(nèi)存回收的delay情況,幫助用戶查看程序的耗時(shí)情況。
F.Vmstat命令
該命令可周期性的查看swap in/out和block in/out的情況。
-
CMA
+關(guān)注
關(guān)注
0文章
27瀏覽量
9806 -
MMU
+關(guān)注
關(guān)注
0文章
91瀏覽量
18289 -
Buddy
+關(guān)注
關(guān)注
0文章
5瀏覽量
7443
原文標(biāo)題:陳延偉:任督二脈之內(nèi)存管理總結(jié)筆記
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論