linux核心的記憶體管理中有一個2.6核心才加入的并不很張揚的結構體,那就是pagevec:
struct pagevec {
unsigned long nr;
unsigned long cold;
struct page *pages[14];
};
以往要加入到lru連結清單的page都要加入到這個pagevec了,并不再直接往lru中加入了。可是不加入lru的page就不會被記憶體管理機制所管理,是以僅僅這樣是不行的,除非給pagevec結構加上lru的功能,然而這又勢必會使這個結構體複雜化,再一個,這個結構體使用的最大範圍就是“每cpu”,更多的它都是局部使用的,這樣就使得鎖的粒度細化了很多,而lru則是全局的,設計pagevec的目的之一正是因為這個。
我們知道,雖然lru機制理論上很完美,并且對于記憶體管理還有很多理論上完美的機制,然而linux核心在運作時卻是有實際上的掃描時機的,與其說将頁面一個一個的單獨加入lru連結清單,不如使用懶惰的思想,在核心實際掃描lru的時候再加入。然而pagevec作為一個批量暫存的容器又不能過于龐大,應該将它調整為剛剛好的大小,關于此的解釋我想沒有比下面的解釋更好并且通俗的了:
instead of carrying water from the well to the house in a spoon, it is more efficient to carry it in a bucket. this is because of the constant time for walking and opening doors and getting exclusive access to the well. but the full bucket (i.e. the contents _and_ the bucket together) should have a good size to handle; together with nr and cold you have 16 pointer-sized members in the structure which fits nicely into the cache lines and is easy to calculate with (if you don't count thumbs and big toes (which may have a special use as carry flags) most humans have 16 fingers and toes altogether).
是這樣的,如果pagevec暫存的page數目過多,在vmscan的時候就會導緻負擔過重,并且pagevec本身的維護費用也會随之增加的,是以就剛好将之定為了2^4這麼大。
我們可以從shrink_active_list和shrink_inactive_list的最後看到,都會将掃描到的page加入一個局部變量pvec(struct pagevec pvec)中,這個舉動其實有兩個層次的含義,第一個含義請看這兩個函數的開始都有隔離連結清單的動作(isolate_lru_pages),這個動作的目的也是為了使鎖的粒度更小一些,否則在操作連結清單的時候就要鎖住全局的連結清單了。在隔離函數中我們看到遞增了page的引用計數:
對于active或者inactive連結清單的每一個page:
list_del(&page->lru);
get_page_unless_zero(page);
list_add(&page->lru, target);
這個get_page的操作意思是說,現在vmscan正在掃描這個頁面,每一個引用計數都代表了一條核心使用路徑。是以我們必須在另一個地方能進行一個UNisolate_lru_pages的操作,在這個函數中遞減page的引用計數,然而沒有這樣的函數,也不需要這樣的函數,pagevec相關的接口函數就能做到這一點,在這兩個shrink函數的最後,都有:
if (!pagevec_add(&pvec, page)) {
spin_unlock_irq(&zone->lru_lock);
__pagevec_release(&pvec);
spin_lock_irq(&zone->lru_lock);
}
其中加入到pvec的page一般是可以full的,是以__pagevec_release總能被調到
void __pagevec_release(struct pagevec *pvec)
{
lru_add_drain();
release_pages(pvec->pages, pagevec_count(pvec), pvec->cold);
pagevec_reinit(pvec);
即使它沒有被調用到,也就是page數量不足14,那麼函數結尾也要調用pagevec_release的。release函數中的第一行lru_add_drain就是将原先加入到“每cpu”各個pagevec的page們轉移到它們真正應該處于的相應的lru連結清單上,同時将當時它們加入pagevec時遞增的計數減下去(加入pagevec就說明有一條pagevec這個路徑在引用這個page),__pagevec_release的第二行就是shrink函數中pvec相應舉動的第二層意義,那就是批量地遞減在isolate中被遞增的引用計數。
shrink函數調用pagevec_add的真實目的其實就是為了最終調用__pagevec_release,這種代碼如果不從全局了解是很難弄懂的。還有一個很值得注意的地方就是shrink_inactive_list和shrink_active_list的傳回值的不同,掃描active傳回空,而掃描inactive傳回實際釋放的頁面數量,這也是合理的,因為掃描active連結清單的目的是為了補充inactive連結清單,也就是說參與記憶體管理的page們必然存在一個“被配置設定->active->inactive->free”的序列,隻有最終地在inactive連結清單中的頁面會被free,然而在shrink_active_list的最後由于pvec這個局部變量的舉動,可能真的會有一個page被釋放,為何不将它們的數量累加到釋放的數量呢,也就是傳回它們的數量呢?這是因為凡是這種方式被釋放的頁面都是在别的核心路徑被put_page的,由于它們在加入“每cpu”的pagevec的時候遞增了一個引用計數才導緻當時被别的路經put_page的時候沒有真的被free掉,是以它早就應該被free了,根本不是這次scan的功勞,它們由于一直暫時安全的呆在pagevec上,是以才活到了現在,它們被free的時刻應該是以前的某個時間(如果它們當初直接加在全局的lru而不是每cpu的pagevec上的話),是以它們當然是被直接的釋放而不發出任何通知。
總之,shrink_active的時候,其目的是将“這次”牽扯的page“釋放”到inactive連結清單,而隻有在shrink_inactive的時候才會将page通過shrink_page_list“釋放”到夥伴系統(廣義上的“釋放”)。pagevec其實本質上就是一個暫存容器,和上述的那段英文說明一樣,它就是一個桶,代替了湯勺
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271160