文章目錄
- 1 伙伴算法
- 頁框操作
- alloc_pages()
- 2 slab
- slab機制要解決的問題
- 使用高速緩存
- 3 內存管理函數
- kmalloc
- kzalloc
- vmalloc
- vzalloc
- 區別
- 參考文章
內核使用struct page
結構體描述每個物理頁,也叫頁框。內核在很多情況下,需要申請連續的頁框,而且數量不定,比如4個、5個、9個等。如果頻繁地請求和釋放不同大小的一組連續的頁框,必然導致在已分配的塊內分散了許多小塊的空閑頁面,由此帶來的問題是,即使有足夠的空閑頁框可以滿足請求,但要分配一個大塊的連續頁框可能無法滿足請求。為了避免這種情況,Linux內核引入了伙伴算法。
1 伙伴算法
伙伴算法把所有的空閑頁框分為11個塊鏈表,每塊鏈表中分布包含特定的連續頁框內存空間,在第i條鏈表中,每個鏈表元素包含2的i次方個連續頁框。
假設要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閑塊,如果沒有,就去512個頁框的鏈表中找,找到了則將頁框塊分為2個256個頁框的塊,一個分配給應用,另外一個移到256個頁框的鏈表中。如果512個頁框的鏈表中仍沒有空閑塊,繼續向1024個頁框的鏈表查找,如果仍然沒有,則返回錯誤。頁框塊在釋放時,會主動將兩個連續的頁框塊合并為一個較大的頁框塊。
從上面可以知道Buddy算法一直在對頁框做拆開合并拆開合并的動作。Buddy算法牛逼就牛逼在運用了世界上任何正整數都可以由2^n的和組成。這也是Buddy算法管理空閑頁表的本質。
頁框操作
alloc_pages()
static inline struct page *
alloc_pages(unsigned int gfp_mask, unsigned int order);
該函數分配2的order次方個連續的頁框,并返回一個指針,該指針指向第一個頁page結構體,如果出錯,返回NULL。可以使用下面這個函數把給定的頁轉為它的邏輯地址:
void *page_address(struct page *page);
該函數返回一個指針,指向給定物理頁當前所在的邏輯地址。
其他頁框操作可以看這篇文章:https://blog.csdn.net/qq_41683305/article/details/123966721
2 slab
在Linux中,伙伴算法是以頁為單位管理和分配內存。但是現實的需求卻以字節為單位,假如我們需要申請20Bytes,總不能分配一頁吧!那此不是嚴重浪費內存。那么該如何分配呢?slab分配器就應運而生了,專為小內存分配而生。slab分配器分配內存以字節為單位。但是slab分配器并沒有脫離伙伴算法,而是基于伙伴算法分配的大內存進一步細分成小內存分配。
我們先來看一張圖:
kmem_cache是cache_chain上的一個元素,kmem_cache描述了一個高速緩存,每個高速緩存包含了一個slabs的列表,這通常是一段連續的內存塊。存在3種slab:
- slabs_full:slab都已經分配完
- slabs_partial:slab部分分配
- slab_empty:空slab或者沒有對象被分配。
kmem_cache高速緩存以緩存對象的大小來區分,所包含的三種slab都是鏈表,里面有一個或多個slab,每個slab由一個或多個連續的物理頁組成,在物理頁上保存的才是對象。
slab是slab分配器的最小單位,在實現上一個slab由一個或多個連續的物理頁組成(通常只有一頁)。單個slab可以在slab鏈表之間移動,例如如果一個半滿slab被分配了對象后變滿了,就要從slabs_partial中被刪除,同時插入到slabs_full中去。
slab機制要解決的問題
- 減少伙伴算法在分配小塊連續內存時所產生的內部碎片
- 將頻繁使用的對象緩存起來,減少分配、初始化和釋放對象的時間開銷
- 通過著色技術調整對象以更好的使用硬件高速緩存
使用高速緩存
一個新的高速緩存是通過以下函數創建的:
kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags,void (*ctor)(void *, kmem_cache_t *, unsigned long),void (*dtor)(void *, kmem_cache_t *, unsigned long));
第一個參數是字符串,存放著高速緩存的名字。第二個參數是高速緩存中每個元素的大小,第三個參數就是高速緩存內第一個對象的偏移,這用來確保在頁內進行特定的對齊,通常情況,0就可以滿足要求,也就是標準對齊。flags是可選的設置項,用來控制高速緩存的行為。
我們使用cat /proc/slabinfo可以看到系統所有的高速緩存:
創建高速緩存之后,就可以通過下列函數從中獲取對象:
void *kmem_cache_alloc(kmem_cache_t *cachep,int flags);
該函數從給定的高速緩存cachep中返回一個指向對象的指針。如果高速緩存的所有slab中都沒有空閑的對象,那么slab層必須通過kmem_getpages()獲取新的頁,flags的值傳遞給__get_free_pages()。
最后釋放一個對象,并把它返回給原先的slab,可以使用下面的函數:
void kmem_cache_free(kmem_cache_t *cachep,void *objp);
這樣就能把高速緩存cachep中的對象objp標記為空閑了。
要銷毀一個高速緩存,則調用:
int kmem_cache_destroy(kmem_cache_t *cachep);
同樣,也不能從中斷上下文中調用這個函數,因為它也可能會睡眠。調用該函數之前必須確保以下兩個條件:
- 高速緩存中的所有slab都必須為空
- 在調用kmem_cache_destroy()期間,不能再訪問這個高速緩存
3 內存管理函數
kmalloc
void *kmalloc(size_t size, int flags);
- size:是指要分配的內存的字節數。
- flags:是分配標志,它提供了多種kmalloc( )的行為
這個函數返回一個指向內存塊的指針,其內存塊至少要有size大小,所分配的內存區在物理上是連續的,在出錯時,它返回NULL,除非沒有足夠的內存可用,否則內核總能分配成功。在使用kmalloc()時,必須檢查返回值是不是NULL。
kzalloc
void *kzalloc(size_t size, int flags);
kzalloc的功能比kmalloc多了一步,會將申請到連續物理內存數據置為0
vmalloc
void *vmalloc(unsigned long size);
該函數返回一個指針,指向邏輯上連續的一塊內存區,其大小至少為size。在發生錯誤時,函數返回NULL。函數可能睡眠,因此,不能從中斷上下文中進行調用,也不能從其他不允許阻塞的情況下使用。
vzalloc
void *valloc(unsigned long size);
vzalloc比vmalloc步驟多了一步,將申請的邏輯地址連續的內存數據置為0。
區別
kmalloc和kzalloc申請的內存在物理上連續,vmalloc和vzalloc申請的內存在物理上不需要連續,它們在邏輯上連續。kmalloc和kzalloc申請的內存可由kfree函數釋放
void kfree(const void *ptr);
vmalloc和vzalloc申請的內存可由vfree函數釋放
void vfree(void *addr);
參考文章
https://zhuanlan.zhihu.com/p/36140017
https://www.cnblogs.com/cherishui/p/4246133.html