linux設備驅動程序學習(8) 分配內存

 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.實現過程如下:

kmem_cache_t *kmem_cache_create(const char *name, size_t size,size_t offset,
                  unsigned long flags,
       void (*constructor)(void *, kmem_cache_t *,unsigned long flags),

      void (*destructor)(void *, kmem_cache_t *, unsigned long flags));
/*創建一個可以容納任意數目內存區域的、大小都相同的高速緩存對象,這些區域的大小都相同,由size參數決定。*/

參數*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

mempool_t *mempool_create(int min_nr,

                           mempool_alloc_t *alloc_fn,
                           mempool_free_t *free_fn,
                           void *pool_data);

/*min_nr 參數是內存池應當一直保留的最小數量的分配對象*/

/*實際的分配和釋放對象由 alloc_fn 和 free_fn 處理,原型:*/
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
/*給 mempool_create 最後的參數 *pool_data 被傳遞給 alloc_fn 和 free_fn */

你可編寫特殊用途的函數來處理 mempool 的內存分配,但通常只需使用 slab 分配器爲你處理這個任務:mempool_alloc_slab 和 mempool_free_slab的原型和上述內存池分配原型匹配,並使用 kmem_cache_alloc 和 kmem_cache_free 處理內存的分配和釋放。

典型的設置內存池的代碼如下:

cache = kmem_cache_create(. . .);
pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);

(2)創建內存池後,分配和釋放對象:

void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);

 在創建mempool時,分配函數將被調用多次來創建預先分配的對象。因此,對 mempool_alloc 的調用是試圖用分配函數請求額外的對象,如果失敗,則返回預先分配的對象(如果存在)。用 mempool_free 釋放對象時,若預分配的對象數目小於最小量,就將它保留在池中,否則將它返回給系統。

可用一下函數重定義mempool預分配對象的數量:

int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
/*若成功,內存池至少有 new_min_nr 個對象*/

 

(3)若不再需要內存池,則返回給系統:

void mempool_destroy(mempool_t *pool);
/*在銷燬 mempool 之前,必須返回所有分配的對象,否則會產生 oops*/

 

get_free_page與相關函數

1.如果模塊需要分配大塊的內存,使用面向頁的分配技術會更好一些,就是整頁的分配。

__get_free_page(unsigned int flags);
/*返回一個指向新頁的指針, 未清零該頁*/

get_zeroed_page(unsigned int flags);
/*類似於__get_free_page,但用零填充該頁*/

__get_free_pages(unsigned int flags, unsigned int order);
/*分配是若干(物理連續的)頁面並返回指向該內存區域的第一個字節的指針,該內存區域未清零*/

/*參數flags 與 kmalloc 的用法相同;
參數order 是請求或釋放的頁數以 2 爲底的對數。若其值過大(沒有這麼大的連續區可用), 則分配失敗*/

2.get_order 函數可以用來從一個整數參數 size(必須是 2 的冪) 中提取 order,函數也很簡單:

/* Pure 2^n version of get_order */
static __inline__ __attribute_const__ int get_order(unsigned long size)
{
    int order;

    size = (size - 1) >> (PAGE_SHIFT - 1);
    order = -1;
    do {
        size >>= 1;
        order++;
    } while (size);
    return order;
}

3.通過/proc/buddyinfo 可以知道系統中每個內存區段上的每個 order 下可獲得的數據塊數目。

4.。當程序不需要頁面時,它可用下列函數之一來釋放它們。

void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);

它們的關係是:

#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask),0)

若試圖釋放和你分配的數目不等的頁面,會破壞內存映射關係,系統會出錯。

 

alloc_pages 接口

1.struct page 是一個描述一個內存頁的內部內核結構,定義在<linux/Mm_types.h>

2.

Linux 頁分配器的核心是稱爲 alloc_pages_node 的函數:

struct page *alloc_pages_node(int nid, unsigned int flags,
 unsigned int order);

/*以下是這個函數的 2 個變體(是簡單的宏):*/
struct page *alloc_pages(unsigned int flags, unsigned int order);
struct page *alloc_page(unsigned int flags);

/*他們的關係是:*/
#define alloc_pages(gfp_mask, order) \
        alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

參數nid 是要分配內存的 NUMA 節點 ID,
參數flags 是 GFP_ 分配標誌,
參數order 是分配內存的大小.
返回值是一個指向第一個(可能返回多個頁)page結構的指針, 失敗時返回NULL。

alloc_pages 通過在當前 NUMA 節點分配內存( 它使用 numa_node_id 的返回值作爲 nid 參數調用 alloc_pages_node)簡化了alloc_pages_node調用。alloc_pages 省略了 order 參數而只分配單個頁面。

釋放分配的頁:

void __free_page(struct page *page);
void __free_pages(struct page *page, unsigned int order);
void free_hot_page(struct page *page);
void free_cold_page(struct page *page);
/*若知道某個頁中的內容是否駐留在處理器高速緩存中,可以使用 free_hot_page (對於駐留在緩存中的頁) 或 free_cold_page(對於沒有駐留在緩存中的頁) 通知內核,幫助分配器優化內存使用*/

 

vmalloc 和 ioremap

vmalloc 是一個基本的 Linux 內存分配機制,它在虛擬內存空間分配一塊連續的內存區,儘管這些頁在物理內存中不連續 (使用一個單獨的 alloc_page 調用來獲得每個頁),但內核認爲它們地址是連續的。 應當注意的是:vmalloc 在大部分情況下不推薦使用。因爲在某些體系上留給 vmalloc 的地址空間相對小,且效率不高。函數原型如下:

#include <linux/vmalloc.h>
void *vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);


kmalloc 和 _get_free_pages 返回的內存地址也是虛擬地址,其實際值仍需 MMU 處理才能轉爲物理地址。vmalloc和它們在使用硬件上沒有不同,不同是在內核如何執行分配任務上:kmalloc 和 __get_free_pages 使用的(虛擬)地址範圍和物理內存是一對一映射的, 可能會偏移一個常量 PAGE_OFFSET 值,無需修改頁表。

而vmalloc 和 ioremap 使用的地址範圍完全是虛擬的,且每次分配都要通過適當地設置頁表來建立(虛擬)內存區域。 vmalloc 可獲得的地址在從 VMALLOC_STARTVAMLLOC_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變量使用如下宏定義:

DEFINE_PER_CPU(type, name);
/*若變量( name)是一個數組,則必須包含類型的維數信息,例如一個有 3 個整數的per-CPU 數組創建如下: */
DEFINE_PER_CPU(int[3], my_percpu_array);

雖然操作per-CPU變量幾乎不必使用鎖定機制。 但是必須記住 2.6 內核是可搶佔的,所以在修改一個per-CPU變量的臨界區中可能被搶佔。並且還要避免進程在對一個per-CPU變量訪問時被移動到另一個處理器上運行。所以必須顯式使用 get_cpu_var 宏來訪問當前處理器的變量副本, 並在結束後調用 put_cpu_var。 對 get_cpu_var 的調用返回一個當前處理器變量版本的 lvalue ,並且禁止搶佔。又因爲返回的是lvalue,所以可被直接賦值或操作。例如:

get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);

當要訪問另一個處理器的變量副本時, 使用:

per_cpu(variable, int cpu_id);

 

當代碼涉及到多處理器的per-CPU變量,就必須實現一個加鎖機制來保證訪問安全。
 
動態分配per-CPU變量方法如下:

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);/*需要一個特定對齊的情況下調用*/
void free_percpu(void *per_cpu_var); /* 將per-CPU 變量返回給系統*/

/*訪問動態分配的per-CPU變量通過 per_cpu_ptr 來完成,這個宏返回一個指向給定 cpu_id 版本的per_cpu_var變量的指針。若操作當前處理器版本的per-CPU變量,必須保證不能被切換出那個處理器:*/
per_cpu_ptr(void *per_cpu_var, int cpu_id);

/*通常使用 get_cpu 來阻止在使用per-CPU變量時被搶佔,典型代碼如下:*/

int cpu;
cpu = get_cpu()
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* work with ptr */
put_cpu();

/*當使用編譯時的per-CPU 變量, get_cpu_var 和 put_cpu_var 宏將處理這些細節。動態per-CPU變量需要更明確的保護*/


per-CPU變量可以導出給模塊, 但必須使用一個特殊的宏版本:

EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);


/*要在模塊中訪問這樣一個變量,聲明如下:*/
DECLARE_PER_CPU(type, name);

 
注意:在某些體系架構上,per-CPU變量的使用是受地址空間有限的。若在代碼中創建per-CPU變量, 應當儘量保持變量較小.

 

 

獲得大的緩衝區
大量連續內存緩衝的分配是容易失敗的。到目前止執行大 I/O 操作的最好方法是通過離散/聚集操作 。

在引導時獲得專用緩衝區
 
若真的需要大塊連續的內存作緩衝區,最好的方法是在引導時來請求內存來分配。在引導時分配是獲得大量連續內存頁(避開 __get_free_pages 對緩衝大小和固定顆粒雙重限制)的唯一方法。一個模塊無法在引導時分配內存,只有直接連接到內核的驅動纔可以。 而且這對普通用戶不是一個靈活的選擇,因爲這個機制只對連接到內核映象中的代碼纔可用。要安裝或替換使用這種分配方法的設備驅動,只能通過重新編譯內核並且重啓計算機。

當內核被引導, 它可以訪問系統種所有可用物理內存,接着通過調用子系統的初始化函數, 允許初始化代碼通過減少留給常規系統操作使用的 RAM 數量來分配私有內存緩衝給自己。
 
在引導時獲得專用緩衝區要通過調用下面函數進行:

#include <linux/bootmem.h>
/*分配不在頁面邊界上對齊的內存區*/
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size); /*分配非高端內存。希望分配到用於DMA操作的內存可能需要,因爲高端內存不總是支持DMA*/

/*分配整個頁*/
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);/*分配非高端內存*/

/*很少在啓動時釋放分配的內存,但肯定不能在之後取回它。注意:以這個方式釋放的部分頁不返回給系統*/
void free_bootmem(unsigned long addr, unsigned long size);

 

 

 

 

 

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章