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