?? (ldd) ch07-獲取內存(轉載).txt
字號:
注意在2.1版內核中vremap已經被重命名為ioremap。而且,Linux 2.1引入了一
個新的頭文件,<linux/vmalloc.h>,使用vmalloc時應將它包含進來。
與其他內存分配函數不同的是,vmalloc返回很“高”的地址值-這些地址要高
于物理內存的頂部。由于vmalloc對頁表調整后允許用連續的“高”地址訪問分配得到的
頁面,因此處理器是可以訪問返回得到的內存區域的。內核能和其他地址一樣地使用vma
lloc返回的地址,但程序中用到的這個地址與地址總線上的地址并不相同。
用vmalloc分配得到的地址是不能在微處理器之外使用的,因為它們只有在處理
器的分頁單元之上才有意義。但驅動程序需要真正的物理地址時(象外設用以驅動系統總
線的DMA地址),你就不能使用vmalloc了。正確使用vmalloc函數的場合是為軟件分配一
大塊連續的用于緩沖的內存區域。注意vmalloc的開銷要比__get_free_pages大,因為它
處理獲取內存還要建立頁表。因此,不值得用vmalloc函數只分配一頁的內存空間。
使用vmalloc函數的一個例子函數是create_module系統調用,它利用vmalloc函
數來獲取被創建模塊需要的內存空間。而在insmod調用重定位模塊代碼后,將會調用mem
cpy_fromfs函數把模塊本身拷貝進分配而得的空間內。
用vmalloc分配得到的內存空間用vfree函數來釋放,這就象要用kfree函數來釋
放kmalloc函數分配得到的內存空間。
和vmalloc一樣,vremap(或ioremap)也建立新的頁表,但和vmalloc不同的是,v
remap實際上并不分配內存。vremap的返回值是個虛擬地址,可以用來訪問指定的物理內
存區域;得到的這個虛擬地址最后要調用vfree來釋放掉。
vremap用于將高內存空間的PCI緩沖區映射到用戶空間。例如,如果VGA設備的幀緩沖區
被映射到地址0xf0000000(典型的一個值)后,vremap就可以建立正確的頁表讓處理機可
以訪問。而系統初始化時建立的頁表只是用于訪問低于物理地址空間的內存區域。系統
的初始化過程并不檢測PCI緩沖區,而是由各個驅動程序自己負責管理自己的緩沖區;PC
I的細節將在第15章“外設總線概貌”的“PCI接口”一節中討論。另外,你不必重映射
I的細節將在第15章“外設總線概貌”的“PCI接口”一節中討論。另外,你不必重映射
低于1MB的ISA內存區域,因為這段內存空間可用其他方法訪問,參見第8章“硬件管理”
的“訪問設備卡上的內存”一節。
如果你希望驅動程序能在不同的平臺間移植,那么使用vremap時就要小心。在一
些平臺上是不能直接將PCI內存區域映射到處理機的地址空間的,例如Alpha上就不行。
此時你就不能象普通內存區域那樣地對重映射區域進行訪問,你要用readb函數或者其他
一些I/O函數(可參見第8章的“1M內存空間之上的ISA內存”一節)。這套函數可以在不同
平臺間移植。
對vmalloc和vremap函數可分配的內存空間大小并沒有什么限制,但為了能檢測
到程序員的犯下的一些錯誤,vmalloc不允許分配超過物理內存大小的內存空間。但是記
著,vmalloc函數請求過多的內存空間會產生一些和調用kmalloc函數時相同的問題。
vremap和vmalloc函數都是面向頁的(它們都會修改頁表);因此分配或釋放的內
存空間實際上都會上調為最近的一個頁邊界。而且,vremap函數并不考慮如何重映射不
是頁邊界的物理地址。
是頁邊界的物理地址。
vmalloc函數的一個小缺點是它不能在中斷時間內使用,因為它的內部實現調用
了kmalloc(GFP_KERNEL)來獲取頁表的存儲空間。但這不是什么問題-如果__get_free_p
age函數都還不能滿足你的中斷處理程序的話,那你還是先修改一下你的軟件設計吧。
使用虛擬地址的scull: scullv
使用了vmalloc的示例程序是scullv模塊。正如scullp,這個模塊也是scull的一
個變種,只是使用了不同的分配函數來獲取設備用以儲存數據的內存空間。
該模塊每次分配16頁的內存(在Alpha上是128KB,x86上是64KB)。這里內存分配
用了較大的數據塊,目的是獲取比scullp更好的性能,并且表明此時使用其他可行的分
配技術相對來說會更耗時。用__get_free_pages函數來分配一頁以上的內存空間容易出
錯,而且即使成功了,也相對較慢。前面我們已經看到,用vmalloc分配若干頁比其他函
數要快一些,但由于存在建立頁表的開銷,只分配一頁時卻會慢一些。scullv設計得和s
cullp很相似。order參數指定分配的內存空間的“冪”,缺省為4。scullv和scullp的唯
一差別在下面一段代碼:
/* 此處用虛擬地址來分配一個單位內存 */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE <<order);
if (!dptr->data[s_pos])
return -ENOMEM;
/* 這段代碼釋放所有分配單元 */
for (i = 0; i < qset; i++)
if (dptr->data[i])
vfree (dptr->data[i]);
如果你在編譯這兩個模塊時都打開了調試開關,就可以通過讀它們在/proc下創建的文件
來查看它們進行的數據分配。下面的快照取自我的計算機,我機器的物理地址是從0到0x
1800000(共24MB):
morgana.root# cp /bin/cp /dev/scullp0
morgana.root# cat /proc/scullpmem
Device 0: qset 500, order 0, sz 19652
Item at 0063e598, qset at 006eb018
0: 150e000
1: de6000
2: 10ca000
3: e19000
3: e19000
4: bd1000
morgana.root# cp /zImage.last /dev/scullv0
morgana.root# cat /proc/scullvmem
Device 0: qset 500, order 4, sz 289840
Item at 0063ec98, qset at 00b3e810
0: 2034000
1: 2045000
2: 2056000
3: 2067000
4: 2078000
4: 2078000
從這些值可以看到,scullp分配物理地址(小于0x1800000),而scullv分配虛擬
地址(但注意實際數值與Linux 2.1會不同,因為虛擬地址空間的組織形式變了-見第17
章“近期發展”的“虛擬內存”一節)。
“臟”的處理方法(Playing Dirty)
如果你確實需要大量的連續的內存用作緩沖區,最簡單的(也是最不靈活的,但
也最容易出錯的)方法是在系統啟動時分配。顯然,模塊不能在啟動時分配內存;只有直
接連到內核的設備驅動程序才能運行這種“臟”的處理方式,在啟動時分配內存。
盡管在啟動時就進行分配似乎是獲得大量內存緩沖區的唯一方法,但我還會在第
13章的“分配DMA緩沖區”一節中介紹到另一種分配技術(雖然可能更不好)。在啟動時分
配緩沖區有點“臟”,因為它跳過了內核內存管理機制。而且,這種技術普通用戶無法
使用,因為它要修改內核。絕大多數用戶還是愿意裝載模塊,而并不愿意對內核打補定
或重新編譯內核。盡管我不推薦你使用這種“分配技術”,但它還是值得在此提及的,
因為在GFP_DMA被引入之前,這種技術曾是Linux的早期版本里分配可用于DAM傳輸的緩沖
區的唯一方法。
讓我們先看看啟動是是如何進行分配的。內核啟動時,它可以訪問系統所有的內
存空間。然后以空閑內存區域的邊界作為參數,調用內核的各個子系統的初始化函數進
行初始化。每個初始化函數都可以“偷取”一部分空閑區域,并返回新的空閑內存下界
。由于驅動程序是在系統啟動時進行內存分配的,所以可以從空閑RAM的線性數組獲取連
續的內存空間。
除了不能釋放得到的緩沖區,這種內存分配技術還有些缺點。驅動程序得到這些
內存頁后,就無法將它們再放到空閑頁面池中了;頁面池是在已經物理內存的分配結束
后才建立起來的,而且我也不推薦象這樣“黑客”內存管理的內部數據結構。但另一方
面,這種技術的優勢是,它可以獲取用于DMA傳輸等用途的一段連續區域。目前這也是分
配超過32頁的連續內存緩沖區的唯一的“安全”的方式,32頁這個值是源于get_free_pa
ges函數參數order可取的最大值為5。但是如果你需要的多個內存頁可以是物理上不連續
的,最好還是用vmalloc函數。
如果你真要在啟動時獲取內存的話,你必須修改內核代碼中的init/main.c文件
。關于main.c文件的更多細節可參見第16章和第8章的“1M內存空間之上的ISA內存”一
。關于main.c文件的更多細節可參見第16章和第8章的“1M內存空間之上的ISA內存”一
節。
注意,這種“分配”只能是按頁面大小的倍數進行,而頁面數不必是2的某個冪
次。
快速參考
與內存分配有關的函數和符號列在下面:
#include <linux/malloc.h>
void *kmalloc(unsigned int size, int priority);
void kfree(void *obj);
這兩個函數是最常用的內存分配函數。
#include <linux/mm.h>
GFP_KERNEL
GFP_ATOMIC
GFP_DMA
kmalloc函數的優先權。GFP_DMA是個標志位,可以與GFP_KERNEL和/或GFP_ATOMIC相或。
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);
這些都是面向頁的內存分配函數。以下劃線開頭的函數不清零分配而得的頁。只有前兩
這些都是面向頁的內存分配函數。以下劃線開頭的函數不清零分配而得的頁。只有前兩
個函數是在1.2版和2.0版的Linux間可移植的,而后兩者在1.2版與2.0版中的行為并不同
。
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
這些函數用于釋放面向頁的分配得到的內存空間。
void* vmalloc(unsigned long size);
void* vremap(unsigned long offset, unsigned long size);
void vfree(void* addr);
這些函數分配或釋放連續的虛擬地址空間。vremap用虛擬地址訪問物理內存(在2.1版的L
inux中被稱為ioremap),而vmalloc是用來分配空閑頁面。兩種情況下,都是用vfree來
釋放分配的內存頁。2.1版的Linux引入了頭文件<linux/vmalloc.h>,使用這些函數時必
void* vremap(unsigned long offset, unsigned long size);
void vfree(void* addr);
這些函數分配或釋放連續的虛擬地址空間。vremap用虛擬地址訪問物理內存(在2.1版的L
inux中被稱為ioremap),而vmalloc是用來分配空閑頁面。兩種情況下,都是用vfree來
釋放分配的內存頁。2.1版的Linux引入了頭文件<linux/vmalloc.h>,使用這些函數時必
須先包含(#include)這個頭文件。
--
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -