kmalloc函數
#include<linux/slab.h> void *kmalloc(size_t size,int flags); |
1.不會對所申請的內存清零,保留原有數據
2.參數:size:分配大小
flags:kmalloc行爲
3.flags:GFP_KERNEL :內核內存通常的分配方法,可能引起休眠
GFP_ATOMIC :用於在中斷處理例程或其它運行於進程上下文之外的代碼中分配內存,不會休眠
GFP_USER:用於爲用戶空間分配內存,可能會引起休眠
GFP_HIGHUSER:類似於GFP_USER,不過如果有高端內存的話就從那裏分配
GFP_NOIO:在GFP_KERNEL的基礎上,禁止任何I/O的初始化
GFG_NOFS:在GFP_KERNEL的基礎上,不允許執行任何文件系統的調用
另外有一些分配標誌與上述“或”起來使用
__GFP_DMA:
__GFP_HIGHMEM:
__GFP_COLD:
__GFP_NOWARN:
__GFP_HIGH:
__GFP_REPEAT:
__GFP_NOFAIL:
__GFP_NORETRY:
4.內存區段
linux通常把內存分成三個區段:
可用於DMA內存 | 存在於特別的地址範圍 |
常規內存 | |
高端內存 | 32位平臺爲訪問(相對)大量內存而存在的一種機制 |
5.size
linux處理內存分配:創建一系列的內存對象池,每個池中的內存塊大小是固定一致的。處理分配請求時,就直接在包含有足夠大的內存塊的池中傳遞一個整塊給請求者。
內核只能分配一些預定義的,固定大小的字節數組。若申請任意數量的內存,則得到的可能會多一些,最多可以得到申請數量的兩倍。
下限 | 32或者64,取決於當前體系 |
上限 | 128kb,使用的頁面的大小 |
後備高速緩存
驅動程序常常需要反覆分配許多相同大小內存塊的情況,增加了一些特殊的內存池,稱爲後備高速緩存(lookaside cache)。 設備驅動程序通常不會涉及後備高速緩存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驅動。Linux 內核的高速緩存管理器有時稱爲“slab 分配器”,相關函數和類型在 <linux/slab.h> 中聲明。slab 分配器實現的高速緩存具有 kmem_cache_t 類型。
1.實現過程如下:
|
參數*name: 一個指向 name 的指針,name和這個後備高速緩存相關聯,功能是管理信息以便追蹤問題;通常設置爲被緩存的結構類型的名字,不能包含空格。
參數size:每個內存區域的大小。
參數offset:頁內第一個對象的偏移量;用來確保被分配對象的特殊對齊,0 表示缺省值。
參數flags:控制分配方式的位掩碼:
- SLAB_NO_REAP 保護緩存在系統查找內存時不被削減,不推薦。
- SLAB_HWCACHE_ALIGN 所有數據對象跟高速緩存行對齊,平臺依賴,可能浪費內存。
- SLAB_CACHE_DMA 每個數據對象在 DMA 內存區段分配.。
2.一旦某個對象的高速緩存被創建,就可以調用kmem_cache_alloc從中分配內存對象:
viod *kmem_cache_alloc(kmem_cache_t *cache,int flags); cache是之前創建的高速緩存,flags和傳遞給kmalloc的相同,並且當需要分配更多的內存來滿足kmem_cache_alloc時,高速緩存還會利用這個參數 |
3.釋放一個內存對象,使用kmem_cache_free:
void kmem_cache_free(kmem_cache_t *cache,const void *obj); |
4.如果驅動程序代碼中和高速緩存有關的部分已經處理完了(典型情況:模塊被卸載的時候),這時驅動程序應該釋放它的高速緩存:
int kmem_cache_destroy(kmem_cache_t *cache); /*只在從這個緩存中分配的所有的對象都已返時才成功。因此,應檢查 kmem_cache_destroy 的返回值:失敗指示模塊存在內存泄漏*/ |
內存池
爲了確保在內存分配不允許失敗情況下成功分配內存,內核提供了稱爲內存池( "mempool" )的抽象,它其實是某種後備高速緩存。它爲了緊急情況下的使用,盡力一直保持空閒內存。所以使用時必須注意: mempool 會分配一些內存塊,使其空閒而不真正使用,所以容易消耗大量內存 。而且不要使用 mempool 處理可能失敗的分配。應避免在驅動代碼中使用 mempool。
內存池的類型爲 mempool_t ,在 <linux/mempool.h> ,使用方法如下:
1.創建mempool
/*min_nr 參數是內存池應當一直保留的最小數量的分配對象*/ /*實際的分配和釋放對象由 alloc_fn 和 free_fn 處理,原型:*/ |
你可編寫特殊用途的函數來處理 mempool 的內存分配,但通常只需使用 slab 分配器爲你處理這個任務:mempool_alloc_slab 和 mempool_free_slab的原型和上述內存池分配原型匹配,並使用 kmem_cache_alloc 和 kmem_cache_free 處理內存的分配和釋放。
典型的設置內存池的代碼如下:
|
(2)創建內存池後,分配和釋放對象:
|
可用一下函數重定義mempool預分配對象的數量:
|
(3)若不再需要內存池,則返回給系統:
|
get_free_page與相關函數
1.如果模塊需要分配大塊的內存,使用面向頁的分配技術會更好一些,就是整頁的分配。
|
2.get_order 函數可以用來從一個整數參數 size(必須是 2 的冪) 中提取 order,函數也很簡單:
|
3.通過/proc/buddyinfo 可以知道系統中每個內存區段上的每個 order 下可獲得的數據塊數目。
4.。當程序不需要頁面時,它可用下列函數之一來釋放它們。
它們的關係是:
|
alloc_pages 接口
1.struct page 是一個描述一個內存頁的內部內核結構,定義在<linux/Mm_types.h>
2.
Linux 頁分配器的核心是稱爲 alloc_pages_node 的函數:
|
參數nid 是要分配內存的 NUMA 節點 ID,
參數flags 是 GFP_ 分配標誌,
參數order 是分配內存的大小.
返回值是一個指向第一個(可能返回多個頁)page結構的指針, 失敗時返回NULL。
alloc_pages 通過在當前 NUMA 節點分配內存( 它使用 numa_node_id 的返回值作爲 nid 參數調用 alloc_pages_node)簡化了alloc_pages_node調用。alloc_pages 省略了 order 參數而只分配單個頁面。
釋放分配的頁:
|
vmalloc 和 ioremap
vmalloc 是一個基本的 Linux 內存分配機制,它在虛擬內存空間分配一塊連續的內存區,儘管這些頁在物理內存中不連續 (使用一個單獨的 alloc_page 調用來獲得每個頁),但內核認爲它們地址是連續的。 應當注意的是:vmalloc 在大部分情況下不推薦使用。因爲在某些體系上留給 vmalloc 的地址空間相對小,且效率不高。函數原型如下:
|
kmalloc 和 _get_free_pages 返回的內存地址也是虛擬地址,其實際值仍需 MMU 處理才能轉爲物理地址。vmalloc和它們在使用硬件上沒有不同,不同是在內核如何執行分配任務上:kmalloc 和 __get_free_pages 使用的(虛擬)地址範圍和物理內存是一對一映射的, 可能會偏移一個常量 PAGE_OFFSET 值,無需修改頁表。
而vmalloc 和 ioremap 使用的地址範圍完全是虛擬的,且每次分配都要通過適當地設置頁表來建立(虛擬)內存區域。 vmalloc 可獲得的地址在從 VMALLOC_START 到 VAMLLOC_END 的範圍中,定義在 <asm/patable.h> 中。vmalloc 分配的地址只在處理器的 MMU 之上纔有意義。當驅動需要真正的物理地址時,就不能使用 vmalloc。 調用 vmalloc 的正確場合是分配一個大的、只存在於軟件中的、用於緩存的內存區域時。注意:vamlloc 比 __get_free_pages 要更多開銷,因爲它必須即獲取內存又建立頁表。因此, 調用 vmalloc 來分配僅僅一頁是不值得的。vmalloc 的一個小的缺點在於它無法在原子上下文中使用。因爲它內部使用 kmalloc(GFP_KERNEL) 來獲取頁表的存儲空間,因此可能休眠。
ioremap 也要建立新頁表,但它實際上不分配任何內存,其返回值是一個特殊的虛擬地址可用來訪問特定的物理地址區域。
爲了保持可移植性,不應當像訪問內存指針一樣直接訪問由 ioremap 返回的地址,而應當始終使用 readb 和 其他 I/O 函數。
ioremap 和 vmalloc 是面向頁的(它們會修改頁表),重定位的或分配的空間都會被上調到最近的頁邊界。ioremap 通過將重映射的地址下調到頁邊界,並返回第一個重映射頁內的偏移量來模擬一個非對齊的映射。
per-CPU變量
per-CPU 變量是一個有趣的 2.6 內核特性,定義在 <linux/percpu.h> 中。當創建一個per-CPU變量,系統中每個處理器都會獲得該變量的副本。其優點是對per-CPU變量的訪問(幾乎)不需要加鎖,因爲每個處理器都使用自己的副本。per-CPU 變量也可存在於它們各自的處理器緩存中,這就在頻繁更新時帶來了更好性能
在編譯時間創建一個per-CPU變量使用如下宏定義:
|
雖然操作per-CPU變量幾乎不必使用鎖定機制。 但是必須記住 2.6 內核是可搶佔的,所以在修改一個per-CPU變量的臨界區中可能被搶佔。並且還要避免進程在對一個per-CPU變量訪問時被移動到另一個處理器上運行。所以必須顯式使用 get_cpu_var 宏來訪問當前處理器的變量副本, 並在結束後調用 put_cpu_var。 對 get_cpu_var 的調用返回一個當前處理器變量版本的 lvalue ,並且禁止搶佔。又因爲返回的是lvalue,所以可被直接賦值或操作。例如:
|
當要訪問另一個處理器的變量副本時, 使用:
|
|
per-CPU變量可以導出給模塊, 但必須使用一個特殊的宏版本:
|
獲得大的緩衝區
大量連續內存緩衝的分配是容易失敗的。到目前止執行大 I/O 操作的最好方法是通過離散/聚集操作 。
當內核被引導, 它可以訪問系統種所有可用物理內存,接着通過調用子系統的初始化函數, 允許初始化代碼通過減少留給常規系統操作使用的 RAM 數量來分配私有內存緩衝給自己。
|