?? (ldd) ch07-獲取內(nèi)存(轉(zhuǎn)載).txt
字號(hào):
(LDD) Ch07-獲取內(nèi)存(轉(zhuǎn)載)
7章 獲取內(nèi)存
到目前為止,我們總是用kmalloc和kfree來進(jìn)行內(nèi)存分配。當(dāng)然,只用這些函數(shù)的確是
管理內(nèi)存的捷徑。本章將會(huì)介紹其他一些內(nèi)存分配技術(shù)。但我們目前并不關(guān)心不同的體
系結(jié)構(gòu)實(shí)際上是如何進(jìn)行內(nèi)存管理的。因?yàn)閮?nèi)核為設(shè)備驅(qū)動(dòng)程序提供了一致的接口,本
章的模塊都不必涉及分段,分頁等問題。另外,本章我也不會(huì)介紹內(nèi)存管理的內(nèi)部細(xì)節(jié)
,這些問題將留到第13章“Mmap和DMA”的“Linux的內(nèi)存管理”一節(jié)討論。
kmalloc函數(shù)的內(nèi)幕
kmalloc內(nèi)存分配引擎功能強(qiáng)大,由于和malloc函數(shù)很相似,很容易就可以學(xué)會(huì)
。這個(gè)函數(shù)運(yùn)行得很快-一除非它被阻塞-一它不清零它獲得的內(nèi)存空間;分配給它的
區(qū)域仍存放著原有的數(shù)據(jù)。在下面幾節(jié),我會(huì)詳細(xì)介紹kmalloc函數(shù),你可以將它和我后
面要介紹的一些內(nèi)存分配技術(shù)作個(gè)比較。
優(yōu)先權(quán)參數(shù)
kmalloc函數(shù)的第一個(gè)參數(shù)是size(大小),我留在下個(gè)小節(jié)介紹。第二個(gè)參數(shù),
是優(yōu)先權(quán),更有意思,因?yàn)樗鼤?huì)使得kmalloc函數(shù)在尋找空閑頁較困難時(shí)改變它的行為。
最常用的優(yōu)先權(quán)是GFP_KERNEL,它的意思是該內(nèi)存分配(內(nèi)部是通過調(diào)用get_fre
e_pages來實(shí)現(xiàn)的,所以名字中帶GFP)是由運(yùn)行在內(nèi)核態(tài)的進(jìn)程調(diào)用的。也就是說,調(diào)用
它的函數(shù)屬于某個(gè)進(jìn)程的,使用GFP_KERNEL優(yōu)先權(quán)允許kmalloc函數(shù)在系統(tǒng)空閑內(nèi)存低于
水平線min_free_pages時(shí)延遲分配函數(shù)的返回。當(dāng)空閑內(nèi)存太少時(shí),kmalloc函數(shù)會(huì)使當(dāng)
前進(jìn)程進(jìn)入睡眠,等待空閑頁的出現(xiàn)。
新的頁面可以通過以下幾種途徑獲得。一種方法是換出其他頁;因?yàn)閷?duì)換需要時(shí)
間,進(jìn)程會(huì)等待它完成,這時(shí)內(nèi)核可以調(diào)度執(zhí)行其他的任務(wù)。因此,每個(gè)調(diào)用kmalloc(G
FP_KERNEL)的內(nèi)核函數(shù)都應(yīng)該是可重入的。關(guān)于可重入的更多細(xì)節(jié)可見第5章“字符設(shè)備
驅(qū)動(dòng)程序的擴(kuò)展操作”的“編寫可重入的代碼”一節(jié)。
并非使用GFP_KERNEL優(yōu)先權(quán)后一定正確;有時(shí)kmalloc是在進(jìn)程上下文之外調(diào)用
并非使用GFP_KERNEL優(yōu)先權(quán)后一定正確;有時(shí)kmalloc是在進(jìn)程上下文之外調(diào)用
的-一比如,在中斷處理,任務(wù)隊(duì)列處理和內(nèi)核定時(shí)器處理時(shí)發(fā)生。這些情況下,curre
nt進(jìn)程就不應(yīng)該進(jìn)入睡眠,這時(shí)應(yīng)該就使用優(yōu)先權(quán)GFP_ATOMIC。原子性(atomic)的內(nèi)存
分配允許使用內(nèi)存的空閑位,而與min_free_pages值無關(guān)。實(shí)際上,這個(gè)最低水平線值
的存在就是為了能滿足原子性的請(qǐng)求。但由于內(nèi)核并不允許通過換出數(shù)據(jù)或縮減文件系
統(tǒng)緩沖區(qū)來滿足這種分配請(qǐng)求,所以必須還有一些真正可以獲得的空閑內(nèi)存。
為kmalloc還定義了其他一些優(yōu)先權(quán),但都不經(jīng)常使用,其中一些只在內(nèi)部的內(nèi)
存管理算法中使用。另一個(gè)值的注意的優(yōu)先權(quán)是GFP_NFS,它會(huì)使得NFS文件系統(tǒng)縮減空
閑列表到min_free_pages值以下。顯然,為使驅(qū)動(dòng)程序“更快”而用GFP_NFS優(yōu)先權(quán)取代
GFP_KERNEL優(yōu)先權(quán)會(huì)降低整個(gè)系統(tǒng)的性能。
除了這些常用的優(yōu)先權(quán),kmalloc還可以識(shí)別一個(gè)位域:GFP_DMA。GFP_DMA標(biāo)志
位要和GFP_KERNEL和GFP_ATOMIC優(yōu)先權(quán)一起使用來分配用于直接內(nèi)存訪問(DMA)的內(nèi)存頁
。我們將在第13章的“直接內(nèi)存訪問”一節(jié)討論如何使用這個(gè)標(biāo)志位。
size參數(shù)
系統(tǒng)物理內(nèi)存的管理是由內(nèi)核負(fù)責(zé)的,物理內(nèi)存只能按頁大小進(jìn)行分配。這就需
要一個(gè)面向頁的分配技術(shù)以取得計(jì)算機(jī)內(nèi)存管理上最大的靈活性。類似malloc函數(shù)的簡
要一個(gè)面向頁的分配技術(shù)以取得計(jì)算機(jī)內(nèi)存管理上最大的靈活性。類似malloc函數(shù)的簡
單的線性的分配技術(shù)不再有效了;在象Unix內(nèi)核這樣的面向頁的系統(tǒng)中內(nèi)存如果是線性
分配的就很難維護(hù)。空洞的處理很快就會(huì)成為一個(gè)問題,會(huì)導(dǎo)致內(nèi)存浪費(fèi),降低系統(tǒng)的
性能。
Linux是通過維護(hù)頁面池來處理kmalloc的分配要求的,這樣頁面就可以很容易地
放進(jìn)或者取出頁面池。為了能夠滿足超過PAGE_SIZE字節(jié)數(shù)大小的內(nèi)存分配請(qǐng)求,fs/kma
lloc.c文件維護(hù)頁面簇的列表。每個(gè)頁面簇都存放著連續(xù)若干頁,可用于DMA分配。在這
里我不介紹底層的實(shí)現(xiàn)細(xì)節(jié),因?yàn)閮?nèi)部的數(shù)據(jù)結(jié)構(gòu)可以在不影響分配語義和驅(qū)動(dòng)程序代
碼的前提下加以改變。事實(shí)上,2.1.38版已經(jīng)將kmalloc重新實(shí)現(xiàn)了。2.0版的內(nèi)存分配
實(shí)現(xiàn)代碼可以參見文件mm/malloc.c,而新版的實(shí)現(xiàn)在文件mm/slab.c中。想了解2.0版實(shí)
現(xiàn)的詳情可參見第16章“內(nèi)核代碼的物理布局”的“分配和釋放”一節(jié)。
Linux所使用的分配策略的最終方案是,內(nèi)核只能分配一些預(yù)定義的固定大小的
字節(jié)數(shù)組。如果你申請(qǐng)任意大小的內(nèi)存空間,那么很可能系統(tǒng)會(huì)多給你一點(diǎn)。
這些預(yù)定義的內(nèi)存大小一般“稍小于2的某次方”(而在更新的實(shí)現(xiàn)中系統(tǒng)管理的
這些預(yù)定義的內(nèi)存大小一般“稍小于2的某次方”(而在更新的實(shí)現(xiàn)中系統(tǒng)管理的
內(nèi)存大小恰好為2的各次方)。如果你能記住這一點(diǎn),就可以更有效地使用內(nèi)存了。例如
,如果在Linux 2.0上你需要一個(gè)2000字節(jié)左右的緩沖區(qū),你最好還是申請(qǐng)2000字節(jié),而
不要申請(qǐng)2048字節(jié)。在低與2.1.38版的內(nèi)核中,申請(qǐng)恰好是2的冪次的內(nèi)存空間是最糟糕
的情況了-內(nèi)核會(huì)分配兩倍于你申請(qǐng)空間大小的內(nèi)存給你。這也就是為什么在示例程序s
cull中每個(gè)單元(quantum)要用4000字節(jié)而不是4096字節(jié)的原因了。
你可以從文件mm/malloc.c(或者mm/slab.c)得到預(yù)定義的分配塊大小的確切數(shù)值
,但注意這些值可能在以后的版本中被改變。在當(dāng)前的2.0版和2.1版的內(nèi)核中,都可以
用個(gè)小技巧-盡量分配小于4K字節(jié)的內(nèi)存空間,但不能保證這種方法將來也是最優(yōu)的。
無論如何,Linux2.0中kmalloc函數(shù)可以分配的內(nèi)存空間最大不能超過32個(gè)頁-A
lpha上的256KB或者Intel和其他體系結(jié)構(gòu)上的128KB。2.1.38版和更新的內(nèi)核中這個(gè)上限
是128KB。如果你需要更多一些空間,那么有下面一些的更好的解決方法。
get_free_page和相關(guān)函數(shù)
如果模塊需要分配大塊的內(nèi)存,那使用面向頁的分配技術(shù)會(huì)更好。請(qǐng)求整頁還有
其他一些好處,后面第13章的“mmap設(shè)備驅(qū)動(dòng)程序操作”一節(jié)將會(huì)介紹。
分配頁面可使用下面一些函數(shù):
l get_free_page返回指向新頁面的指針并將頁面清零。
l __get_free_pages和get_free_page類似,但不清零頁面。
l __get_free_pages返回一個(gè)指向大小為幾個(gè)頁的內(nèi)存區(qū)域的第一個(gè)字節(jié)位置的
指針,但也不清零這段內(nèi)存區(qū)域。
l __get_dma_pages返回一個(gè)指向大小為幾個(gè)頁的內(nèi)存區(qū)域的第一個(gè)字節(jié)位置的
指針;這些頁面在物理上是連續(xù)的,可用于DMA傳輸。
這些函數(shù)的原型在Linux2.0中定義如下:
unsigned long get_free_page(int priority);
unsigned long __get_free_page(int priority);
unsigned long __get_dma_pages(int priority, unsigned long order);
unsigned long __get_free_pages(int priority, unsigned long order, int
dma);
實(shí)際上,除了__get_free_pages,這些函數(shù)或者是宏或者是最終調(diào)用了__get_free_page
s的內(nèi)聯(lián)函數(shù)。
當(dāng)程序使用完分配給它的頁面,就應(yīng)該調(diào)用下面的函數(shù)。下面的第一個(gè)函數(shù)是個(gè)宏,其
當(dāng)程序使用完分配給它的頁面,就應(yīng)該調(diào)用下面的函數(shù)。下面的第一個(gè)函數(shù)是個(gè)宏,其
中調(diào)用了第二個(gè)函數(shù):
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
如果你希望代碼在1.2版和2.0版的Linux上都能運(yùn)行,那最好還是不要直接使用函數(shù)__ge
t_free_pages,因?yàn)樗恼{(diào)用方式在這兩個(gè)版本間修改過2次。只使用函數(shù)get_free_pag
e(和__get_free_page)更安全更可移植,而且也足夠了。
至于DMA,由于PC平臺(tái)設(shè)計(jì)上的一些“特殊性”,要正確尋址ISA卡還有些問題。我在第1
3章的“直接內(nèi)存訪問”一節(jié)中介紹DMA時(shí),將只限于2.0版內(nèi)核上的實(shí)現(xiàn),以避免引入移
植方面的問題。
分配函數(shù)中的priority參數(shù)和kmalloc函數(shù)中含義是一樣的。__get_free_pages函數(shù)中的
dma參數(shù)是零或非零;如果不是零,那么對(duì)分配的頁面簇可以進(jìn)行DMA傳輸。order是你請(qǐng)
求分配或釋放的內(nèi)存空間相對(duì)2的冪次(即log2N)。例如,如果需要1頁,order為0;需要
8頁,order為3。如果order太大,分配就會(huì)失敗。如果你釋放的內(nèi)存空間大小和分配得
到的大小不同,那么有可能破壞內(nèi)存映射。在Linux目前的版本中,order最大為5(相當(dāng)
于32個(gè)頁)。總之,order越大,分配就越可能失敗。
這里值得強(qiáng)調(diào)的是,可以使用類似kmalloc函數(shù)中的priority參數(shù)調(diào)用get_free_pages和
其他這些函數(shù)。某些情況下內(nèi)存分配會(huì)失敗,最經(jīng)常的情形就是優(yōu)先權(quán)為GFP_ATOMIC的
時(shí)候。因此,調(diào)用這些函數(shù)的程序在分配出錯(cuò)時(shí)都應(yīng)提供相應(yīng)的的處理。
我們已經(jīng)說過,如果不怕冒險(xiǎn)的話,你可以假定按優(yōu)先權(quán)GFP_KERNEL調(diào)用kmalloc和底層
的get_free_pages函數(shù)都不會(huì)失敗。一般說來這是對(duì)的,但有時(shí)也未必:我那臺(tái)忠實(shí)可
靠的386,有著4MB的空閑的RAM,但當(dāng)我運(yùn)行一個(gè)"play-it-dangerous"(冒險(xiǎn))模塊時(shí)卻
象瘋了一樣。除非你有足夠的內(nèi)存,想寫個(gè)程序玩玩,否則我建議你總檢查檢查調(diào)用分
配函數(shù)的結(jié)果。
盡管kmalloc(GFP_KERNEL)在沒有空閑內(nèi)存時(shí)有時(shí)會(huì)失敗,但內(nèi)核總是盡可能滿足該內(nèi)存
分配請(qǐng)求。因此,如果分配太多內(nèi)存,系統(tǒng)的響應(yīng)性能很容易就會(huì)降下來。例如,如果
往scull設(shè)備寫入大量數(shù)據(jù),計(jì)算機(jī)可能就會(huì)死掉;為滿足kmalloc分配請(qǐng)求而換出內(nèi)存
頁,系統(tǒng)就會(huì)變得很慢。所有資源都被貪婪的設(shè)備所吞噬,計(jì)算機(jī)很快就變的無法使用
了;因?yàn)榇藭r(shí)已經(jīng)無法為你的shell生成新的進(jìn)程了。我沒有在scull模塊中提到這個(gè)問
題,因?yàn)樗皇莻€(gè)例子模塊,并不能真的在多用戶系統(tǒng)中使用。但作為一個(gè)編程者,你
必須要小心,因?yàn)槟K是特權(quán)代碼,會(huì)帶來系統(tǒng)的安全漏洞(比如說,很可能會(huì)造成DoS(
"denail-of-service")安全漏洞)。
使用一整頁的scull: scullp
至此,我們已經(jīng)較完全地介紹了內(nèi)存分配的原理,下面我會(huì)給出一些使用了頁面
分配技術(shù)的程序代碼。scullp是scull模塊的一個(gè)變種,它只實(shí)現(xiàn)了一個(gè)裸(bare)設(shè)備-
持久性的內(nèi)存區(qū)域。和scull不同,scullp使用頁面分配技術(shù)來獲取內(nèi)存;scullp_order
變量缺省為0,也可以在編譯時(shí)或裝載模塊時(shí)指定。在Linux 1.2上編譯的scullp設(shè)備在o
rder大于零時(shí)會(huì)據(jù)絕被加載,原因我們前面已經(jīng)說明過了。在Linux 1.2上,scullp模塊
只允許“安全的”單頁的分配函數(shù)。
盡管這是個(gè)實(shí)際的例子,但值的在這提到的只有兩行代碼,因?yàn)樵撛O(shè)備其實(shí)只是
分配和釋放函數(shù)略加改動(dòng)的scull設(shè)備。下面給出了分配和釋放頁面的代碼行及其相關(guān)的
分配和釋放函數(shù)略加改動(dòng)的scull設(shè)備。下面給出了分配和釋放頁面的代碼行及其相關(guān)的
上下文:
/* 此處分配一個(gè)單位內(nèi)存 */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = (void *)__get_free_pages(GFP_KERNEL,
dptr->order,0);
if (!dptr->data[s_pos])
return -ENOMEM;
memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
/* 這段代碼釋放所有分配單元 */
for (i = 0; i < qset; i++)
for (i = 0; i < qset; i++)
if (dptr->data[i])
free_pages((unsigned long)(dptr->data[i]),
dptr->order);
從用戶的角度看,可以感覺到的差異就是速度快了一些。我作了寫測(cè)試,把4M字
節(jié)的數(shù)據(jù)從scull0拷貝到scull1,然后再從scullp0拷貝到scullp1;結(jié)果表明內(nèi)核空間處
理器的使用率有所提高。
但性能提高的并不多,因?yàn)閗malloc設(shè)計(jì)得也運(yùn)行得很快。基于頁的分配策略的
優(yōu)點(diǎn)實(shí)際不在速度上,而是更有效地使用了內(nèi)存。按頁分配不會(huì)浪費(fèi)內(nèi)存空間,而用kma
lloc函數(shù)則會(huì)浪費(fèi)一定數(shù)量的內(nèi)存。事實(shí)上,你可能會(huì)回想起第5章的“所使用的數(shù)據(jù)結(jié)
構(gòu)”一節(jié)中我們已經(jīng)提到過select_table用了__get_free_page函數(shù)。
使用__get_free_page函數(shù)的最大優(yōu)點(diǎn)是這些分配得到頁面完全屬于你,而且在
理論上可以通過適當(dāng)?shù)卣{(diào)整頁表將它們合并成一個(gè)線性區(qū)域。結(jié)果就允許用戶進(jìn)程對(duì)這
理論上可以通過適當(dāng)?shù)卣{(diào)整頁表將它們合并成一個(gè)線性區(qū)域。結(jié)果就允許用戶進(jìn)程對(duì)這
些分配得到的不連續(xù)內(nèi)存區(qū)域進(jìn)行mmap。我將在第13章的“mmap設(shè)備驅(qū)動(dòng)程序操作”一
節(jié)中討論mmap調(diào)用和頁表的實(shí)現(xiàn)內(nèi)幕。
vmalloc和相關(guān)函數(shù)
下面要介紹的內(nèi)存分配函數(shù)是vmalloc,它分配虛擬地址空間的連續(xù)區(qū)域。盡管
這段區(qū)域在物理上可能是不連續(xù)的(要訪問其中的每個(gè)頁面都必須獨(dú)立地調(diào)用函數(shù)__get_
free_page),內(nèi)核卻認(rèn)為它們?cè)诘刂飞鲜沁B續(xù)的。分配的內(nèi)存空間被映射進(jìn)入內(nèi)核數(shù)據(jù)
段中,從用戶空間是不可見的-這一點(diǎn)上與其他分配技術(shù)不同。vmalloc發(fā)生錯(cuò)誤時(shí)返回
0(NULL地址),成功時(shí)返回一個(gè)指向一個(gè)大小為size的線性地址空間的指針。
該函數(shù)及其相關(guān)函數(shù)的原型如下:
void* vmalloc(unsigned long size);
void vfree(void* addr);
void* vremap(unsigned long offset, unsigned long size);
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -