0 CPU頁幀緩存概念
在前一節中,我們學習了buddy伙伴關系系統,它適用于申請連續的大塊物理內存;而有些時候,經常需要申請和釋放單個頁幀。但是,如果使用伙伴關系系統,需要查表、進行合并等操作,效率不高。為了提高性能,每個內存ZONE區都提供了一個per-CPU變量,CPU頁幀緩存。每個CPU頁幀緩存都包含一些預分配好的頁幀,滿足本地CPU發起的單個頁幀請求。
實際上,每個內存ZONE區和每個CPU都有2個緩存:一個是熱緩存,它存儲頁幀,其內容可能包含在CPU的硬件緩存中;另一個是冷緩存。
如果內核或用戶進程在分配后立即寫入頁幀,那么從熱緩存中獲取頁幀將有利于系統性能。實際上,每次訪問頁幀的某個內存位置,都會導致硬件Cache中替換其它頁幀的某一行(Cache-line),當然,除非硬件Cache已經包含剛剛訪問的“熱”頁幀中內存位置的一行。
相反,如果要用DMA操作填充頁幀,則從冷緩存中取頁幀是很方便的。在這種情況下,不涉及CPU,也不會修改硬件Cache的任何行。從冷緩存中取頁幀可以為其他類型的內存分配請求保留熱頁幀。
CPU頁幀緩存的數據結構是per_cpu_pageset類型的數組,其存儲在內存ZONE描述符中的pageset成員中,如下面的代碼所示:
structzone{ /*...*/ structper_cpu_pagesetpageset[NR_CPUS]; /*...*/ }
數組個數與CPU個數相關,其中的每個數組元素又包含2個per_cpu_pages描述符成員:一個是熱緩存;另一個是冷緩存。而per_cpu_pages數據類型的成員如下表所示:
structper_cpu_pages{ intcount;/*緩存中的頁幀數量*/ intlow;/*閾值下限,用于緩存補充*/ inthigh;/*閾值上限,需要清空緩存*/ intbatch;/*需從緩存中添加或減少的頁幀數*/ structlist_headlist;/*緩存中頁幀描述符列表,即內存頁列表*/ };
內核使用兩個閾值(low和high)監控冷/熱緩存的大?。喝绻搸瑪盗康陀陂撝?,則內核使用伙伴系統分配一定數量的單個頁幀(batch);否則,頁幀數量超過閾值上限,內核將緩存中的頁幀釋放到伙伴系統中(batch)。batch、low和high的值,具體依賴于內存ZONE區的頁幀數量。
1 通過CPU頁幀緩存分配頁幀
buffered_rmqueue()函數在給定的內存ZONE區中分配頁幀。它利用CPU頁幀緩存來處理單個頁幀請求。
Linux v2.6.11內核源碼實現如下所示(文件位置:/mm/page_alloc.c):
staticstructpage* buffered_rmqueue(structzone*zone,intorder,intgfp_flags) { unsignedlongflags; structpage*page=NULL; intcold=!!(gfp_flags&__GFP_COLD); if(order==0){ structper_cpu_pages*pcp; pcp=&zone->pageset[get_cpu()].pcp[cold]; local_irq_save(flags); if(pcp->count<=?pcp->low) pcp->count+=rmqueue_bulk(zone,0, pcp->batch,&pcp->list); if(pcp->count){ page=list_entry(pcp->list.next,structpage,lru); list_del(&page->lru); pcp->count--; } local_irq_restore(flags); put_cpu(); } if(page==NULL){ spin_lock_irqsave(&zone->lock,flags); page=__rmqueue(zone,order); spin_unlock_irqrestore(&zone->lock,flags); } if(page!=NULL){ BUG_ON(bad_range(zone,page)); mod_page_state_zone(zone,pgalloc,1<
輸入參數分別是內存ZONE區的描述符的地址(zone)、內存分配請求大?。?^order)和分配標志gfp_flags。如果在gfp_flags中設置了__GFP_COLD標志,則應從冷緩存中獲取頁幀,否則應從熱緩存中獲取頁幀(此標志僅對單個頁幀請求有意義)。該函數基本上執行以下操作:
如果order不等于0,則頁幀緩存不能使用,函數直接跳轉到第4步。
檢查由__GFP_COLD標志標識的內存ZONE區域的CPU緩存是否必須被補充(per_cpu_pages的count ≤ low)。在本例中,它執行以下子步驟:
重復調用__rmqueue()函數,從伙伴系統中分配batch個頁幀。
將分配的頁幀描述符插入到緩存的列表中。
更新count變量(將新分配的頁幀數量加上)。
如果count > 0,從緩存列表中取一個頁幀,然后跳轉到第5步。(CPU頁幀緩存可能是空的,在第2步的__rmqueue()沒有申請到頁幀時就會發生)
到這兒,如果內存請求沒有被滿足,調用__rmqueue()申請從伙伴系統中分配所請求頁幀。
如果內存請求被滿足,初始化該頁幀(第1個)的頁描述符:清除某些標志、設置private為0,設置頁幀引用計數器為1。另外,如果設置了__GPF_ZERO,將申請的內存清零。
返回頁幀(第1個)的描述符,失敗返回NULL。
2 通過CPU頁幀緩存釋放頁幀
從CPU頁幀緩存中釋放頁幀,使用free_hot_page()和free_cold_page()函數。它們都是free_hot_cold_page()的封裝函數,如下所示(文件位置:/mm/page_alloc.c):
staticvoidfastcallfree_hot_cold_page(structpage*page,intcold) { structzone*zone=page_zone(page); structper_cpu_pages*pcp; unsignedlongflags; arch_free_page(page,0); kernel_map_pages(page,1,0); inc_page_state(pgfree); if(PageAnon(page)) page->mapping=NULL; free_pages_check(__FUNCTION__,page); pcp=&zone->pageset[get_cpu()].pcp[cold]; local_irq_save(flags); if(pcp->count>=pcp->high) pcp->count-=free_pages_bulk(zone,pcp->batch,&pcp->list,0); list_add(&page->lru,&pcp->list); pcp->count++; local_irq_restore(flags); put_cpu(); }
free_hot_cold_page()接受的參數是待釋放頁幀的描述符地址page,表示熱緩存還是冷緩存的標志cold。
執行的步驟如下:
根據頁幀,獲取page->flags標志。
根據cold標志獲取對應頁幀緩存的描述符per_cpu_pages地址。
檢查緩存是否不足:如果count ≥ high,調用free_pages_bulk()函數。該函數會重復調用__free_pages_bulk()函數釋放指定的頁幀到伙伴系統中。
將該頁幀添加到緩存列表中,增加count計數。
應該注意的是,在Linux v2.6內核中,沒有任何頁幀被釋放到冷緩存中:內核總是假設釋放的頁幀相對于硬件緩存來說是熱的。當然,這并不意味著冷緩存是空的:當達到低閾值時,緩存由buffered_rmqueue()補充。
3 移除__GFP_COLD
雖然我們前邊分析了基于冷熱緩存的CPU頁幀緩存,但是,從v4.14版本以后的內核中已經移除,參考patch。Patches 1-4是與移除冷緩存最相關的部分;Patches 5-8是可選的,因為它們都是刪除無用但也不影響性能的代碼。
free_hot_cold_page的大多數調用者用戶都聲稱被釋放的頁是熱緩存的。唯一的例外是頁回收代碼,因為在不久的將來可能會釋放足夠多的頁,因此CPU的本地頁幀緩存列表將被回收,熱緩存信息將丟失。由于沒有人真正關心被釋放到分配器的頁的熱信息,所以省略該參數即可。
審核編輯:劉清
-
計數器
+關注
關注
32文章
2199瀏覽量
93303 -
Cache
+關注
關注
0文章
127瀏覽量
28067 -
LINUX內核
+關注
關注
1文章
312瀏覽量
21407
原文標題:Linux內核8.5-內存管理之CPU本地頁幀緩存
文章出處:【微信號:嵌入式ARM和Linux,微信公眾號:嵌入式ARM和Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論