核心為裝置驅動提供了一個統一的記憶體管理介面,所以模組無需涉及分段和分頁等問題。 我已經在第一個scull模組中使用了 kmalloc 和 kfree 來分配和釋放記憶體空間。
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 告知分配器如果無法滿足請求,立即返回。
後備快取記憶體
核心為驅動程式常常需要反覆分配許多相同大小記憶體塊的情況,增加了一些特殊的記憶體池,稱為後備快取記憶體(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));
/*建立一個可以容納任意數目記憶體區域的、大小都相同的快取記憶體物件*/
引數*name: 一個指向 name 的指標,name和這個後備快取記憶體相關聯,功能是管理資訊以便追蹤問題;通常設定為被快取的結構型別的名字,不能包含空格。
引數size:每個記憶體區域的大小。
引數offset:頁內第一個物件的偏移量;用來確保被分配物件的特殊對齊,0 表示預設值。
引數flags:控制分配方式的位掩碼:
SLAB_NO_REAP 保護快取在系統查詢記憶體時不被削減,不推薦。
SLAB_HWCACHE_ALIGN 所有資料物件跟快取記憶體行對齊,平臺依賴,可能浪費記憶體。
SLAB_CACHE_DMA 每個資料物件在 DMA 記憶體區段分配.
其他標誌詳見 mm/slab.c。但是,通常這些標誌在只在開發系統中通過核心配置選項被全域性性地設定。
引數constructor 和 destructor 是可選函式(不能只有destructor,而沒有constructor ),用來初始化新分配的物件和在記憶體被作為整體釋放給系統之前“清理”物件。
constructor 函式在分配一組物件的記憶體時被呼叫,由於記憶體可能持有幾個物件,所以可能被多次呼叫。同理,destructor不是立刻在一個物件被釋放後呼叫,而可能在以後某個未知的時間中呼叫。 根據它們是否被傳遞 SLAB_CTOR_ATOMIC 標誌( CTOR 是 constructor 的縮寫),控制是否允許休眠。由於當被呼叫者是constructor函式時,slab 分配器會傳遞 SLAB_CTOR_CONSTRUCTOR 標誌。為了方便,它們可通過檢測這個標誌以使用同一函式。
(2)通過呼叫 kmem_cache_alloc 從已建立的後備快取記憶體中分配物件:
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
/*cache 引數是剛建立快取,flags 是和kmalloc 的相同*/
(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 的返回值:失敗指示模組存在記憶體洩漏*/
使用後備快取記憶體的一個好處是核心會統計後備快取記憶體的使用,統計情況可從 /proc/slabinfo 獲得。
記憶體池
為了確保在記憶體分配不允許失敗情況下成功分配記憶體,核心提供了稱為記憶體池( "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 和相關函式
如果一個模組需要分配大塊的記憶體,最好使用面向頁的分配技術。
__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 為底的對數。若其值過大(沒有這麼大的連續區可用), 則分配失敗*/
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;
}
使用方法舉例在Linux裝置驅動程式學習(7)-核心的資料型別 中有。
通過/proc/buddyinfo 可以知道系統中每個記憶體區段上的每個 order 下可獲得的資料塊數目。
當程式不需要頁面時,它可用下列函式之一來釋放它們。
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)
若試圖釋放和你分配的數目不等的頁面,會破壞記憶體對映關係,系統會出錯。
注意:只要符合和 kmalloc 的相同規則, __get_free_pages 和其他的函式可以在任何時候呼叫。這些函式可能失敗(特別當使用 GFP_ATOMIC 時),因此呼叫這些函式的程式必須提供分配失敗的處理。
從使用者的角度,可感覺到的區別主要是速度提高和更好的記憶體利用率(因為沒有內部的記憶體碎片)。但主要優勢實際不是速度, 而是更有效的記憶體利用。 __get_free_page 函式的最大優勢是獲得的頁完全屬於呼叫者, 且理論上可以適當的設定頁表將起合併成一個線性的區域。
alloc_pages 介面
struct page 是一個描述一個記憶體頁的內部核心結構,定義在<linux/Mm_types.h> 。
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_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變數使用如下巨集定義:
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);
更多細節請看核心原始碼中 Documentation/kbuild 下的檔案。
ARM9開發板實驗
這幾個實驗我都沒有用LDD3原配的程式碼,而是用以前的ioctl_and_lseek的程式碼改的。
(1)scullc 模組試驗
模組原始碼:scullc
測試程式碼:scullc_test
(2)scullp 模組試驗
模組原始碼:scullp
測試程式碼:scullp_test
(3)scullv 模組試驗
模組原始碼:scullv
實驗現象:
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullc.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 scullc c 252 0
[Tekkaman2440@SBC2440V4]#ls -l /tmp/test (注:test是普通的2070B的文字檔案)
-rw-r--r-- 1 root root 2070 Nov 24 2007 /
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 1 1 4020 1 1 : tunables 24 12 0 : slabdata 1 1 0 : globalstat 1 1 1 0 0 0 0 0 0 : cpustat 0 1 0 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edca00, qset at c3efe000
0: c3f56028
[Tekkaman2440@SBC2440V4]#/tmp/scullc_test
open scull !
scull_quantum=10 scull_qset=4
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 207 207 4020 1 1 : tunables 24 12 0 : slabdata 207 207 0 : globalstat 208 207 208 1 0 0 0 0 0 : cpustat 0 208 1 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcmem
Device 0: qset 4, q 10, sz 2070
item at c3edc930, qset at c3edc8c8
item at c3edc894, qset at c3edc860
item at c3edc82c, qset at c3edc7f8
item at c3edc7c4, qset at c3edc790
item at c3edc75c, qset at c3edc178
item at c3edc624, qset at c3edc4b8
item at c3edc9cc, qset at c3edc998
item at c3edc4ec, qset at c3edc964
item at c3edc484, qset at c3edccd8
item at c3edcca4, qset at c3edcc70
item at c3edcc3c, qset at c3edcc08
item at c3edcbd4, qset at c3edcba0
item at c3edcb6c, qset at c3edcb38
item at c3edcb04, qset at c3edcad0
item at c3edca9c, qset at c3edca68
item at c3edca34, qset at c3edca00
item at c3edc8fc, qset at c3edcfb0
item at c3edcf7c, qset at c3edcf48
item at c3edcf14, qset at c3edcee0
item at c3edceac, qset at c3edce78
item at c3edce44, qset at c3edce10
item at c3edcddc, qset at c3edcda8
item at c3edcd74, qset at c3edcd40
item at c3edcd0c, qset at c388a450
item at c388a41c, qset at c388a3e8
item at c388a3b4, qset at c388a380
item at c388a34c, qset at c388a318
item at c388a2e4, qset at c388a2b0
item at c388a27c, qset at c388a248
item at c388a214, qset at c388a1e0
item at c388a1ac, qset at c388a178
item at c388a144, qset at c388a790
item at c388a75c, qset at c388a728
item at c388a6f4, qset at c388a6c0
item at c388a68c, qset at c388a658
item at c388a624, qset at c388a5f0
item at c388a5bc, qset at c388a588
item at c388a554, qset at c388a520
item at c388a4ec, qset at c388a4b8
item at c388a484, qset at c388aad0
item at c388aa9c, qset at c388aa68
item at c388aa34, qset at c388aa00
item at c388a9cc, qset at c388a998
item at c388a964, qset at c388a930
item at c388a8fc, qset at c388a8c8
item at c388a894, qset at c388a860
item at c388a82c, qset at c388a7f8
item at c388a7c4, qset at c388ae10
item at c388addc, qset at c388ada8
item at c388ad74, qset at c388ad40
item at c388ad0c, qset at c388acd8
item at c388aca4, qset at c388ac70
0: c38fb028
1: c38fc028
2: c38fd028
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullp.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullp c 251 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc964, qset at c3eff000
0: c3f21000
[Tekkaman2440@SBC2440V4]#/tmp/scullp_test
open scull !
scull_quantum=1000 scull_qset=2
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullv.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
250 scullv
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullv c 250 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullv
[Tekkaman2440@SBC2440V4]#cat /proc/scullvseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc7c4, qset at c389d000
0: c485e000
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
從上面的資料可以看出: 在s3c2440 (ARM9)體系上, vmalloc返回的虛擬地址就在用於對映實體記憶體的地址上。
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 告知分配器如果無法滿足請求,立即返回。
後備快取記憶體
核心為驅動程式常常需要反覆分配許多相同大小記憶體塊的情況,增加了一些特殊的記憶體池,稱為後備快取記憶體(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));
/*建立一個可以容納任意數目記憶體區域的、大小都相同的快取記憶體物件*/
引數*name: 一個指向 name 的指標,name和這個後備快取記憶體相關聯,功能是管理資訊以便追蹤問題;通常設定為被快取的結構型別的名字,不能包含空格。
引數size:每個記憶體區域的大小。
引數offset:頁內第一個物件的偏移量;用來確保被分配物件的特殊對齊,0 表示預設值。
引數flags:控制分配方式的位掩碼:
SLAB_NO_REAP 保護快取在系統查詢記憶體時不被削減,不推薦。
SLAB_HWCACHE_ALIGN 所有資料物件跟快取記憶體行對齊,平臺依賴,可能浪費記憶體。
SLAB_CACHE_DMA 每個資料物件在 DMA 記憶體區段分配.
其他標誌詳見 mm/slab.c。但是,通常這些標誌在只在開發系統中通過核心配置選項被全域性性地設定。
引數constructor 和 destructor 是可選函式(不能只有destructor,而沒有constructor ),用來初始化新分配的物件和在記憶體被作為整體釋放給系統之前“清理”物件。
constructor 函式在分配一組物件的記憶體時被呼叫,由於記憶體可能持有幾個物件,所以可能被多次呼叫。同理,destructor不是立刻在一個物件被釋放後呼叫,而可能在以後某個未知的時間中呼叫。 根據它們是否被傳遞 SLAB_CTOR_ATOMIC 標誌( CTOR 是 constructor 的縮寫),控制是否允許休眠。由於當被呼叫者是constructor函式時,slab 分配器會傳遞 SLAB_CTOR_CONSTRUCTOR 標誌。為了方便,它們可通過檢測這個標誌以使用同一函式。
(2)通過呼叫 kmem_cache_alloc 從已建立的後備快取記憶體中分配物件:
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
/*cache 引數是剛建立快取,flags 是和kmalloc 的相同*/
(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 的返回值:失敗指示模組存在記憶體洩漏*/
使用後備快取記憶體的一個好處是核心會統計後備快取記憶體的使用,統計情況可從 /proc/slabinfo 獲得。
記憶體池
為了確保在記憶體分配不允許失敗情況下成功分配記憶體,核心提供了稱為記憶體池( "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 和相關函式
如果一個模組需要分配大塊的記憶體,最好使用面向頁的分配技術。
__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 為底的對數。若其值過大(沒有這麼大的連續區可用), 則分配失敗*/
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;
}
使用方法舉例在Linux裝置驅動程式學習(7)-核心的資料型別 中有。
通過/proc/buddyinfo 可以知道系統中每個記憶體區段上的每個 order 下可獲得的資料塊數目。
當程式不需要頁面時,它可用下列函式之一來釋放它們。
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)
若試圖釋放和你分配的數目不等的頁面,會破壞記憶體對映關係,系統會出錯。
注意:只要符合和 kmalloc 的相同規則, __get_free_pages 和其他的函式可以在任何時候呼叫。這些函式可能失敗(特別當使用 GFP_ATOMIC 時),因此呼叫這些函式的程式必須提供分配失敗的處理。
從使用者的角度,可感覺到的區別主要是速度提高和更好的記憶體利用率(因為沒有內部的記憶體碎片)。但主要優勢實際不是速度, 而是更有效的記憶體利用。 __get_free_page 函式的最大優勢是獲得的頁完全屬於呼叫者, 且理論上可以適當的設定頁表將起合併成一個線性的區域。
alloc_pages 介面
struct page 是一個描述一個記憶體頁的內部核心結構,定義在<linux/Mm_types.h> 。
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_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變數使用如下巨集定義:
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);
更多細節請看核心原始碼中 Documentation/kbuild 下的檔案。
ARM9開發板實驗
這幾個實驗我都沒有用LDD3原配的程式碼,而是用以前的ioctl_and_lseek的程式碼改的。
(1)scullc 模組試驗
模組原始碼:scullc
測試程式碼:scullc_test
(2)scullp 模組試驗
模組原始碼:scullp
測試程式碼:scullp_test
(3)scullv 模組試驗
模組原始碼:scullv
實驗現象:
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullc.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 scullc c 252 0
[Tekkaman2440@SBC2440V4]#ls -l /tmp/test (注:test是普通的2070B的文字檔案)
-rw-r--r-- 1 root root 2070 Nov 24 2007 /
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 1 1 4020 1 1 : tunables 24 12 0 : slabdata 1 1 0 : globalstat 1 1 1 0 0 0 0 0 0 : cpustat 0 1 0 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edca00, qset at c3efe000
0: c3f56028
[Tekkaman2440@SBC2440V4]#/tmp/scullc_test
open scull !
scull_quantum=10 scull_qset=4
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 207 207 4020 1 1 : tunables 24 12 0 : slabdata 207 207 0 : globalstat 208 207 208 1 0 0 0 0 0 : cpustat 0 208 1 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcmem
Device 0: qset 4, q 10, sz 2070
item at c3edc930, qset at c3edc8c8
item at c3edc894, qset at c3edc860
item at c3edc82c, qset at c3edc7f8
item at c3edc7c4, qset at c3edc790
item at c3edc75c, qset at c3edc178
item at c3edc624, qset at c3edc4b8
item at c3edc9cc, qset at c3edc998
item at c3edc4ec, qset at c3edc964
item at c3edc484, qset at c3edccd8
item at c3edcca4, qset at c3edcc70
item at c3edcc3c, qset at c3edcc08
item at c3edcbd4, qset at c3edcba0
item at c3edcb6c, qset at c3edcb38
item at c3edcb04, qset at c3edcad0
item at c3edca9c, qset at c3edca68
item at c3edca34, qset at c3edca00
item at c3edc8fc, qset at c3edcfb0
item at c3edcf7c, qset at c3edcf48
item at c3edcf14, qset at c3edcee0
item at c3edceac, qset at c3edce78
item at c3edce44, qset at c3edce10
item at c3edcddc, qset at c3edcda8
item at c3edcd74, qset at c3edcd40
item at c3edcd0c, qset at c388a450
item at c388a41c, qset at c388a3e8
item at c388a3b4, qset at c388a380
item at c388a34c, qset at c388a318
item at c388a2e4, qset at c388a2b0
item at c388a27c, qset at c388a248
item at c388a214, qset at c388a1e0
item at c388a1ac, qset at c388a178
item at c388a144, qset at c388a790
item at c388a75c, qset at c388a728
item at c388a6f4, qset at c388a6c0
item at c388a68c, qset at c388a658
item at c388a624, qset at c388a5f0
item at c388a5bc, qset at c388a588
item at c388a554, qset at c388a520
item at c388a4ec, qset at c388a4b8
item at c388a484, qset at c388aad0
item at c388aa9c, qset at c388aa68
item at c388aa34, qset at c388aa00
item at c388a9cc, qset at c388a998
item at c388a964, qset at c388a930
item at c388a8fc, qset at c388a8c8
item at c388a894, qset at c388a860
item at c388a82c, qset at c388a7f8
item at c388a7c4, qset at c388ae10
item at c388addc, qset at c388ada8
item at c388ad74, qset at c388ad40
item at c388ad0c, qset at c388acd8
item at c388aca4, qset at c388ac70
0: c38fb028
1: c38fc028
2: c38fd028
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullp.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullp c 251 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc964, qset at c3eff000
0: c3f21000
[Tekkaman2440@SBC2440V4]#/tmp/scullp_test
open scull !
scull_quantum=1000 scull_qset=2
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullv.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
250 scullv
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullv c 250 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullv
[Tekkaman2440@SBC2440V4]#cat /proc/scullvseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc7c4, qset at c389d000
0: c485e000
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
從上面的資料可以看出: 在s3c2440 (ARM9)體系上, vmalloc返回的虛擬地址就在用於對映實體記憶體的地址上。