一文看懂 | 內(nèi)存交換機制
本文基于 Linux-2.4.16 內(nèi)核版本由于計算機的物理內(nèi)存是有限的, 而進程對內(nèi)存的使用是不確定的, 所以物理內(nèi)存總有用完的可能性. 那么當(dāng)系統(tǒng)的物理內(nèi)存不足時, Linux內(nèi)核使用什么方案來避免申請不到物理內(nèi)存這個問題呢?
相對于內(nèi)存來說, 磁盤的容量是非常大的, 所以Linux內(nèi)核實現(xiàn)了一個叫?
內(nèi)存交換
?的功能 -- 把某些進程的一些暫時用不到的內(nèi)存頁保存到磁盤中, 然后把物理內(nèi)存頁分配給更緊急的用戶使用, 當(dāng)進程用到時再從磁盤讀回到內(nèi)存中即可. 有了?內(nèi)存交換
?功能, 系統(tǒng)可使用的內(nèi)存就可以遠遠大于物理內(nèi)存的容量.LRU算法
內(nèi)存交換
?過程首先是找到一個合適的用戶進程內(nèi)存管理結(jié)構(gòu),然后把進程占用的內(nèi)存頁交換到磁盤中,并斷開虛擬內(nèi)存與物理內(nèi)存的映射,最后釋放進程占用的內(nèi)存頁。由于涉及到IO操作,所以這是一個比較耗時的過程。如果被交換出去的內(nèi)存頁剛好又被訪問了,這時又需要從磁盤中把內(nèi)存頁的數(shù)據(jù)交換到內(nèi)存中。所以,在這種情況下不單不能解決內(nèi)存緊缺的問題,而且增加了系統(tǒng)的負(fù)荷。為了解決這個問題,Linux內(nèi)核使用了一種稱為?LRU (Least Recently Used)
?的算法, 下面介紹一下?LRU算法
?的大體過程.LRU
?的中文翻譯是?最近最少使用
, 顧名思義就是一段時間內(nèi)沒有被使用, 那么Linux內(nèi)核怎么知道哪些內(nèi)存頁面最近沒有被使用呢? 最簡單的方法就是把內(nèi)存頁放進一個隊列里, 如果內(nèi)存頁被訪問了, 就把內(nèi)存頁移動到鏈表的頭部, 這樣沒被訪問的內(nèi)存頁在一段時間后便會移動到隊列的尾部, 而釋放內(nèi)存頁時從鏈表的尾部開始. 著名的緩存服務(wù)器?memcached
?就是使用這種?LRU算法
.Linux內(nèi)核也使用了類似的算法, 但相對要復(fù)雜一些. Linux內(nèi)核維護著三個隊列: 活躍隊列, 非活躍臟隊列和非活躍干凈隊列. 為什么Linux需要維護三個隊列, 而不是使用一個隊列呢? 這是因為Linux希望內(nèi)存頁交換過程慢慢進行, Linux內(nèi)核有個內(nèi)核線程?kswapd
?會定時檢查系統(tǒng)的空閑內(nèi)存頁是否緊缺, 如果系統(tǒng)的空閑內(nèi)存頁緊缺時時, 就會選擇一些用戶進程把其占用的內(nèi)存頁添加到活躍鏈表中并斷開進程與此內(nèi)存頁的映射關(guān)系. 隨著時間的推移, 如果內(nèi)存頁沒有被訪問, 那么就會被移動到非活躍臟鏈表. 非活躍臟鏈表中的內(nèi)存頁是需要被交換到磁盤的, 當(dāng)系統(tǒng)中空閑內(nèi)存頁緊缺時就會從非活躍臟鏈表的尾部開始把內(nèi)存頁刷新到磁盤中, 然后移動到非活躍干凈鏈表中, 非活躍干凈鏈表中的內(nèi)存頁是可以立刻分配給進程使用的. 各個鏈表之間的移動如下圖:如果在這個過程中, 內(nèi)存頁又被訪問了, 那么Linux內(nèi)核會把內(nèi)存頁移動到活躍鏈表中, 并且建立內(nèi)存映射關(guān)系, 這樣就不需要從磁盤中讀取內(nèi)存頁的內(nèi)容.注意: 內(nèi)核只維護著一個活躍鏈表和一個非活躍臟鏈表, 但是非活躍干凈鏈表是每個內(nèi)存管理區(qū)都有一個的.
這是因為分配內(nèi)存是在內(nèi)存管理區(qū)的基礎(chǔ)上進行的, 所以一個內(nèi)存頁必須屬于某一個內(nèi)存管理區(qū).
kswapd內(nèi)核線程
在Linux系統(tǒng)啟動時會調(diào)用?kswapd_init()
?函數(shù), 代碼如下: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()
?函數(shù)會創(chuàng)建?kswapd
?和?kreclaimd
?兩個內(nèi)核線程, 這兩個內(nèi)核線程負(fù)責(zé)在系統(tǒng)物理內(nèi)存緊缺時釋放一些物理內(nèi)存頁, 從而使系統(tǒng)的可用內(nèi)存達到一個平衡. 下面我們重點來分析?kswapd
?這個內(nèi)核線程,?kswapd()
?的源碼如下:int?kswapd(void?*unused)
{
????struct?task_struct?*tsk?=?current;
????tsk->session?=?1;
????tsk->pgrp?=?1;
????strcpy(tsk->comm,?"kswapd");
????sigfillset(