程序與記憶體6-快取記憶體1(每CPU頁框快取記憶體和記憶體快取記憶體)
首先我提幾個主題:
磁碟快取記憶體、記憶體快取記憶體、硬體快取記憶體、每cpu頁框快取記憶體、頁快取記憶體、目錄項快取記憶體、索引節點快取記憶體、轉換後援緩衝器(TLB)、哈佛結構的快取記憶體、寫緩衝器、快取記憶體一致性、L1和L2等快取記憶體在驅動的使用。
上面這些就我在接下來的文章中要討論的東西。如果您對上面的東西瞭如指掌,真心希望您能給本屌絲指點指點。
快取記憶體大家喜歡叫cache。對於嵌入式的觀眾,第一反應可能是arm中常提到的dcache、icache。如果你意識到這個;恭喜你!你已經看見哈佛結構的快取記憶體了同時明白了硬體快取記憶體。對於這樣的認識,只是在硬體層,本身哈佛結構就是硬體層的話題。但是我的所有討論都是在linux環境下,那麼linux中提到的快取記憶體是什麼?(下面程式碼參考linux-3.2.8)
下面我們討論由軟體機制構造出的快取記憶體。(自備飲料、爆米花和3D眼鏡)
1.每CPU頁框快取記憶體。
核心經常請求和釋放單個頁框。為了提升系統性能,每個記憶體管理區定義了一個“每CPU”頁框快取記憶體。所有“每CPU”快取記憶體包含一些預先分配的頁框。
這個快取記憶體分為兩個快取,一個是熱快取記憶體,它存放的頁框中所包含的的內容很可能就在cpu硬體快取記憶體中。;另一個是冷快取記憶體。
對於這兩個的管理,很多文章說有兩個per_cpu_pages描述符組成。不過在我在linux-3.2.8中看到只用了一個,簡單看一下:
struct per_cpu_pages *pcp;
struct list_head *list;
//…省略程式碼
list =&pcp->lists[migratetype];
//…省略程式碼
if (cold)
page =list_entry(list->prev, struct page, lru);
else
page =list_entry(list->next, struct page, lru);
看一下這個描述符:
struct per_cpu_pages {
int count; /* number of pages in the list */
int high; /* high watermark, emptyingneeded */
int batch; /* chunk size for buddy add/remove*/
/* Lists of pages, oneper migrate type stored on the pcp-lists */
struct list_headlists[MIGRATE_PCPTYPES];
};
ULK的原話:
核心使用兩個位標來監視熱快取記憶體和冷快取記憶體的大小:如果頁個數低於下界low,核心通過buddy系統分配batch個單一頁面來補充對應的快取記憶體;否則,如果頁框個數高過上界high,核心從快取記憶體中釋放batch個頁框到buddy系統中。
你可以看到我的per_cpu_pages中更本就沒有low,之前看到一位有專家標籤的csdn博主,和我貼出一樣的struct per_cpu_pages卻直接引用了ULK的原話!!!
我們看看linux-3.2.8中是怎麼判斷的:
list =&pcp->lists[migratetype];
if(list_empty(list)) {
pcp->count += rmqueue_bulk(zone, 0,
pcp->batch, list,
migratetype, cold);
這樣就相當於曾經的low=0。
batch、high的值是初始化呼叫setup_pageset()計算的,和記憶體大小有關。
buffered_rmqueue()函式在指定的記憶體管理區中分配頁框。它使用每CPU頁框快取記憶體來處理單一頁框請求。記得之前的__alloc_pages()吧,它就存在一個過程:
alloc_pages()->alloc_pages_node()->__alloc_pages()->__alloc_pages_nodemask()->get_page_from_freelist()->buffered_rmqueue()
只貼出部分程式碼:
static inline
struct page*buffered_rmqueue(struct zone *preferred_zone,
struct zone *zone, intorder, gfp_t gfp_flags,
int migratetype)
{
unsigned long flags;
struct page *page;
int cold = !!(gfp_flags &__GFP_COLD);//冷熱判斷。
if (likely(order == 0)) {//上面提到:“核心經常請求和釋放單個頁框”,單個頁框請求是使用每CPU頁框快取記憶體的條件。
struct per_cpu_pages *pcp;
struct list_head *list;
local_irq_save(flags);
pcp =&this_cpu_ptr(zone->pageset)->pcp;
list =&pcp->lists[migratetype];
if (list_empty(list)) {//空就就要增加
pcp->count +=rmqueue_bulk(zone, 0,
pcp->batch, list,
migratetype, cold);
if(unlikely(list_empty(list)))
goto failed;
}
//下面根據冷熱取
if (cold)
page =list_entry(list->prev, struct page, lru);
else
page =list_entry(list->next, struct page, lru);
list_del(&page->lru);
pcp->count--;//減去取出的
} else {//這下面是呼叫__rmqueu()函式從夥伴系統中分配所請求的頁框
//…
page = __rmqueue(zone, order,migratetype);
//…
}
}
提速的根本:
如果pcp->lists[migratetype]不是NULL,那麼獲取頁框只是從這個list中獲取一個元素。而不用利用__rmqueue()去請求。
最後再說一句,每CPU頁框快取記憶體只限於單一頁框。
2.記憶體快取記憶體
在ldd3中我們就看到了後備快取的概率,linux核心的快取管理者有時稱為“slab分配器”,如果驅動中要頻繁請求記憶體,可以考慮使用這個slab分配器去做。slab分配器把那些 頁框儲存在快取記憶體中並很快地重新使用它們。
這個和上面說的每CPU頁框快取記憶體的目的都是繞過核心記憶體分配器。
每個快取記憶體的描述符是kmem_cache_t,kmem_cache_t對應多了slab,每個slab由一個或多了連續的頁框組成。
對於這個slab,在ldd3中或者網上有很多的應用示例,我就不多說了。