1. 程式人生 > >程序與記憶體6-快取記憶體1(每CPU頁框快取記憶體和記憶體快取記憶體)

程序與記憶體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中或者網上有很多的應用示例,我就不多說了。