date: 2014-10-07 19:09node
內核能夠經過alloc_pages函數來進行內存頁面的分配,函數的定義在mm.h文件中:linux
<include/linux/mm.h>
#ifndef CONFIG_DISCONTIGMEM
static inline struct page * alloc_pages(int gfp_mask, unsigned long order)
{
/*
* Gets optimized away by the compiler.
*/
if (order >= MAX_ORDER)
return NULL;
return __alloc_pages(contig_page_data.node_zonelists+(gfp_mask), order);
}
#else /* !CONFIG_DISCONTIGMEM */
extern struct page * alloc_pages(int gfp_mask, unsigned long order);
#endif /* !CONFIG_DISCONTIGMEM */
看來有兩個版本的alloc_pages,另外一個版本的實如今numa.c,編譯時根據編譯選項CONFIG_DISCONTIGMEM進行取捨。爲何要有這個取捨?這與以前講到的NUMA相關。但這裏的宏開關卻不是CONFIG_NUMA而是CONFIG_DISCONTIGMEM,表示「非連續存儲空間」。其實非連續存儲空間是一種廣義的NUMA,由於那表示在最低物理地址到最高物理地址之間存在空洞,既然存在空洞,質地固然是非均勻的了,從而也就要劃分出若干質地均勻(地址連續)的「節點」了,從而也就有一個pg_data_t結構隊列。數組
本文主要分析「連續存儲空間」中的內存頁面分配,這種狀況下只有一個節點,意即只有一個pg_data_t結構contig_page_data。pg_data_t結構中的node_zonelists是一個數組,表示全部的分配策略,這裏參數gfp_mask被用做數組下標;參數order表示要分配的連續的頁面個數爲2^order個。alloc_pages函數檢查order以後調用__alloc_pages(內核中以「__」開頭的函數表示僅供模塊內部使用),下面主要梳理下__alloc_pages的流程。函數
![物理頁面分配流程 物理頁面分配流程](http://static.javashuo.com/static/loading.gif)
按流程順序說明以下:操作系統
- 若是申請分配一個頁面(由於不活躍乾淨頁面隊列中的頁面是零散的不連續的,因此只有在申請一個頁面時容許從該隊列中分配),而分配策略要求等待分配完成,且當前進程不是「內存分配工做者」進程(即當前進程沒有設置PF_MEMALLOC標誌),那麼萬不得已時,能夠從內存管理區中的不活躍乾淨隊列中分配。
- 進程的PF_MEMALLOC標誌。若是進程的task_struct結構中的flag字段設置了PF_MEMALLOC,那麼表示該進程爲「內存分配工做者」,通常是內核進程kswapd和kreclaimd。若是這兩個進程要分配內存頁面,那是爲了執行公務,是爲了使得其餘進程更好地分配內存頁面,因此它們比通常的進程要有更高的待遇。圖中的step7就是在內存極端緊缺的狀況下,試圖知足這種進程的內存分配需求。
- rmqueue用來在管理區的空閒隊列中分配內存頁面。前面講過,爲了減小內存碎片,管理區的空閒列表是按連續頁面個數分開存放的。rmqueue在分配頁面時,先根據申請的頁面大小去相應的隊列中找空閒的頁面。若是相應的隊列中沒有可用的頁面,則去更高一檔的隊列中去找。找到之後,將一半分配出去,將另外一半鏈到低檔的隊列中。
- 要是分配策略中全部的管理區都分配失敗,則要加大力度再試了。一方面將管理區中的不活躍乾淨頁面考慮進去,一方面下降水位的要求。這就是__alloc_pages_limit函數所作的事情。先以參數「PAGES_HIGH」調用__alloc_pages_limit,若是還不行就再狠一點,以參數「PAGES_LOW」調用__alloc_pages_limit再試一次。
- 在__alloc_pages_limit 函數中會調用reclaim_page函數,後者直接從管理區的inactive_clean_list 隊列中回收頁面。
- 若是仍然不成功,則喚醒守護kswapd,讓它設法換出一些頁面。若是分配策略設置了__GFP_WAIT標誌,表示本次分配志在必得,不成功毋寧等。當前進程主動讓出CPU,並執行一次調度。這樣,一來,kswapd有可能當即調度執行;二來即便其餘的進程被調度運行,其餘進程也可能釋放出一些頁面。噹噹前進程再次被調度運行後,或者分配策略代表不容許等待時,以參數「PAGES_MIN」在調一次__alloc_pages_limit,內核已經有了「破釜沉舟」的決心了。
- 若是仍是分配不成功,那就要看當前進程是普通進程仍是「內存分配工做者」。對於普通進程,失敗的緣由可能有兩個。
- 緣由之一,可分配頁面總數還不少,可是比較零散,不知足申請連續頁面的要求。這種狀況下,能夠將不活躍髒頁面洗淨(經過page_launder函數),使得這些頁面轉入內存管理區的不活躍乾淨頁面隊列。對於每一個管理區,調用reclaim_page從不活躍乾淨隊列中回收一個頁面,再調用__free_page釋放頁面並試圖將空閒頁面拼湊成儘量大的頁面塊,以後再調用rmqueue再試圖從管理區的空閒隊列中分配。值得注意的是,在page_launder執行期間把當前進程的PF_MEMALLOC標誌置1,使其有了「執行公務」的特權,執行完成以後,再將其恢復成0。爲何這樣作呢?由於在page_launder中也會要求分配一些臨時性的工做頁面,不提升它的權限的話,就有可能遞歸執行到這裏。
- 緣由之二,系統中的內存真的不足了。這種狀況下,有兩種處理:若是分配策略中同時設置了__GFP_WAIT和__GFP_IO,則喚醒kswapd,當前進程進入睡眠並等待kswapd喚醒咱們,當前進程被喚醒後,可能kswapd已經設法回收了頁面,再回到開頭處(標號try_again處)從頭再試;若是分配策略中沒有指定__GFP_IO,那麼咱們就不能喚醒kswapd並等待被喚醒。由於守護進程kswapd在進行頁面回收操做時須要IO操做,須要申請某些IO鎖,而若是這些鎖恰好被當前進程所hold,這樣就形成了死鎖。這種狀況下,直接調用try_to_free_pages(本來該有kswapd進程調用)來試圖釋放些頁面。
- 一次次加大力度調用__alloc_pages_limit,實際上仍是有所保留的(至少可分配內存數量>最小水位值),管理區中還有一些壓箱底的內存,以備緊急狀況下使用。對於執行公務的進程,咱們只能不惜下血本了。
- 流程圖展現了內核爲了分配內存頁面所進行的堅苦卓絕的努力。當正常狀況來說,在step1便可分配成功。剩下的流程都是內核「屢戰屢敗」「屢敗屢戰」的結果,畢竟做爲一個操做系統的內核,遇到困難它可不能簡單的撂挑子,要絞盡腦汁去解決困難。
- 另,流程圖中涉及到分配策略中的幾個標誌,簡要解釋以下:
- __GFP_WAIT:等待頁面分配完成,即申請頁面的進程能夠被阻塞,意味着調度器能夠在分配期間調度另一個進程運行。
- __GFP_IO:內核在查找空閒頁的過程當中能夠進行I/O操做,如此內核能夠將換出的頁面寫到磁盤中,即將不活躍髒頁面洗白。