date: 2014-10-10 19:09node
爲了不總在CPU忙碌時也就是缺頁異常發生時,臨時再來搜尋空換出的頁面進行換出,內核將按期檢查並預先將若干頁面換出以騰出空間,維持系統空閒內存的的保有量,以減輕系統在缺頁異常發生時的負擔。爲此內核設置了一個專司頁面換出的守護神kswapd進程。數據結構
kswapd進程是個內核進程,這意味着,一方面kswapd沒有對應用戶空間(只有系統空間),可是有對應task_struct結構(所以調度器可能對它進行調度);一方面進程的可執行代碼與內核連接在一塊兒(編譯到內核鏡像中),所以他能夠自由訪問內核中的全局函數。函數
kswapd進程建立的代碼以下:code
<mm/vmscan.c> static int __init kswapd_init(void) { printk("Starting kswapd v1.8\n"); swap_setup(); kernel_thread(kswapd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); kernel_thread(kreclaimd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); return 0; }
kswapd_init在系統初始化期間被調用,它經過調用kernel_thread建立了兩個內核進程kswapd和kreclaimd。kernel_thread的第一參數爲「進程入口函數」,kswapd進程對應的進程函數爲kswapd(其實進程的名字是在kswapd函數中設置的),kswapd函數的實如今同一文件中。下文主要分析kswapd函數的流程。對象
在進行一些簡答的初始化後,kswapd進入一個死循環。死循環中的操做能夠分爲兩部分:第一部分是當內存短缺時調用do_try_to_free_pages,目的在於預先找出若干頁面,將這些頁面的映射斷開,而且使之從活躍狀態變爲不活躍狀態,爲頁面換出作準備。第二部分是例行公事,每一個循環都會作的,那就是把不活躍的髒頁面寫入交換設備,使它們成爲不活躍乾淨頁面繼續緩衝,或更進一步,回收一些這樣的頁面使之成爲空閒頁面。blog
處理完這兩部分工做以後,kswapd已經設法回收了一些頁面了,這時能夠喚醒哪些因內存不足而轉入睡眠的進程(參考內存頁面分配流程)。若是本次嘗試已經使得系統的有了必定的空閒內存「保有量」,爲了避免佔用CPU時間,kswapd像一個謙謙君子,主動轉入睡眠,睡眠時間是1秒,但在睡眠期間運行被提早喚醒。若是內存仍然不足,那就只能忍痛殺死一個進程(oom_kill),犧牲小我成就大我,以此來回收一些內存。索引
將活躍頁面的映射斷開,使之轉入不活躍狀態甚至最終被交換出去,這都是不得已而爲之,是內核的一種痛。所以do_try_to_free_pages按照痛的等級從淺到深的順序來選擇頁面。先調用page_launder來將不活躍髒頁面洗淨,使他們變成當即可分配的頁面。若是可分配的頁面數量仍然短缺,再從三個方面着手:隊列
refill_inactive主要的操做有兩層循環,外層循環以掃描力度priority(初始值爲6)爲終止條件。內層有兩個循環,其一是循環調用refill_inactive_scan,掃描全局的活躍頁面隊列active_list,試圖從中找到能夠轉入不活躍狀態的頁面,挑選的準則是頁面「壽命」(該壽命由kswapd的賜予並由kswapd「倒數」)以及頁面使用計數雙重標準。固然並非掃描隊列中的每個頁面,掃描力度由priority決定,當priority爲0時才掃描每一個頁面;其二是循環調用swap_out,選擇一個進程(選擇的準則「劫富濟貧」,即找駐內存頁面rss最多的進程),而後後掃描其頁面映射表,找出其中能夠轉入不活躍狀態的頁面。讀者也許會問,既然這個頁面是有映射的(不然不會出如今進程的頁面映射表中),那怎麼會不在活躍頁面隊列active_list中呢?在講頁面換入時咱們會看到,當因頁面頁面而恢復一個不活躍頁面的映射時,該頁面並非當即就轉到活躍頁面隊列,而把這項工做留給page_launder,讓其在系統比較空閒時再來處理,因此這樣的頁面有可能不在活躍頁面隊列中。進程
refill_inactive_scan和swap_out每次嘗試「換出」一個頁面,若是空閒內存以及非活躍內存再也不短缺,則可提早退出外層循環,若是refill_inactive_scan和swap_out「換出」頁面困難,那麼外層循環將是一個巨大的循環,將消耗大量的CPU時間。咱們在《進程與進程調度》一章將看到在系統調用或者是中斷服務程序返回用戶空間以前,內核會檢查task_struct結構中的need_resched字段,若是該字段置1,則要求調度。但kswapd是個內核進程,永遠不會返回用戶空間,這樣就可能繞過這個檢查而佔着CPU不放,因此只能靠它「自律」了。在refill_inactive的每次外層循環以前,主動檢查current->need_resched並請求一次調度。內存
前面說過,將頁面換出到磁盤頁面上時,pte_t已經變身爲swp_entry_t,那麼swp_entry_t的值從何而來,每次換出都須要從新指定嗎?顯然,若是該內存頁面以前曾換出過,並且從那以後該內存頁面的內容沒有被改寫,意味着頁面是乾淨的,那麼只需找到以前的「磁盤頁面」,從新創建聯繫就能夠了,並不須要真正的寫出到磁盤。並且即便改寫了,創建這種一對一的聯繫也便於咱們管理。爲了此目的,page結構中有一個index成員(其實就是swp_entry_t),指向該內存頁面在磁盤上的「歸宿」,須要換出時,根據index找到歸宿,必要時,將頁面的內容「轉移」到對應的磁盤頁面上就能夠了。