核心中的記憶體分配通常通過kmalloc/kfree來進行,但是也有其它的方式來獲取記憶體,所有這些方式共同提供了核心中分配、釋放記憶體的介面。
類似於標準c中的malloc/free,kmalloc/kfree是核心中的用於常規記憶體分配的介面。
kmalloc/kfree是工作在slab分配器的基礎上的,在系統啟動時會呼叫kmem_cache_init,該函式會建立多個通用緩衝池,這些緩衝池中的slab物件的大小都是2的整數倍,並且依次增大,最小的大小為1《當通過kmalloc申請記憶體時,核心會根據所請求的大小來從通用緩衝池中選擇最合適的緩衝池進行記憶體分配,所謂的最合適就是大於等於申請大小的所有緩衝池中slab物件大小最小的那乙個。
在分配時可以指定一些標記來指定分配時的行為,最常用的是gfp_kernel,使用該標記時,可能會休眠,另外乙個是gfp_atomic,分配不會休眠,用於原子上下文。還有很多不同的標記,可以參考檔案「include/linux/gfp.h」該檔案包含了分配標記及其含義。
通常情況下,通用緩衝池是夠用的,但是對於有的核心部件來說,它可能需要反覆申請、釋放固定大小的記憶體塊,這時也可以選擇建立自己的專用緩衝池,然後從該專用池中進行申請、釋放。這涉及到三個api:
[cpp]view plain
copy
typedef
struct mempool_s mempool_t;
記憶體池的設計思想是:在建立記憶體池時,首先申請指定數目的記憶體物件,並將其儲存在記憶體池中,隨後在進行分配時,首先嘗試常規的分配,如果無法申請到記憶體,就從記憶體池預留的記憶體物件中取出乙個;在釋放記憶體時,如果記憶體池預留的記憶體物件數目小於指定的數目,則將要釋放的記憶體放入預留記憶體中而不做實際的釋放,如果記憶體池中預留記憶體物件的數目等於指定的數目則進行真正的釋放操作。
記憶體池所預留的記憶體實際上是被浪費了,因而最好不要用它。
mempool_t *mempool_create(int min_nr,mempool_alloc_t *alloc_fn, mempool_free_t*free_fn, void *pool_data)
該函式被用於建立記憶體池,在建立時會首先使用alloc_fn申請min_nr個記憶體物件,並將其儲存在記憶體池中。
不在使用的記憶體池可以用voidmempool_destroy(mempool_t *pool)來銷毀。
void *mempool_alloc(mempool_t *pool, intgfp_mask);
它用於使用標記gfp_mask來從記憶體池pool中申請記憶體,如果常規的申請失敗(即呼叫建立該記憶體池時提供的使用者自定義分配函式分配失敗),則從預留的記憶體物件中返回乙個。gfp_mask類似於kmalloc的flags。
void mempool_free(void *element, mempool_t*pool);
將記憶體element返回給記憶體池pool,如果pool中當前的預留記憶體物件數目小於記憶體池的min_nr,則記憶體被歸還懂啊記憶體池的預留記憶體物件中,否則呼叫真正的釋放函式(即呼叫建立該記憶體池時提供的使用者自定義釋放函式)
如果乙個核心部件需要大塊的記憶體,則可以使用面向頁面的技術(kmalloc對申請的最大大小有限制)
如果乙個模組需要分配大塊的記憶體, 它常常最好是使用乙個面向頁的技術
get_zeroed_page(gfp_t gfp_mask);
返回乙個指向新頁的指標並且用零填充了該頁.
__get_free_page(gfp_t gfp_mask);
類似於get_zeroed_page, 但是沒有清零該頁.
__get_free_pages(unsigned int gfp_mask,unsigned int order);
分配並返回乙個指向乙個記憶體區第乙個位元組的指標, 記憶體區可能是幾個(物理上連
續)頁長但是沒有清零。
order:為冪指數,即它指定分配多少個頁,比如order為0,表示分配2的0次冪即1頁。
gfp_mask:和kmalloc的flags相同
分配可能失敗,因而呼叫者必須處理失敗。
void free_page(unsigned long addr);
void free_pages(unsigned long addr,unsigned long order);
這兩個函式用於釋放頁,注意如果指定了order,則申請時和釋放時必須使用相同的值。需要注意的是這裡的api無法用於分配高階記憶體。
static inline struct page*alloc_pages(gfp_t gfp_mask, unsigned int order)
#define alloc_page(gfp_mask)alloc_pages(gfp_mask, 0)
這兩個函式也用於分配頁,但是它們返回的是乙個指向page資料結構的指標,而不是頁面的起始位址。gfp_mask類似於kmalloc的flags。order類似於__get_free_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);
這裡的api適用於高階記憶體。
vmalloc用於從虛擬記憶體空間分配一塊連續的記憶體區,儘管這些頁在物理記憶體中不連續 (使用alloc_page來獲得每個頁),但是核心將它們作為乙個連續的位址範圍來看待。
從 vmalloc 獲得的記憶體用起來稍微低效些,因此不建議使用它。另外由vmalloc返回的位址必須經過頁表才能找到真正的實體地址,因而如果核心部件需要使用真正的實體地址,則不能使用它來分配。
void *vmalloc(unsigned long size);用來申請記憶體
void vfree(void * addr);用來釋放用vmalloc申請的記憶體
vmalloc返回的是虛擬位址,但是實際上kmalloc和__get_free_pages及相關函式返回的也是虛擬位址,那麼為什麼vmalloc的效率很低,而其它兩個不低呢,這是因為雖然kmalloc和__get_free_pages及相關函式雖然也返回虛擬位址,但是它們所返回的虛擬位址是不同的。
vmalloc不能在原子上下文使用,因為它要建立頁表,因而需要使用kmalloc來為頁表來分配記憶體空間,這個過程可能會休眠。類似於vmalloc,使用ioremap 時也要建立新頁表,不同於 vmallocd的是它實際上不分配任何記憶體,ioremap 的返回值是乙個特殊的虛擬位址,該位址用於訪問特定的實體地址範圍。使用它獲取的位址要用iounmap 來釋放。對於ioremap返回的位址在使用時最好使用/io讀寫函式而不是直接訪問。
在核心啟動後,尤其是執行一段時間後,就很難通過上述方法來獲取大塊物理上連續的記憶體區域,因為使用上述方法可以獲取大塊連續的物理記憶體的方法就是呼叫__get_free_pages,但是在核心執行一段時間後,可能就很難找到大塊物理上連續的記憶體了。如果乙個核心部件確實需要大塊物理上連續的記憶體,那麼最好的方法是在啟動過程中就進行分配,然後保留給自己使用。
啟動過程中分配並保留記憶體的方法是呼叫如下api:
#include
void *alloc_bootmem(unsigned long size);
該函式用於分配指定大小的記憶體區域。
void *alloc_bootmem_low(unsigned long size);
該函式用於在低端位址區域分配指定大小的記憶體。低端位址區域指的是小於arch_low_address_limit的位址。
void *alloc_bootmem_pages(unsigned long size);
該函式用於分配指定大小的記憶體區域,但是分配的位址會對其到page上。
void *alloc_bootmem_low_pages(unsigned long size);
該函式用於在低端位址區域分配指定大小的記憶體。低端位址區域指的是小於arch_low_address_limit的位址。但是分配的位址會對其到page上。
需要注意的是這種分配是有限制的,即使用這種分配的**必須在系統啟動時就被載入執行,模組是不可能使用這種技術的。另外使用該技術分配的記憶體對記憶體管理子系統來說是不可見的,因而它會減少系統的可用記憶體。
使用該技術分配的記憶體可以用free_bootmem釋放,但是這種釋放並不能把記憶體釋放給記憶體管理子系統(除非你在記憶體管理子系統進行初始化之前將其歸還給了系統)。
除了使用bootmem之外,在較新的核心中還引入了一種新的機制來在啟動階段分配預留記憶體,這就是memblock。memblock這個部件在初始化階段會獲取系統的所有物理記憶體的資訊,並將它們分為兩類,常規記憶體和保留記憶體,在記憶體剛被發現時它都是常規記憶體,核心部件可以通過memblock_alloc這個api來申請並預留一片記憶體區域,通過memblock_free可以釋放相應的記憶體區域,它們的工作機制類似於bootmem。不同於bootmem的是:
bootmem需要在bootmem分配器初始化完成後才能呼叫,從**上來說,bootmem在start_kernel->setup_arch->do_init_bootmem中進行初始化,也就是直到這一步之後它才能使用。而memblock的初始化則在:early_setup->early_init_devtree->early_init_dt_scan_memory_ppc(ppc為例),在ppc中,early_setup是在start_kernel之前的。從兩者的功能上來說,也可以看出其先後,bootmem是乙個記憶體分配器,它本身就需要使用物理記憶體,而memblock則完成物理記憶體的檢測,因而bootmem可用的時間點必定在memblock之後
memblock用鍊錶來維護保留區域以及常規記憶體,而bootmem的管理則使用點陣圖來管理,相對而言memblock更靈活(bootmem每次分配進行搜尋時可能會從上一次分配結束的位址開始找到一塊滿足分配的連續區域,因而可能引入碎片,而且如果有大量分配,搜尋點陣圖也比較慢,這也是這個簡單的分配器的乙個弊端,具體的細節可以參考**)。
Linux核心記憶體分配
函式有 kmalloc get free page get free pages get free page vmalloc kmem cache kmem cache alloclinux記憶體分為3中,dma 可以直接訪問 normal memory,high memory 一般記憶體中,前16...
核心記憶體的分配
當使用者態程序需要額外記憶體的時候,可以從核心所維護的空閒頁幀鍊錶中獲取頁,該鍊錶通常由頁替換演算法來更新,這些頁幀通常分散在物理記憶體當中,如果使用者只需要乙個位元組的記憶體,那麼會產生記憶體碎片,這是因為程序會得到整個頁幀 核心記憶體的分配通常是從空閒記憶體池中獲取的,而並不是從滿足普通使用者模...
Linux核心記憶體分配機制
現象 1 壓力測試過程中,發現被測物件效能不夠理想,具體表現為 程序的系統態cpu消耗20,使用者態cpu消耗10,系統idle大約70 2 用ps o majflt,minflt c program命令檢視,發現majflt每秒增量為0,而minflt每秒增量大於10000。初步分析 majflt...