20150222 imx257 linux記憶體空間記憶體配置設定
2015-02-22 李海沿
不知道為什麼,最近做夢總是夢見以前的事,以前的場景,可能是28号回學校的緣故吧!好了,不扯廢話了,前面我針對gpio按鍵這個實驗學習了中斷,信号量,定時器等核心實作,下面我們,使用以前的字元裝置模闆來寫一個linux記憶體空間記憶體配置設定的實驗。
一、kmalloc
kmalloc 是一個功能強大且高速(除非被阻塞)的工具,所配置設定到的記憶體在實體記憶體中連續且保持原有的資料(不清零)。原型:
#include <linux/slab.h>
void *kmalloc(size_t size, int flags);
參數詳解:
size 參數
核心管理系統的實體記憶體,實體記憶體隻能按頁面進行配置設定。kmalloc 和典型的使用者空間 malloc 在實際上有很大的差别,核心使用特殊的基于頁的配置設定技術,以最佳的方式利用系統 ram。linux 處理記憶體配置設定的方法:建立一系列記憶體對象集合,每個集合内的記憶體塊大小是固定。處理配置設定請求時,就直接在包含有足夠大記憶體塊的集合中傳遞一個整塊給請求 者。
必須注意的是:核心隻能配置設定一些預定義的、固定大小的位元組數組。kmalloc 能夠處理的最小記憶體塊是 32 或 64 位元組(體系結構依賴),而記憶體塊大小的上限随着體系和核心配置而變化。考慮到移植性,不應配置設定大于 128 kb的記憶體。若需多于幾個 kb的記憶體塊,最好使用其他方法。
flags 參數
記憶體配置設定最終總是調用 __get_free_pages 來進行實際的配置設定,這就是 gfp_ 字首的由來。
所有标志都定義在 <linux/gfp.h> ,有符号代表常常使用的标志組合。
主要的标志常被稱為配置設定優先級,包括:
gfp_kernel
最常用的标志,意思是這個配置設定代表運作在核心空間的程序進行。核心正常配置設定記憶體。當空閑記憶體較少時,可能進入休眠來等待一個頁面。目前程序休眠時, 核心會采取适當的動作來擷取空閑頁。是以使用 gfp_kernel 來配置設定記憶體的函數必須是可重入,且不能在原子上下文中運作。
gfp_atomic
核心通常會為原子性的配置設定預留一些空閑頁。當目前程序不能被置為睡眠時,應使用 gfp_atomic,這樣kmalloc 甚至能夠使用最後一個空閑頁。如果連這最後一個空閑頁也不存在,則配置設定傳回失敗。常用來從中斷處理和程序上下文之外的其他代碼中配置設定記憶體,從不睡眠。
gfp_user
用來為使用者空間配置設定記憶體頁,可能睡眠。
gfp_highuser
類似 gfp_user,如果有高端記憶體,就從高端記憶體配置設定。
gfp_noio
gfp_nofs
功能類似 gfp_kernel,但是為核心配置設定記憶體的工作增加了限制。具有gfp_nofs 的配置設定不允許執行任何檔案系統調用,而 gfp_noio 禁止任何 i/o 初始化。它們主要用在檔案系統和虛拟記憶體代碼。那裡允許配置設定休眠,但不應發生遞歸的檔案系統調。
linux 核心把記憶體分為 3 個區段: 可用于dma的記憶體(位于一個特别的位址範圍的記憶體, 外設可以在這裡進行 dma 存取)、正常記憶體和高端記憶體(為了通路(相對)大量的記憶體而存在的一種機制)。目的是使每中計算機平台都必須知道如何将自己特定的記憶體範圍歸類到這三個區 段中,而不是所有ram都一樣。
當要配置設定一個滿足kmalloc要求的新頁時, 核心會建立一個記憶體區段的清單以供搜尋。若指定了 __gfp_dma, 隻有可用于dma的記憶體區段被搜尋;若沒有指定特别的标志, 正常和 可用于dma的記憶體區段都被搜尋; 若 設定了 __gfp_highmem,所有的 3 個區段都被搜尋(注意:kmalloc 不能配置設定高端記憶體)。
記憶體區段背後的機制在 mm/page_alloc.c 中實作, 且區段的初始化時平台相關的, 通常在 arch 目錄樹的 mm/init.c中。
有的标志用雙下劃線做字首,他們可與上面标志"或"起來使用,以控制配置設定方式:
__gfp_dma
要求配置設定可用于dma的記憶體。
__gfp_highmem
配置設定的記憶體可以位于高端記憶體.
__gfp_cold
通常,配置設定器試圖傳回"緩存熱(cache warm)"頁面(可在處理器緩存中找到的頁面)。 而這個标志請求一個尚未使用的"冷"頁面。對于用作 dma 讀取的頁面配置設定,可使用此标志。因為此時頁面在處理器緩存中沒多大幫助。
__gfp_nowarn
當一個配置設定無法滿足,阻止核心發出警告(使用 printk )。
__gfp_high
高優先級請求,允許為緊急狀況消耗被核心保留的最後一些記憶體頁。
__gfp_repeat
__gfp_nofail
__gfp_noretry
告訴配置設定器當滿足一個配置設定有困難時,如何動作。__gfp_repeat 表示努力再嘗試一次,仍然可能失敗; __gfp_nofail 告訴配置設定器盡最大努力來滿足要求,始終不傳回失敗,不推薦使用; __gfp_noretry 告知配置設定器如果無法滿足請求,立即傳回。
前面知識點摘自: http://www.douban.com/note/56607778/
二、後備高速緩存 (lookaside cache)
核心中普通對象進行初始化所需的時間超過了對其進行配置設定和釋放所需的時間,是以不應該将記憶體釋放回一個全局的記憶體池,而是将記憶體保持為針對特定目而初始化的狀态。例如,如果記憶體被配置設定給了一個互斥鎖,那麼隻需在為互斥鎖首次配置設定記憶體時執行一次互斥鎖初始化函數(mutex_init)即可。後續的記憶體配置設定不需要執行這個初始化函數,因為從上次釋放和調用析構之後,它已經處于所需的狀态中了。
linux2.6中usb和scsi驅動程式使用了這種高速緩存,是為一些反複使用的塊增加某些特殊的記憶體池。後背高速緩存管理也叫slab配置設定器,相關函數和類型在<linux/slab.h>中申明。
slab配置設定器實作高速緩存具有kmem_cache_t類型。
kmem_cache_t * kmem_cache_create( const char *name, size_t size, size_t align,
unsigned long flags;
void (*constructor)(void*, kmem_cache_t *, unsigned long),
void (*destructor)(void*, kmem_cache_t *, unsigned long));
用于建立一個新的高速緩存對象。
constructor用于初始化新配置設定的對象,destructor用于清除對象。
一旦某個對象的高速緩存被建立以後,就可以調用kmem_cache_alloc從中配置設定記憶體對象。
void * kmem_cache_alloc(kmem_cache_t *cache,int flags);
釋放記憶體對象使用kmem_cache_free
void kmem_cache_free(kmem_cache_t *cache,const void *obj);
在記憶體空間都被釋放後,子產品被解除安裝前,驅動程式應當釋放他的高速緩存。
int kmem_cache_destory(kmem_cache_t *cache);
要檢查其傳回狀态,如果失敗,表明莫塊中發生了記憶體洩露。
基于slab的高速緩存scullc
kmem_cache_t *scullc_cache;
scullc_cache=kmem_cache_creat("scullc",scullc_quantum,0,slab_hwcache_align,null,null);
if(!scullc_cache)
{
scullc_cleanup();
return -enomem;
}
if(!dpte->data[s_pos])
dptr->data[s_pos]=kmem_cache_alloc(scullc_cache,gfp_kernel);
if(!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos],0,scullc_quantum);
for(i=0;i<qset;i++)
if(dptr->data[i])
kmem_cache_free(scullc_cache,dptr->data[i]);
if(scullc_cache)
kmem_cache_destory(scullc_cache);
三、記憶體池
核心中有些地方的記憶體配置設定是不允許失敗的,為確定能配置設定成功,核心建立一種稱為記憶體池的抽象,他試圖始終保持空閑狀态,以便緊急情況使用。
mempool_t * mempool_creat(int min_nr,
mempool_alloc_t *alloc_fn, //對象分配置設定 mempool_alloc_slab
mempool_free_t *free_fn, //釋放 mempool_free_slab
void *pool_data);
可以用如下代碼來構造記憶體池
cache=kmem_cache_creat(...); //建立一個高速緩存
pool=mempool_creat(my_pool_minimum,mempool_alloc_slab,mempool_free_slab,cache);//建立記憶體池對象
void *mempool_alloc(mempool_t *poll,int gfp_mask);//配置設定對象
void *mempool_free(void *element,mempool_t *poll);//釋放對象
void mempool_destroy(mempool_t *poll);//銷毀記憶體池
注意:mempool會配置設定一些記憶體塊,空閑且不會被用到,造成記憶體的大量浪費。是以一般情況不要用記憶體池。
四、執行個體分析
1.定義記憶體操作的指針
2.在init函數中,分别使用kmalloc,get_zeroed_page,vmalloc申請實體記憶體,整頁記憶體,虛拟記憶體
3.申請記憶體之後就是讀寫記憶體
4.當我們不用記憶體時,在exit函數中釋放記憶體,防止造成記憶體洩露
5.編譯測試
如圖所示,當我們insmod時,列印出了申請的記憶體的位址,并且成功的讀出了我們寫入的資料
附上驅動代碼:
view code