內(nèi)核的內(nèi)存使用不像用戶空間那樣隨意,內(nèi)核的內(nèi)存出現(xiàn)錯誤時(shí)也只有靠自己來解決(用戶空間的內(nèi)存錯誤可以拋給內(nèi)核來解決)过吻。
所有內(nèi)核的內(nèi)存管理必須要簡潔而且高效疑故。
主要內(nèi)容
- 內(nèi)存的管理單元
- 獲取內(nèi)存的方法
- 獲取高端內(nèi)存
- 內(nèi)核內(nèi)存的分配方式
1. 頁
內(nèi)核把物理頁作為內(nèi)存管理的基本單位,內(nèi)存管理單元MMU以頁為單位進(jìn)行處理,從虛擬內(nèi)存的角度砚嘴,頁就是最小單位。
struct page {
unsigned long flags; /* 存放頁的狀態(tài),臟頁户敬?被鎖定?等各種狀態(tài)參見<linux/page-flags.h> */
atomic_t _count; /* 頁的引用計(jì)數(shù) */
atomic_t _mapcount; /* 已經(jīng)映射到mms的pte的個數(shù) *
unsigned long private; /* 此page作為私有數(shù)據(jù)時(shí)睁本,指向私有數(shù)據(jù) */
struct address_space *mapping; /* 此page作為頁緩存時(shí)尿庐,指向關(guān)聯(lián)的address_space */
pgoff_t index; /* Our offset within mapping. */
struct list_head lru; /* 將頁關(guān)聯(lián)起來的鏈表項(xiàng) */
void *virtual; /* 頁的虛擬地址 */
};
如果頁的大小是 8KB 的話,消耗的管理page內(nèi)存只有 20MB 左右呢堰。相對于 4GB 來說并不算很多抄瑟。
2. 區(qū)
頁是內(nèi)存管理的最小單元,但是并不是所有的頁對于內(nèi)核都一樣枉疼。
內(nèi)核將內(nèi)存按地址的順序分成了不同的區(qū)皮假,有的硬件只能訪問有專門的區(qū)鞋拟。
在x86(32位系統(tǒng))上
區(qū) | 描述 | 物理內(nèi)存 |
---|---|---|
ZONE_DMA | DMA使用的頁 | <16MB |
ZONE_NORMAL | 正常可尋址的頁 | 16~896MB |
ZONE_HIGHMEM | 動態(tài)映射的頁 | >896MB |
某些硬件只能直接訪問內(nèi)存地址惹资,不支持內(nèi)存映射贺纲,對于這些硬件內(nèi)核會分配 ZONE_DMA 區(qū)的內(nèi)存。
某些硬件的內(nèi)存尋址范圍很廣褪测,比虛擬尋址范圍還要大的多猴誊,那么就會用到 ZONE_HIGHMEM 區(qū)的內(nèi)存。
3. 獲得頁
方法 | 描述 |
---|---|
alloc_page(gfp_mask) | 只分配一頁侮措,返回指向頁結(jié)構(gòu)的指針 |
alloc_pages(gfp_mask, order) | 分配 2^order 個頁懈叹,返回指向第一頁頁結(jié)構(gòu)的指針 |
__get_free_page(gfp_mask) | 只分配一頁,返回指向其邏輯地址的指針 |
__get_free_pages(gfp_mask, order) | 分配 2^order 個頁分扎,返回指向第一頁邏輯地址的指針 |
get_zeroed_page(gfp_mask) | 只分配一頁澄成,讓其內(nèi)容填充為0,返回指向其邏輯地址的指針 |
alloc** 方法和 get** 方法的區(qū)別在于笆包,一個返回的是內(nèi)存的物理地址环揽,一個返回內(nèi)存物理地址映射后的邏輯地址。
如果無須直接操作物理頁結(jié)構(gòu)體的話庵佣,一般使用 get** 方法歉胶。
對應(yīng)的釋放函數(shù):
extern void __free_pages(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
extern void free_hot_page(struct page *page);
4. kmalloc()
獲得以字節(jié)為單位的一塊內(nèi)核內(nèi)存。
/**
* @size - 申請分配的字節(jié)數(shù)
* @flags - 上面討論的各種 gfp_mask
*/
static __always_inline void *kmalloc(size_t size, gfp_t flags)
#+end_src
vmalloc的定義在 mm/vmalloc.c 中
#+begin_src C
/**
* @size - 申請分配的字節(jié)數(shù)
*/
void *vmalloc(unsigned long size)
kmalloc 和 vmalloc 區(qū)別在于:
- kmalloc 分配的內(nèi)存物理地址是連續(xù)的巴粪,虛擬地址也是連續(xù)的
- vmalloc 分配的內(nèi)存物理地址是不連續(xù)的通今,虛擬地址是連續(xù)的
對應(yīng)的釋放方法:kfree, vfree
gfp_mask標(biāo)志
請求內(nèi)存時(shí),參數(shù)中有個標(biāo)志位肛根,控制分配內(nèi)存時(shí)必須遵守的一些規(guī)則辫塌。
- 行為標(biāo)志 :控制分配內(nèi)存時(shí),分配器的一些行為
- 區(qū)標(biāo)志 :控制內(nèi)存分配在那個區(qū)(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM 之類)
- 類型標(biāo)志 :由上面2種標(biāo)志組合而成的一些常用的場景
5. slab層實(shí)現(xiàn)原理
頻繁的分配/釋放內(nèi)存必然導(dǎo)致系統(tǒng)性能的下降派哲,所以有必要為頻繁分配/釋放的對象內(nèi)心建立緩存臼氨。
linux中的高速緩存是用所謂的slab層來實(shí)現(xiàn)的
- 可以在內(nèi)存中建立各種對象的高速緩存(比如進(jìn)程描述相關(guān)的結(jié)構(gòu) task_struct 的高速緩存)
- 除了針對特定對象的高速緩存以外,也有通用對象的高速緩存
- 每個高速緩存中包含多個 slab芭届,slab用于管理緩存的對象
- slab中包含多個緩存的對象储矩,物理上由一頁或多個連續(xù)的頁組成
高速緩存被劃分為slab,每個slab都包含一些對象成員(被緩存的數(shù)據(jù)結(jié)構(gòu))褂乍,slab處于三種狀態(tài)之一(滿持隧、部分滿、空)
每個高速緩存都使用kmem_cache結(jié)構(gòu)來表示逃片,包含三個鏈表:slabs_full屡拨、slabs_partial和slabs_empty
,這些鏈表包含高速緩存中的所有slab。
struct slab {
struct list_head list; /* 存放緩存對象呀狼,這個鏈表有 滿裂允,部分滿,空 3種狀態(tài) */
unsigned long colouroff; /* slab 著色的偏移量 */
void *s_mem; /* 在 slab 中的第一個對象 */
unsigned int inuse; /* slab 中已分配的對象數(shù) */
kmem_bufctl_t free; /* 第一個空閑對象(如果有的話) */
unsigned short nodeid; /* 應(yīng)該是在 NUMA 環(huán)境下使用 */
};
slab層的應(yīng)用主要有四個方法:
- 高速緩存的創(chuàng)建
- 從高速緩存中分配對象
- 向高速緩存釋放對象
- 高速緩存的銷毀
/**
* 創(chuàng)建高速緩存
* 參見文件: mm/slab.c
* 這個函數(shù)的注釋很詳細(xì)赠潦,這里就不多說了叫胖。
*/
struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,
unsigned long flags, void (*ctor)(void *))
/**
* 從高速緩存中分配對象也很簡單
* 函數(shù)參見文件:mm/slab.c
* @cachep - 指向高速緩存指針
* @flags - 之前討論的 gfp_mask 標(biāo)志,只有在高速緩存中所有slab都沒有空閑對象時(shí)她奥,
* 需要申請新的空間時(shí)瓮增,這個標(biāo)志才會起作用。
*
* 分配成功時(shí)哩俭,返回指向?qū)ο蟮闹羔? */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
/**
* 向高速緩存釋放對象
* @cachep - 指向高速緩存指針
* @objp - 要釋放的對象的指針
*/
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
/**
* 銷毀高速緩存
* @cachep - 指向高速緩存指針
*/
void kmem_cache_destroy(struct kmem_cache *cachep)
6. 在棧上的靜態(tài)分配
內(nèi)核空間的棧小而且固定
在x86體系結(jié)構(gòu)中绷跑,內(nèi)核棧的大小一般就是1頁或2頁,即 4KB ~ 8KB
內(nèi)核椃沧剩可以在編譯內(nèi)核時(shí)通過配置選項(xiàng)將內(nèi)核棧配置為1頁砸捏,配置為1頁的好處是分配時(shí)比較簡單,只有一頁隙赁,不存在內(nèi)存碎片的情況垦藏,因?yàn)橐豁撌潜揪褪欠峙涞淖钚挝弧?/p>
當(dāng)有中斷發(fā)生時(shí),如果共享內(nèi)核棧伞访,中斷程序和被中斷程序共享一個內(nèi)核棧會可能導(dǎo)致空間不足掂骏,
于是,每個進(jìn)程除了有個內(nèi)核棧之外厚掷,還有一個中斷棧弟灼,中斷棧一般也就1頁大小。
7. 高端內(nèi)存的映射
x86體系中冒黑,高于896MB的所有物理內(nèi)存的范圍大都是高端呢村田绑,不會永久或自動地映射到內(nèi)核地址空間。
- 永久映射:void kmap(struct page page)
- 臨時(shí)映射:void *kmap_atomic(struct page *page, enum km_type type)
8. 按CPU分配
按CPU來分配數(shù)據(jù)主要有2個優(yōu)點(diǎn):
- 最直接的效果就是減少了對數(shù)據(jù)的鎖抡爹,提高了系統(tǒng)的性能
- 由于每個CPU有自己的數(shù)據(jù)掩驱,所以處理器切換時(shí)可以大大減少緩存失效的幾率
如果一個處理器操作某個數(shù)據(jù),而這個數(shù)據(jù)在另一個處理器的緩存中時(shí)冬竟,那么存放這個數(shù)據(jù)的那個處理器必須清理或刷新自己的緩存欧穴。持續(xù)的緩存失效稱為緩存抖動,對系統(tǒng)性能影響很大诱咏。
percpu接口:
- 編譯時(shí)分配
- 運(yùn)行時(shí)分配,通過指針