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