成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux內(nèi)存回收機(jī)制:系統(tǒng)性能的幕后守護(hù)者

系統(tǒng) Linux
當(dāng)我們在 Linux 系統(tǒng)中啟動(dòng)一個(gè)應(yīng)用程序時(shí),程序的代碼和數(shù)據(jù)會(huì)被加載到內(nèi)存中。CPU 從內(nèi)存中讀取這些指令和數(shù)據(jù)進(jìn)行處理,處理結(jié)果也會(huì)暫時(shí)存儲在內(nèi)存中。可以說,內(nèi)存是連接 CPU 與外部存儲設(shè)備(如硬盤)的橋梁。

在Linux 系統(tǒng)的龐大體系中,內(nèi)存扮演著極為關(guān)鍵的角色,堪稱系統(tǒng)運(yùn)行的 “血液”。就像血液對于人體,一刻不停地循環(huán)流動(dòng),為各個(gè)器官輸送氧氣和營養(yǎng)物質(zhì),維持人體正常運(yùn)轉(zhuǎn)一樣,內(nèi)存為 Linux 系統(tǒng)中的各個(gè)進(jìn)程輸送數(shù)據(jù)和指令,保障系統(tǒng)的穩(wěn)定運(yùn)行。

當(dāng)我們在 Linux 系統(tǒng)中啟動(dòng)一個(gè)應(yīng)用程序時(shí),程序的代碼和數(shù)據(jù)會(huì)被加載到內(nèi)存中。CPU 從內(nèi)存中讀取這些指令和數(shù)據(jù)進(jìn)行處理,處理結(jié)果也會(huì)暫時(shí)存儲在內(nèi)存中。可以說,內(nèi)存是連接 CPU 與外部存儲設(shè)備(如硬盤)的橋梁。由于 CPU 的運(yùn)算速度極快,而硬盤等外部存儲設(shè)備的讀寫速度相對較慢,如果沒有內(nèi)存作為數(shù)據(jù)的臨時(shí)存儲和快速交換區(qū)域,CPU 在等待數(shù)據(jù)從硬盤傳輸?shù)倪^程中會(huì)處于空閑狀態(tài),極大地降低系統(tǒng)的整體性能。內(nèi)存的存在使得 CPU 能夠高效地與外部存儲設(shè)備協(xié)同工作,讓系統(tǒng)能夠快速響應(yīng)用戶的操作。

一、內(nèi)存緊張引發(fā)的 “危機(jī)”

在 Linux 系統(tǒng)的運(yùn)行過程中,內(nèi)存資源并非總是充足的。當(dāng)系統(tǒng)中運(yùn)行的進(jìn)程過多,或者某些進(jìn)程占用了大量內(nèi)存時(shí),內(nèi)存緊張的情況就會(huì)出現(xiàn),這如同人體血液循環(huán)不暢,會(huì)給系統(tǒng)帶來一系列 “危機(jī)”。

最直觀的表現(xiàn)就是系統(tǒng)運(yùn)行卡頓。當(dāng)內(nèi)存緊張時(shí),系統(tǒng)不得不頻繁地將內(nèi)存中的數(shù)據(jù)交換到磁盤的虛擬內(nèi)存(Swap Space)中,這種操作被稱為 Swap。由于磁盤的讀寫速度遠(yuǎn)遠(yuǎn)低于內(nèi)存,頻繁的 Swap 會(huì)導(dǎo)致系統(tǒng)響應(yīng)速度大幅下降。比如,在使用 Linux 系統(tǒng)進(jìn)行多任務(wù)處理時(shí),同時(shí)打開多個(gè)大型文件、運(yùn)行多個(gè)程序,如果內(nèi)存不足,系統(tǒng)就會(huì)出現(xiàn)明顯的卡頓,打開文件的速度變慢,程序之間的切換也變得遲緩,原本流暢的操作變得磕磕絆絆,嚴(yán)重影響用戶體驗(yàn)。

更為嚴(yán)重的是,內(nèi)存緊張還可能導(dǎo)致系統(tǒng)崩潰。當(dāng)內(nèi)存資源耗盡,系統(tǒng)無法為新的進(jìn)程分配內(nèi)存,也無法滿足現(xiàn)有進(jìn)程對內(nèi)存的進(jìn)一步需求時(shí),就會(huì)觸發(fā) OOM(Out Of Memory)機(jī)制 ,即內(nèi)存溢出。OOM Killer 會(huì)根據(jù)一定的算法選擇并殺死一些占用內(nèi)存較多的進(jìn)程,試圖釋放內(nèi)存資源。但在某些極端情況下,這種方式可能無法有效解決問題,最終導(dǎo)致整個(gè)系統(tǒng)崩潰,所有正在運(yùn)行的程序都將被迫終止,數(shù)據(jù)丟失,給用戶帶來巨大的損失。

為了避免這些 “危機(jī)” 的發(fā)生,Linux 系統(tǒng)需要一套高效的內(nèi)存回收機(jī)制,就像人體擁有強(qiáng)大的自我調(diào)節(jié)能力一樣,及時(shí)清理和回收不再使用的內(nèi)存資源,確保系統(tǒng)的穩(wěn)定運(yùn)行。

二、什么時(shí)候回收內(nèi)存?

因?yàn)樵诓煌膬?nèi)存分配路徑中,會(huì)觸發(fā)不同的內(nèi)存回收方式,內(nèi)存回收針對的目標(biāo)有兩種,一種是針對zone的,另一種是針對一個(gè)memcg的,而這里我們只討論針對zone的內(nèi)存回收,個(gè)人把針對zone的內(nèi)存回收方式分為三種,分別是快速內(nèi)存回收、直接內(nèi)存回收、kswapd內(nèi)存回收。

  1. 快速內(nèi)存回收:處于get_page_from_freelist()函數(shù)中,在遍歷zonelist過程中,對每個(gè)zone都在分配前進(jìn)行判斷,如果分配后zone的空閑內(nèi)存數(shù)量 < 閥值 + 保留頁框數(shù)量,那么此zone就會(huì)進(jìn)行快速內(nèi)存回收,即使分配前此zone空閑頁框數(shù)量都沒有達(dá)到閥值,都會(huì)進(jìn)行此zone的快速內(nèi)存回收。注意閥值可能是min/low/high的任何一種,因?yàn)樵诳焖賰?nèi)存分配,慢速內(nèi)存分配和oom分配過程中如果回收的頁框足夠,都會(huì)調(diào)用到get_page_from_freelist()函數(shù),所以快速內(nèi)存回收不僅僅發(fā)生在快速內(nèi)存分配中,在慢速內(nèi)存分配過程中也會(huì)發(fā)生。
  2. 直接內(nèi)存回收:處于慢速分配過程中,直接內(nèi)存回收只有一種情況下會(huì)使用,在慢速分配中無法從zonelist的所有zone中以min閥值分配頁框,并且進(jìn)行異步內(nèi)存壓縮后,還是無法分配到頁框的時(shí)候,就對zonelist中的所有zone進(jìn)行一次直接內(nèi)存回收。注意,直接內(nèi)存回收是針對zonelist中的所有zone的,它并不像快速內(nèi)存回收和kswapd內(nèi)存回收,只會(huì)對zonelist中空閑頁框不達(dá)標(biāo)的zone進(jìn)行內(nèi)存回收。并且在直接內(nèi)存回收中,有可能喚醒flush內(nèi)核線程。
  3. kswapd內(nèi)存回收:發(fā)生在kswapd內(nèi)核線程中,每個(gè)node有一個(gè)swapd內(nèi)核線程,也就是kswapd內(nèi)核線程中的內(nèi)存回收,是只針對所在node的,并且只會(huì)對 分配了order頁框數(shù)量后空閑頁框數(shù)量 < 此zone的high閥值 + 保留頁框數(shù)量 的zone進(jìn)行內(nèi)存回收,并不會(huì)對此node的所有zone進(jìn)行內(nèi)存回收。

這三種內(nèi)存回收雖然是在不同狀態(tài)下會(huì)被觸發(fā),但是如果當(dāng)內(nèi)存不足時(shí),kswapd內(nèi)存回收和直接內(nèi)存回收很大可能是在并發(fā)的進(jìn)行內(nèi)存回收的。而實(shí)際上,這三種回收再怎么不同,進(jìn)行內(nèi)存回收的執(zhí)行代碼是一樣的,只是在內(nèi)存回收前做的一些處理和判斷不同。

2.1快速內(nèi)存回收

無論是在快速分配還是慢速分配過程中,只要內(nèi)核希望從一個(gè)zonelist中獲取連續(xù)頁框,就必須調(diào)用get_page_from_freelist()函數(shù),在此函數(shù)中會(huì)對zonelist中的所有zone進(jìn)行判斷,判斷能否從此zone分配連續(xù)頁框,而判斷一個(gè)zone能否進(jìn)行分配的唯一標(biāo)準(zhǔn)是:分配后剩余的頁框數(shù)量 > 閥值 + 此zone的保留頁框數(shù)量。當(dāng)zone不滿足這個(gè)標(biāo)準(zhǔn),內(nèi)核會(huì)對zone進(jìn)行快速內(nèi)存回收,這個(gè)快速內(nèi)存回收的執(zhí)行路徑是:

get_page_from_freelist() -> zone_reclaim() -> __zone_reclaim() ->shrink_zone()

由于篇幅關(guān)系,就不列代碼了,之前也說了,/proc/sys/vm/zone_reclaim_mode會(huì)影響快速內(nèi)存回收,在get_page_from_freelist()函數(shù)中就有這么一段:

/* 
             * 判斷是否對此zone進(jìn)行內(nèi)存回收,如果開啟了內(nèi)存回收,則會(huì)對此zone進(jìn)行內(nèi)存回收,否則,通過距離判斷是否進(jìn)行內(nèi)存回收
             * zone_allows_reclaim()函數(shù)實(shí)際上就是判斷zone所在node是否與preferred_zone所在node的距離 < RECLAIM_DISTANCE(30或10)
             * 當(dāng)內(nèi)存回收未開啟的情況下,只會(huì)對距離比較近的zone進(jìn)行回收
             */
            if (zone_reclaim_mode == 0 ||
                !zone_allows_reclaim(preferred_zone, zone))
                goto this_zone_full;

zone_allows_reclaim()用于計(jì)算zone與preferred_zone之間的距離,這個(gè)跟node距離有關(guān),當(dāng)距離不滿足時(shí),則不會(huì)對此zone進(jìn)行快速內(nèi)存回收,也就是當(dāng)zone_reclaim_mode開啟后,才會(huì)對zonelist中的所有zone進(jìn)行內(nèi)存回收。

需要注意閥值,之前也說了,在一次分配過程中,可能很多地方會(huì)調(diào)用get_page_from_freelist()函數(shù),而每次傳入的閥值很可能是不同的,在第一次進(jìn)行快速分配時(shí),使用的是zone的low閥值進(jìn)行g(shù)et_page_from_freelist()調(diào)用,在慢速分配過程中,會(huì)使用zone的min閥值進(jìn)行g(shù)et_page_from_freelist()調(diào)用,而在oomkill進(jìn)行分配過程中,會(huì)使用high閥值調(diào)用get_page_from_freelist(),當(dāng)zone的分配后剩余的頁框數(shù)量 < 閥值 + 此zone的保留頁框數(shù)量 時(shí),則會(huì)調(diào)用zone_reclaim()對此zone進(jìn)行內(nèi)存回收而zone_reclaim()又會(huì)調(diào)用到__zone_relcaim()。

在__zone_reclaim()中,主要做三件事:初始化一個(gè)struct scan_control結(jié)構(gòu)、循環(huán)調(diào)用shrink_zone()進(jìn)行對zone的內(nèi)存回收、從調(diào)用shrink_slab()對slab進(jìn)行回收,struct scan_ control結(jié)構(gòu)初始化如下:

struct scan_control sc = {
        /* 最少一次回收SWAP_CLUSTER_MAX,最多一次回收1 << order個(gè),應(yīng)該是1024個(gè) */
        .nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX),
        /* 當(dāng)前進(jìn)程明確禁止分配內(nèi)存的IO操作(禁止__GFP_IO,__GFP_FS標(biāo)志),那么則清除__GFP_IO,__GFP_FS標(biāo)志,表示不進(jìn)行IO操作 */
        .gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),
        .order = order,
        /* 優(yōu)先級為4,默認(rèn)是12,會(huì)比12一次掃描更多l(xiāng)ru鏈表中的頁框,而且掃描次數(shù)會(huì)比優(yōu)先級為12的少,并且如果回收過程中回收到了足夠頁框,就會(huì)返回 */
        .priority = ZONE_RECLAIM_PRIORITY,
        /* 通過/proc/sys/vm/zone_reclaim_mode文件設(shè)置是否允許將臟頁回寫到磁盤,即使設(shè)為允許,快速內(nèi)存回收也不能對臟文件頁進(jìn)行回寫操作。
         * 當(dāng)zone_reclaim_mode為0時(shí),在這里是不允許頁框回寫的,
         */
        .may_writepage = !!(zone_reclaim_mode & RECLAIM_WRITE),
        /* 通過/proc/sys/vm/zone_reclaim_mode文件設(shè)置是否允許將匿名頁回寫到swap分區(qū) 
         * 當(dāng)zone_reclaim_mode為0時(shí),在這里是不允許匿名頁回寫的,我們這里假設(shè)允許
         */
        .may_unmap = !!(zone_reclaim_mode & RECLAIM_SWAP),
        /* 允許對匿名頁lru鏈表操作 */
        .may_swap = 1,
        /* 本結(jié)構(gòu)還有一個(gè)
         * .target_mem_cgroup 表示是針對某個(gè)memcg,還是針對整個(gè)zone進(jìn)行內(nèi)存回收的,這里為空,也就是說這里是針對整個(gè)zone進(jìn)行內(nèi)存回收的
         */
    };

nr_pages是1<<order。可以看到優(yōu)先級為4,sc->may_writepage和sc->may_unmap與zone_reclaim_mode有關(guān),這個(gè)sc是針對一個(gè)zone的,上面也說了,只有當(dāng)zone不滿足 分配后剩余的頁框數(shù)量 > 閥值 + 此zone保留的頁框數(shù)量 時(shí),才會(huì)對zone進(jìn)行內(nèi)存回收,也就是它不是針對整個(gè)zonelist進(jìn)行內(nèi)存回收的,而是針對不滿足情況的zone進(jìn)行。再看看循環(huán)調(diào)用shrink_zone():

do {
            /* 對此zone進(jìn)行內(nèi)存回收,內(nèi)存回收的主要函數(shù) */
            shrink_zone(zone, &sc);
            /* 沒有回收到足夠頁框,并且循環(huán)次數(shù)沒達(dá)到優(yōu)先級次數(shù),繼續(xù) */
        } while (sc.nr_reclaimed < nr_pages && --sc.priority >= 0);

可以看到,每次調(diào)用shrink_zone后都會(huì)sc.priority--,也就是最多進(jìn)行4次調(diào)用shrink_zone(),并且每次調(diào)用shrink_zone()掃描的頁框會(huì)越來越多,直到回收到了1<<order個(gè)頁框?yàn)橹埂?/span>

注意:在快速內(nèi)存回收中,即使zone_reclaim_mode允許回寫,也不會(huì)對臟文件頁進(jìn)行回寫操作的,但是如果zone_reclaim_mode允許,會(huì)對非文件頁進(jìn)行回寫操作。

可以對快速內(nèi)存回收總結(jié)出:

  • 開始標(biāo)志是:此zone分配后剩余的頁框數(shù)量 > 此zone的閥值 + 此zone的保留頁框數(shù)量(閥值可能是:min,low,high其中一個(gè))。
  • 結(jié)束標(biāo)志是:對此zone回收到了本次分配時(shí)需要的頁框數(shù)量 或者 sc->priority降為0(可能會(huì)進(jìn)行多次shrink_zone()的調(diào)用)。
  • 回收對象:zone的干凈文件頁、slab、可能會(huì)回寫匿名頁

2.2直接內(nèi)存回收

調(diào)用流程:

__alloc_pages_slowpath() 
-> __alloc_pages_direct_reclaim() 
-> __perform_reclaim() 
-> try_to_free_pages() 
-> do_try_to_free_pages()
 -> shrink_zones() -> shrink_zone()

直接內(nèi)存回收發(fā)生在慢速分配中,在慢速分配中,首先喚醒所有node結(jié)點(diǎn)的kswap內(nèi)核線程,然后會(huì)調(diào)用get_page_from_freelist()嘗試用min閥值從zonelist的zone中獲取連續(xù)頁框,如果失敗,則對zonelist的zone進(jìn)行異步壓縮,異步壓縮之后再次調(diào)用get_page_from_freelist()嘗試使用min閥值從zonelist的zone中獲取連續(xù)頁框,如果還是失敗,就會(huì)進(jìn)入到直接內(nèi)存回收。

在進(jìn)行直接內(nèi)存回收時(shí),進(jìn)程是有可能加入到node的pgdat->pfmemalloc_wait這個(gè)等待隊(duì)列中,當(dāng)kswapd進(jìn)行內(nèi)存回收后如果node空閑內(nèi)存達(dá)到平衡,那么就會(huì)喚醒pgdat->pfmemalloc_wait中的進(jìn)程,其實(shí)也就是,加入到pgdat->pfmemalloc_wait這個(gè)等待隊(duì)列的進(jìn)程,自身就不會(huì)進(jìn)行直接內(nèi)存回收,而是讓kswapd進(jìn)行,之后kswapd會(huì)喚醒它們。之后的文章會(huì)詳細(xì)說明這種情況。

先看初始化的struct scan_control,是在try_to_free_pages()中進(jìn)行初始化的:

struct scan_control sc = {
        /* 打算回收32個(gè)頁框 */
        .nr_to_reclaim = SWAP_CLUSTER_MAX,
        .gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),
        /* 本次內(nèi)存分配的order值 */
        .order = order,
        /* 允許進(jìn)行回收的node掩碼 */
        .nodemask = nodemask,
        /* 優(yōu)先級為默認(rèn)的12 */
        .priority = DEF_PRIORITY,
        /* 與/proc/sys/vm/laptop_mode文件有關(guān)
         * laptop_mode為0,則允許進(jìn)行回寫操作,即使允許回寫,直接內(nèi)存回收也不能對臟文件頁進(jìn)行回寫
         * 不過允許回寫時(shí),可以對非文件頁進(jìn)行回寫
         */
        .may_writepage = !laptop_mode,
        /* 允許進(jìn)行unmap操作 */
        .may_unmap = 1,
        /* 允許進(jìn)行非文件頁的操作 */
        .may_swap = 1,
    };

在直接內(nèi)存回收過程中,這個(gè)sc結(jié)構(gòu)是對zonelist中所有zone使用的,而不是像快速內(nèi)存回收,是針對zonelist中不滿足條件的一個(gè)一個(gè)zone進(jìn)行使用,對于直接內(nèi)存回收,以下需要注意:

sc的c初始使用的是默認(rèn)的優(yōu)先級12,那么就會(huì)對遍歷12遍zonelist中的所有zone,每次遍歷后sc->priority--,相當(dāng)于讓每個(gè)zone執(zhí)行12次shrink_zone()

只有sc->priority == 12時(shí)會(huì)對zonelist中的所有zone強(qiáng)制執(zhí)行shrink_zone(),而當(dāng)sc->priority == 12這輪循環(huán)過后,會(huì)通過判斷來確定zone是否要執(zhí)行shrink_zone(),這個(gè)判斷標(biāo)志就是:此zone已經(jīng)掃描的頁數(shù) < (此zone所有沒有鎖在內(nèi)存中的文件頁和非文件頁之和 * 6) 。如果掃描頁數(shù)超過此值,就說明已經(jīng)對此zone掃描過太多頁框了,就不對此zone進(jìn)行shrink_zone()了。

并且當(dāng)優(yōu)先級降到10以下時(shí),即使原來sc->may_writepage不允許回寫,這時(shí)候會(huì)開始允許回寫。這樣做是因?yàn)椴换貙懞茈y回收到頁框。

只打算回收的頁框?yàn)?2個(gè),并且在此期間,如果掃描頁數(shù)超過(sc->nr_to_reclaim + sc->nr_to_reclaim / 2),則是會(huì)根據(jù)laptop_mode的情況喚醒flush內(nèi)核線程的。

直接內(nèi)存回收無論如何都不會(huì)對臟文件頁進(jìn)行回寫操作,如果sc->may_writepage為1,那么會(huì)對非文件頁進(jìn)行回寫操作

  • 會(huì)對文件頁和非文件頁進(jìn)行unmap操作
  • 會(huì)對非文件頁處理(加入swap cache,unmap,回寫)
  • 會(huì)先回收在memcg中并且超過所在memcg的soft_limit_in_bytes的進(jìn)程的內(nèi)存
  • 也會(huì)調(diào)用shrink_slab()對slab進(jìn)行回收

個(gè)人認(rèn)為直接內(nèi)存回收是為了讓更多的頁得到掃描,然后進(jìn)行回寫操作,也可能是為了后面的內(nèi)存壓縮回收一些頁框,其實(shí)這里不太理解,為什么只回收32個(gè)頁框,它并不像直接內(nèi)存回收,打算回收的頁框數(shù)量是1<<order。

可以對直接內(nèi)存回收總結(jié)出:

  • 開始標(biāo)志是:zonelist的所有zone都不能通過min閥值獲取到頁框時(shí)。
  • 結(jié)束標(biāo)志:回收到32個(gè)頁框,或者sc->priority降到0,或者空閑頁框足夠進(jìn)行內(nèi)存壓縮了(可能會(huì)進(jìn)行多次shrink_zone()的調(diào)用)。
  • 回收對象:超過所在memcg的soft_limit_in_bytes的進(jìn)程的內(nèi)存、zone的干凈文件頁、slab、匿名頁swap

2.3kswapd內(nèi)存回收

調(diào)用過程:

-> balance_pgdat() -> kswapd_shrink_zone() -> shrink_zone()

在分配過程中,只要get_page_from_freelist()函數(shù)無法以low閥值從zonelist的zone中獲取到連續(xù)頁框,并且分配內(nèi)存標(biāo)志gfp_mask沒有標(biāo)記__GFP_NO_KSWAPD,則會(huì)喚醒kswapd內(nèi)核線程,在當(dāng)中執(zhí)行kswapd內(nèi)存回收,先看初始化的sc結(jié)構(gòu):

/* 掃描控制結(jié)構(gòu) */
    struct scan_control sc = {
        /* (__GFP_WAIT | __GFP_IO | __GFP_FS)
         * 此次內(nèi)存回收允許進(jìn)行IO和文件系統(tǒng)操作,有可能阻塞
         */
        .gfp_mask = GFP_KERNEL,
        /* 分配內(nèi)存失敗時(shí)使用的order值,因?yàn)橹挥蟹峙鋬?nèi)存失敗才會(huì)喚醒kswapd */
        .order = order,
        /* 這個(gè)優(yōu)先級決定了一次掃描多少隊(duì)列 */
        .priority = DEF_PRIORITY,
        .may_writepage = !laptop_mode,
        .may_unmap = 1,
        .may_swap = 1,
    };

由于此sc是針對整個(gè)node的所有zone的,這里沒有設(shè)置sc->nr_to_reclaim,在確定對某個(gè)zone進(jìn)行內(nèi)存回收時(shí),這個(gè)sc->nr_to_reclaim被設(shè)置為:

sc->nr_to_reclaim = max(SWAP_CLUSTER_MAX, high_wmark_pages(zone));

可以看到,如果回收的頁框數(shù)量達(dá)到了zone的high閥值,其實(shí)意思就是盡可能的回收頁框了,kswapd內(nèi)核線程是每個(gè)node有一個(gè)的,那也意味著,此node的kswapd只會(huì)對此node的zone進(jìn)行內(nèi)存回收工作,也就不需要zonelist了。

要點(diǎn):

優(yōu)先級使用默認(rèn)為的12,會(huì)執(zhí)行多次遍歷node(并不是node中的所有zone),但并不會(huì)每次遍歷都進(jìn)行sc->priority--,當(dāng)能夠回收的內(nèi)存時(shí),才進(jìn)行sc->priority--以ZONE_HIGHMEM -> ZONE_NORMAL ->ZONE_DMA的順序找出第一個(gè)不平衡的zone,平衡條件是: 此zone分配頁框后剩余的頁框數(shù)量 > 此zone的high閥值 + 此zone保留的頁框數(shù)量。不滿足則表示此zone不平衡。

對第一個(gè)不平衡的zone及其后面的zone進(jìn)行回收在memcg中并且超過所在memcg的soft_limit_in_bytes的進(jìn)程的內(nèi)存,比如第一個(gè)不平衡的zone是ZONE_NORMAL,那么執(zhí)行內(nèi)存回收的zone就是ZONE_NORMAL和ZONE_DMA。

如果zone是平衡的,則不對zone進(jìn)行內(nèi)存回收(但是上面那部不會(huì)因?yàn)閦one平衡而不執(zhí)行),而如果zone是不平衡的,那么會(huì)調(diào)用shrink_zone()進(jìn)行內(nèi)存回收,以及調(diào)用shrink_slab()進(jìn)行slab的回收。

對于node中所有 zone分配后剩余內(nèi)存 < zone的low閥值 + zone保留的頁框數(shù)量 的zone,會(huì)進(jìn)行內(nèi)存壓縮

檢查node中所有zone是否都平衡,沒有平衡則繼續(xù)循環(huán)

如果laptop == 0,那么會(huì)對文件頁和非文件頁進(jìn)行回寫操作,如果laptop == 1,那么只有當(dāng)sc->priority < 10時(shí)才會(huì)對文件頁和非文件頁進(jìn)行回寫操作

會(huì)對文件頁和非文件頁進(jìn)行回寫unmap操作

會(huì)對非文件頁進(jìn)行處理(加入swapcache,unmap,回寫)

可以看出來,kswapd內(nèi)存回收會(huì)將node結(jié)點(diǎn)中的所有zone的空閑頁框都至少拉高h(yuǎn)igh閥值。

可以對kswapd內(nèi)存回收總結(jié)出:

  • 開始標(biāo)志:zonelist的所有zone都不能通過min閥值獲取到頁框時(shí),會(huì)喚醒所有node的kswapd內(nèi)核線程,然后在kswapd中會(huì)對不滿足 zone分配頁框后剩余的頁框數(shù)量 > 此zone的high閥值 + 此zone保留的頁框數(shù)量 的zone進(jìn)行內(nèi)存回收。
  • 結(jié)束標(biāo)志:node中所有zone都滿足 zone分配頁框后剩余的頁框數(shù)量 > 此zone的high閥值 + 此zone保留的頁框數(shù)量(可能會(huì)進(jìn)行多次shrink_zone()的調(diào)用)。
  • 回收對象:超過所在memcg的soft_limit_in_bytes的進(jìn)程的內(nèi)存、zone的干凈的文件頁、zone的臟的文件頁、slab、匿名頁swap

2.4回收哪些內(nèi)存

(1)Page Cache

CPU如果要訪問外部磁盤上的文件,需要首先將這些文件的內(nèi)容拷貝到內(nèi)存中,由于硬件的限制,從磁盤到內(nèi)存的數(shù)據(jù)傳輸速度是很慢的,如果現(xiàn)在物理內(nèi)存有空余,干嘛不用這些空閑內(nèi)存來緩存一些磁盤的文件內(nèi)容呢,這部分用作緩存磁盤文件的內(nèi)存就叫做page cache。

用戶進(jìn)程啟動(dòng)read()系統(tǒng)調(diào)用后,內(nèi)核會(huì)首先查看page cache里有沒有用戶要讀取的文件內(nèi)容,如果有(cache hit),那就直接讀取,沒有的話(cache miss)再啟動(dòng)I/O操作從磁盤上讀取,然后放到page cache中,下次再訪問這部分內(nèi)容的時(shí)候,就又可以cache hit,不用忍受磁盤的龜速了(比內(nèi)存慢幾個(gè)數(shù)量級)。

和CPU里的硬件cache是不是很像?兩者其實(shí)都是利用的局部性原理,只不過硬件cache是CPU緩存內(nèi)存的數(shù)據(jù),而page cache是內(nèi)存緩存磁盤的數(shù)據(jù),這也體現(xiàn)了memory hierarchy分級的思想。

相對于磁盤,內(nèi)存的容量還是很有限的,所以沒必要緩存整個(gè)文件,只需要當(dāng)文件的某部分內(nèi)容真正被訪問到時(shí),再將這部分內(nèi)容調(diào)入內(nèi)存緩存起來就可以了,這種方式叫做demand paging(按需調(diào)頁),把對需求的滿足延遲到最后一刻,很懶很實(shí)用。

page cache中那么多的page frames,怎么管理和查找呢?這就要說到之前的文章提到的address_space結(jié)構(gòu)體,一個(gè)address_space管理了一個(gè)文件在內(nèi)存中緩存的所有pages。這個(gè)address_space可不是進(jìn)程虛擬地址空間的address space,但是兩者之間也是由很多聯(lián)系的。

上文講到,mmap映射可以將文件的一部分區(qū)域映射到虛擬地址空間的一個(gè)VMA,如果有5個(gè)進(jìn)程,每個(gè)進(jìn)程mmap同一個(gè)文件兩次(文件的兩個(gè)不同部分),那么就有10個(gè)VMA,但address_space只有一個(gè)。每個(gè)進(jìn)程打開一個(gè)文件的時(shí)候,都會(huì)生成一個(gè)表示這個(gè)文件的strut file,但是文件的struct inode只有一個(gè),inode才是文件的唯一標(biāo)識,指向address_space的指針就是內(nèi)嵌在inode結(jié)構(gòu)體中的。在page cache中,每個(gè)page都有對應(yīng)的文件,這個(gè)文件就是這個(gè)page的owner,address_space將屬于同一owner的pages聯(lián)系起來,將這些pages的操作方法與文件所屬的文件系統(tǒng)聯(lián)系起來。

來看下address_space結(jié)構(gòu)體具體是怎樣構(gòu)成的:

struct address_space { 
	struct inode            *host;              /* Owner, either the inode or the block_device */ 
	struct radix_tree_root  page_tree;          /* Cached pages */ 
	spinlock_t              tree_lock;          /* page_tree lock */ 
	struct prio_tree_root   i_mmap;             /* Tree of private and shared mappings */ 
	struct spinlock_t       i_mmap_lock;        /* Protects @i_mmap */       
	unsigned long           nrpages;            /* total number of pages */
        struct address_space_operations   *a_ops;   /* operations table */ 
        ...
}
  • host指向address_space對應(yīng)文件的inode。
  • address_space中的page cache之前一直是用radix tree的數(shù)據(jù)結(jié)構(gòu)組織的,tree_lock是訪問這個(gè)radix tree的spinlcok(現(xiàn)在已換成xarray)。
  • i_mmap是管理address_space所屬文件的多個(gè)VMA映射的,用priority search tree的數(shù)據(jù)結(jié)構(gòu)組織,i_mmap_lock是訪問這個(gè)priority search tree的spinlcok。
  • nr_pages是address_space中含有的page frames的總數(shù)。
  • a_ops是關(guān)于page cache如何與磁盤(backing store)交互的一系列operations。

(2)從Radix Tree到XArray

radix tree的每個(gè)節(jié)點(diǎn)可以存放64個(gè)slots(由RADIX_TREE_MAP_SHIFT設(shè)定,小型系統(tǒng)為了節(jié)省內(nèi)存可以配置為16),每個(gè)slot的指針指向下一層節(jié)點(diǎn),最后一層slot的指針指向struct page(關(guān)于struct page請參考這篇文章),因此一個(gè)高度為2的radix tree可以容納64個(gè)pages,高度為3則可以容納4096個(gè)pages。

如何在radix tree中找到一個(gè)指定的page呢?那就要回顧下struct page中的mapping和index了,mapping指向page所屬文件對應(yīng)的address_space,進(jìn)而可以找到address_space的radix tree,index既是page在文件內(nèi)的offset,也可作為查找這個(gè)radix tree的索引,因?yàn)閞adix tree就是按page的index來組織struct page的。這里是用page index中的一部分bit位作為radix tree第一層的索引,另一部分bit位作為第二層的索引,以此類推。因?yàn)橐粋€(gè)radix tree節(jié)點(diǎn)存放64個(gè)slots,因此一層索引需要6個(gè)bits,如果radix tree高度為2,則需要12個(gè)bits。

內(nèi)核中具體的查找函數(shù)是find_get_page(mapping, offset),如果在page cache中沒有找到,就會(huì)觸發(fā)page fault,調(diào)用__page_cache_alloc()在內(nèi)存中分配若干物理頁面,然后將數(shù)據(jù)從磁盤對應(yīng)位置copy過來,通過add_to_page_cache()-->radix_tree_insert()放入radix tree中。在將一個(gè)page添加到page cache和從page cache移除時(shí),需要將page和對應(yīng)的radix tree都上鎖。

linux中radix tree的每個(gè)slot除了存放指針,還存放著標(biāo)志page和磁盤文件同步狀態(tài)的tag。如果page cache中一個(gè)page在內(nèi)存中被修改后沒有同步到磁盤,就說這個(gè)page是dirty的,此時(shí)tag就是PAGE_CACHE_DIRTY。如果正在同步,tag就是PAGE_CACHE_WRITEBACK。只要下一層中有一個(gè)slot指向的page是dirty的,那么上一層的這個(gè)slot的tag就是PAGE_CACHE_DIRTY的,就像一滴墨水一樣,放入清水后,清水也就不再完全清澈了。

前面介紹struct page中的flags時(shí)提到,flags可以是PG_dirty或PG_writeback,既然struct page中已經(jīng)有了標(biāo)識同步狀態(tài)的信息,為什么這里radix tree還要再加上tag來標(biāo)記呢?這是為了管理的方便,內(nèi)核可以據(jù)此快速判斷某個(gè)區(qū)域中是否有dirty page或正在write back的page,而無須掃描該區(qū)域中的所有pages。

(3)Reverse Mapping

要回收一個(gè)page,可不僅僅是釋放掉那么簡單,別忘了linux中進(jìn)程和內(nèi)核都是使用虛擬地址的,多少個(gè)PTE頁表項(xiàng)還指向這個(gè)page呢,回收之前,需要將這些PTE中P標(biāo)志位設(shè)為0(not present),同時(shí)將page的物理頁面號PFN也全部設(shè)成0,要不然下次PTE指向的位置存放的就是無效的數(shù)據(jù)了。可是struct page中好像并沒有一個(gè)維護(hù)所有指向這個(gè)page的PTE組成的鏈表。

前面的文章說過,struct page數(shù)量極其龐大,如果每個(gè)page都有這樣一個(gè)鏈表,那將顯著增加內(nèi)存占用,而且PTE中的內(nèi)容是在不斷變化的,維護(hù)這一鏈表的開銷也是不小的。那如何找到這些PTE呢?從虛擬地址映射到物理地址是正向映射,而通過物理頁面尋址映射它的虛擬地址,叫reverse mapping(逆向映射)。page的確沒有直接指向PTE的反向指針,但是page所屬的文件是和VMA有mmap線性映射關(guān)系的啊,通過page在文件中的offset/index,就可以知道VMA中的哪個(gè)虛擬地址映射了這個(gè)page。

在代碼中的實(shí)現(xiàn)是這樣的:

__vma_address(struct page *page, struct vm_area_struct *vma)
{
	pgoff_t pgoff = page_to_pgoff(page);
	return vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
}

映射了某個(gè)address_space中至少一個(gè)page的所有進(jìn)程的所有VMA,就共同構(gòu)成了這個(gè)address_space的priority search tree(PST)。PST是一種糅合了radix tree和heap的數(shù)據(jù)結(jié)構(gòu),其實(shí)現(xiàn)較為復(fù)雜,現(xiàn)在已經(jīng)被基于augmented rbtree的interval tree所取代。

對比一下,一個(gè)進(jìn)程所含有的所有VMA是通過鏈表和紅黑樹組織起來的,一個(gè)文件所對應(yīng)的所有VMA是通過基于紅黑樹的interval tree組織起來的。因此,一個(gè)VMA被創(chuàng)建之后,需要通過vma_link()插入到這3種數(shù)據(jù)結(jié)構(gòu)中。

三、Linux內(nèi)存回收機(jī)制

2.1回收對象:匿名頁與文件頁

在 Linux 系統(tǒng)中,內(nèi)存回收主要針對匿名頁和文件頁展開。匿名頁是一種比較特殊的內(nèi)存頁,它不像文件頁那樣與磁盤上的文件存在直接映射關(guān)系,通常用于存儲進(jìn)程的堆、棧數(shù)據(jù)等 。當(dāng)系統(tǒng)需要回收匿名頁時(shí),會(huì)篩選出那些訪問頻率較低、不經(jīng)常使用的匿名頁,將它們寫入到 swap 分區(qū)中。swap 分區(qū)就像是內(nèi)存的 “臨時(shí)倉庫”,當(dāng)內(nèi)存空間緊張時(shí),把暫時(shí)不用的數(shù)據(jù)存放到這里,等需要時(shí)再取回來。寫入 swap 分區(qū)后,這些匿名頁就可以作為空閑頁框釋放到伙伴系統(tǒng),供其他進(jìn)程申請使用,從而有效緩解內(nèi)存壓力。

文件頁則涵蓋了內(nèi)核緩存的磁盤數(shù)據(jù)(Buffer)以及內(nèi)核緩存的文件數(shù)據(jù)(Cache)。在回收文件頁時(shí),系統(tǒng)會(huì)先判斷文件頁的狀態(tài)。如果文件頁保存的內(nèi)容與磁盤中文件對應(yīng)內(nèi)容一致,即該文件頁是干凈的,那么無需進(jìn)行回寫操作,可直接將其作為空閑頁框釋放到伙伴系統(tǒng);反之,如果文件頁保存的數(shù)據(jù)和磁盤中文件對應(yīng)的數(shù)據(jù)不一致,這樣的文件頁被稱為臟頁,就需要先將其回寫到磁盤中對應(yīng)數(shù)據(jù)所在的位置,確保數(shù)據(jù)的一致性,然后才能作為空閑頁框釋放 。

例如,當(dāng)我們編輯一個(gè)文本文件時(shí),在保存之前,文件在內(nèi)存中的對應(yīng)頁就是臟頁,只有保存后,數(shù)據(jù)寫入磁盤,相應(yīng)的文件頁才會(huì)變成干凈頁。通過這種有針對性的回收策略,系統(tǒng)能夠合理地管理內(nèi)存資源,提高內(nèi)存的使用效率。

2.2zone:內(nèi)存回收的基本單位

在 Linux 系統(tǒng)中,內(nèi)存回收是以 zone 為基本單位進(jìn)行的。zone 是對內(nèi)存的一種邏輯劃分,它將物理內(nèi)存按照不同的特性和用途進(jìn)行分類管理,主要包括 DMA zone、Normal zone 和 HighMem zone 等 。不同的 zone 適用于不同類型的內(nèi)存訪問需求,例如,DMA zone 主要用于直接內(nèi)存訪問設(shè)備,Normal zone 用于常規(guī)的內(nèi)存分配,而 HighMem zone 用于高端內(nèi)存的管理。

在每個(gè) zone 中,都有三條重要的閾值線,即 watermark [WMARK_MIN](最小閾值)、watermark [WMARK_LOW](低閾值)和 watermark [WMARK_HIGH](高閾值),它們在內(nèi)存分配和回收過程中起著關(guān)鍵的判斷和觸發(fā)作用。當(dāng)系統(tǒng)進(jìn)行內(nèi)存分配時(shí),如果是快速分配,默認(rèn)會(huì)以 watermark [WMARK_LOW] 作為閾值進(jìn)行判斷。

如果某個(gè) zone 的空閑頁數(shù)量低于這個(gè)低閾值,說明該 zone 的內(nèi)存資源較為緊張,系統(tǒng)會(huì)立即對該 zone 執(zhí)行快速內(nèi)存回收操作,以獲取更多的空閑內(nèi)存,滿足當(dāng)前的內(nèi)存分配請求 。比如,當(dāng)一個(gè)新的進(jìn)程啟動(dòng)需要申請內(nèi)存時(shí),如果發(fā)現(xiàn)所在 zone 的空閑頁數(shù)量低于低閾值,系統(tǒng)就會(huì)迅速啟動(dòng)快速內(nèi)存回收,優(yōu)先保障新進(jìn)程的內(nèi)存需求。

若快速內(nèi)存分配失敗,系統(tǒng)會(huì)進(jìn)入慢速分配階段,此時(shí)會(huì)使用 watermark [WMARK_MIN] 這個(gè)最小閾值進(jìn)行內(nèi)存分配。如果即使使用最小閾值也無法完成內(nèi)存分配,那就意味著系統(tǒng)內(nèi)存極度緊張,會(huì)觸發(fā)直接內(nèi)存回收以及快速內(nèi)存回收機(jī)制,盡力從各個(gè)方面回收內(nèi)存,避免因內(nèi)存不足導(dǎo)致系統(tǒng)出現(xiàn)異常。

而 watermark [WMARK_HIGH] 代表著 zone 對于空閑頁數(shù)量比較滿意的一個(gè)數(shù)值狀態(tài) 。當(dāng) zone 的空閑頁數(shù)量高于這個(gè)高閾值時(shí),說明該 zone 的內(nèi)存資源充足,系統(tǒng)處于比較良好的運(yùn)行狀態(tài);當(dāng)對 zone 進(jìn)行內(nèi)存回收時(shí),通常會(huì)將目標(biāo)設(shè)定為把 zone 的空閑頁數(shù)量提高到此高閾值以上,使內(nèi)存資源達(dá)到一個(gè)較為理想的平衡狀態(tài) 。在系統(tǒng)運(yùn)行過程中,通過不斷地根據(jù)這三條閾值線對內(nèi)存進(jìn)行監(jiān)控和調(diào)整,Linux 系統(tǒng)能夠有效地管理內(nèi)存資源,保障系統(tǒng)的穩(wěn)定運(yùn)行和高效性能。我們可以通過/proc/zoneinfo文件查看各個(gè) zone 的這三個(gè)閾值的具體數(shù)值,以便更好地了解系統(tǒng)內(nèi)存狀態(tài)。

四、Linux內(nèi)存回收的方式

4.1zone的閥值

內(nèi)存回收是以zone為單位進(jìn)行的(也會(huì)以memcg為單位,這里不討論這種情況),而系統(tǒng)判斷一個(gè)zone需不需要進(jìn)行內(nèi)存回收,如上面所說,為zone設(shè)置一條線,當(dāng)此zone的空閑頁框不足以到達(dá)這條線時(shí),就會(huì)對此zone進(jìn)行內(nèi)存回收,實(shí)際上一個(gè)zone有三條線,這三條線分別是最小閥值(WMARK_MIN),低閥值(WMARK_LOW),高閥值(WMARK_HIGH),它們都保存在zone的watermark[NR_WMARK]數(shù)組中,這個(gè)數(shù)組中保存的是各個(gè)閥值要求的頁框數(shù)量,而每個(gè)閥值都會(huì)對內(nèi)存回收造成影響。而它們的描述如下:

  • watermark[WMARK_MIN](min閥值):在快速分配失敗后的慢速分配中會(huì)使用此閥值進(jìn)行分配,如果慢速分配過程中使用此值還是無法進(jìn)行分配,那就會(huì)執(zhí)行直接內(nèi)存回收和快速內(nèi)存回收
  • watermark[WMARK_LOW](low閥值):也叫低閥值,是快速分配的默認(rèn)閥值,在分配內(nèi)存過程中,如果zone的空閑頁框數(shù)量低于此閥值,系統(tǒng)會(huì)對zone執(zhí)行快速內(nèi)存回收
  • watermark[WMARK_HIGH](high閥值):也叫高閥值,是zone對于空閑頁框數(shù)量比較滿意的一個(gè)值,當(dāng)zone的空閑頁框數(shù)量高于這個(gè)值時(shí),表示zone的空閑頁框較多。所以對zone進(jìn)行內(nèi)存回收時(shí),目標(biāo)也是希望將zone的空閑頁框數(shù)量提高到此值以上,系統(tǒng)會(huì)使用此閥值用于oomkill進(jìn)行內(nèi)存回收。

這三個(gè)閥值的關(guān)系是:min閥值 < low閥值 < high閥值。在系統(tǒng)初始化期間,根據(jù)系統(tǒng)中整個(gè)內(nèi)存的數(shù)量與每個(gè)zone管理的頁框數(shù)量,計(jì)算出每個(gè)zone的min閥值,然后low閥值 = min閥值 + (min閥值 / 4),high閥值 = min閥值 + (min閥值 / 2)。這樣就得出了這三個(gè)閥值的數(shù)值,我們可以通過/proc/zoneinfo中查看這三個(gè)閥值的數(shù)值:

可以很明顯看出來,相對于整個(gè)zone管理的總頁框數(shù)量(managed),這三個(gè)值是非常非常小的,連managed的1%都不到,這些都是在系統(tǒng)初始化期間進(jìn)行設(shè)置的,具體設(shè)置函數(shù)是__setup_per_zone_wmarks()。有興趣的可以去看看。這個(gè)閥值對內(nèi)存回收的進(jìn)行具有很重要的意義,后面會(huì)詳細(xì)進(jìn)行說明。

對于zone的內(nèi)存回收,它針對三樣?xùn)|西進(jìn)程回收:slab、lru鏈表中的頁、buffer_head。這里只討論內(nèi)存回收針對lru鏈表中的頁是如何進(jìn)行回收的。lru鏈表主要用于管理進(jìn)程空間中使用的內(nèi)存頁,它主要管理三種類型的頁:匿名頁、文件頁以及shmem使用的頁。在內(nèi)存回收過程中,說簡單些,就是將lru鏈表中的一些頁數(shù)據(jù)放到磁盤中,然后將這些頁釋放,當(dāng)然實(shí)際上可沒有那么簡單,這個(gè)后面會(huì)詳細(xì)說明。

在說內(nèi)存回收前,要先補(bǔ)充一些知識,因?yàn)閮?nèi)存回收并不是一個(gè)孤立的功能,它內(nèi)部會(huì)涉及到其他很多東西,比如內(nèi)存分配、lru鏈表、反向映射、swapcache、pagecache等。

(1)頁描述符頁描述符中對內(nèi)存回收來說非常必要的標(biāo)志:

  • PG_lru:表示頁在lru鏈表中
  • PG_referenced: 表示頁最近被訪問(只有文件頁使用)
  • PG_dirty:頁為臟頁,文件頁被修改,以及非文件頁加入到swap cache后,就會(huì)被標(biāo)記為臟頁。在此頁回寫前會(huì)被清除,但是回寫失敗時(shí)又會(huì)被置位
  • PG_active:頁為活動(dòng)頁,配合PG_lru就可以得出頁是處于非活動(dòng)頁lru鏈表還是活動(dòng)頁lru鏈表
  • PG_private:頁描述符中的page->private保存有數(shù)據(jù)
  • PG_writeback:頁正在進(jìn)行回寫
  • PG_swapbacked:此頁可寫入swap分區(qū),一般用于表示此頁是非文件頁
  • PG_swapcache:頁已經(jīng)加入到了swap cache中(只有非文件頁使用)
  • PG_reclaim:頁正在進(jìn)行回收,只有在內(nèi)存回收時(shí)才會(huì)對需要回收的頁進(jìn)行此標(biāo)記
  • PG_mlocked:頁被鎖在內(nèi)存中

在內(nèi)核中,只有一種頁能夠進(jìn)行回收,就是頁描述符中的_count為0的頁,每個(gè)頁都有自己唯一的頁描述符,而每個(gè)頁描述符中都有一個(gè)_count,這個(gè)_count代表的是此頁的引用計(jì)數(shù),當(dāng)_count為-1時(shí),說明此頁是空閑的,存放在伙伴系統(tǒng)中,每當(dāng)有一個(gè)進(jìn)程映射了此頁時(shí),此頁的_count就會(huì)++,也就是當(dāng)某個(gè)頁被10個(gè)進(jìn)程映射了,它的page->_count肯定大于10(不等于10是因?yàn)榭赡苓€有其他模塊引用了此頁,比如塊層、驅(qū)動(dòng)等),所以也可以反過來說,如果某個(gè)頁的page->_count == 0,那就說明此頁可以直接釋放回收了。

也就是說,內(nèi)核實(shí)際上回收的是那些page->_count == 0的頁,但是如果真的是這樣,內(nèi)存回收這就沒有任何意義了,因?yàn)楫?dāng)最后一個(gè)引用此頁的模塊釋放掉此頁的引用時(shí),如果page->_count為0,肯定會(huì)釋放回收此頁的。實(shí)際上內(nèi)存回收做的事情,就是想辦法將一些page->_count不為0的頁,嘗試將它們的page->_count降到0,這樣系統(tǒng)就可以回收這些頁了。下面是我總結(jié)出來在內(nèi)存回收過程中會(huì)對頁的page->_count產(chǎn)生影響的操作:

  • 一個(gè)進(jìn)程映射此頁,page->_count++
  • 一個(gè)進(jìn)程取消映射此頁,page->_count--
  • 此頁加入到lru緩存中,page->_count++
  • 此頁從lru緩存加入到lru鏈表中,page->_count--
  • 此頁被加入到一個(gè)address_space中,page->_count++
  • 此頁從address_space中移除時(shí),page->_count--
  • 文件頁添加了buffer_heads,page->_count++
  • 文件頁刪除了buffer_heads,page->_count--
  • swap分區(qū)

4.2lru鏈表

lru鏈表主要作用就是將頁排序,將最應(yīng)該回收的頁放到最后面,最不應(yīng)該回收的頁放到最前面,,然后進(jìn)行內(nèi)存回收時(shí),就會(huì)從后面向前面進(jìn)行掃描,將掃描到的頁嘗試進(jìn)行回收。這里只需要記住一點(diǎn),回收的頁都是非活動(dòng)匿名頁lru鏈表或者非活動(dòng)文件頁lru鏈表上的頁。這些頁包括:進(jìn)程堆、棧、匿名mmap共享內(nèi)存映射、shmem共享內(nèi)存映射使用的頁、映射磁盤文件的頁。

(1)頁的換入換出

首先先說明一下頁描述符中對內(nèi)存回收來說非常必要的標(biāo)志:

  • PG_lru:表示頁在lru鏈表中
  • PG_referenced: 表示頁最近被訪問(只有文件頁使用)
  • PG_dirty:頁為臟頁,文件頁被修改,以及非文件頁加入到swap cache后,就會(huì)被標(biāo)記為臟頁。在此頁回寫前會(huì)被清除,但是回寫失敗時(shí)又會(huì)被置位
  • PG_active:頁為活動(dòng)頁,配合PG_lru就可以得出頁是處于非活動(dòng)頁lru鏈表還是活動(dòng)頁lru鏈表
  • PG_private:頁描述符中的page->private保存有數(shù)據(jù)
  • PG_writeback:頁正在進(jìn)行回寫
  • PG_swapbacked:此頁可寫入swap分區(qū),一般用于表示此頁是非文件頁
  • PG_swapcache:頁已經(jīng)加入到了swap cache中(只有非文件頁使用)
  • PG_reclaim:頁正在進(jìn)行回收,只有在內(nèi)存回收時(shí)才會(huì)對需要回收的頁進(jìn)行此標(biāo)記
  • PG_mlocked:頁被鎖在內(nèi)存中(此標(biāo)志可以保證不被換出,但是無法保證不被被做內(nèi)存遷移)

內(nèi)存回收做的事情就是想辦法將目標(biāo)頁的page->_count降到0,對于那些沒有進(jìn)程映射了頁,釋放起來就很簡單,如果頁映射了磁盤文件,并且頁為臟頁(被寫過),那就就把頁中的數(shù)據(jù)回寫到磁盤中映射的文件中,而如果頁沒有映射磁盤文件,那么直接釋放即可。但是對于有進(jìn)程映射的頁,如果此頁映射了磁盤文件,并且頁為臟頁,那么和之前一樣,將此頁進(jìn)行回寫,然后釋放回收即可,但是此頁沒有映射磁盤文件,情況就會(huì)稍微復(fù)雜,會(huì)將頁數(shù)據(jù)寫入到swap分區(qū)中,然后將此頁釋放回收。總結(jié)如下:

  • 干凈頁,并且映射了磁盤文件的頁,直接回收
  • 臟頁(PG_dirty置位),回寫到對應(yīng)磁盤文件中,然后回收
  • 沒有進(jìn)程映射,并且沒有映射磁盤文件的頁,直接回收
  • 有進(jìn)程映射,并且沒有映射磁盤文件的頁,回寫到swap分區(qū)中,然后回收

接下來會(huì)分為非活動(dòng)匿名頁lru鏈表的頁的換入換出,非活動(dòng)文件頁lru鏈表的頁的換入換出進(jìn)行描述。

匿名頁lru鏈表上保存的頁為:進(jìn)程堆、棧、數(shù)據(jù)段,匿名mmap共享內(nèi)存映射,shmem映射。這些類型的頁都有個(gè)特點(diǎn),在磁盤上沒有映射對應(yīng)的文件(shmem有對應(yīng)的文件,是/dev/zero,但它不是映射此設(shè)備文件)。而在內(nèi)存回收時(shí),會(huì)從非活動(dòng)匿名頁lru鏈表末尾向前掃描一定數(shù)量的頁框,然后嘗試將這些頁框進(jìn)行回收,而如果這些頁框沒有進(jìn)程映射它們,那么它們可以直接釋放,而如果有進(jìn)程映射了它們,那么系統(tǒng)就必須將這些頁框回寫到磁盤上。在linux系統(tǒng)中,你可以給系統(tǒng)掛載一個(gè)swap分區(qū),這個(gè)分區(qū)就是專門用于保存這些類型的頁的。

當(dāng)這些頁需要回收,并且有進(jìn)程映射了它們時(shí),系統(tǒng)就會(huì)將這些頁寫入swap分區(qū),需要注意,它們需要回收只有在內(nèi)存不足進(jìn)行內(nèi)存回收時(shí)才會(huì)發(fā)生,也就是當(dāng)系統(tǒng)內(nèi)存充足時(shí),是不會(huì)將這些類型的頁寫入到swap分區(qū)中的(使用memcg除外),在磁盤上,一個(gè)swap分區(qū)是一組連續(xù)的物理扇區(qū),比如一個(gè)1G大小的swap分區(qū),那么它在磁盤上會(huì)占有1G大小磁盤塊,然后這塊磁盤塊的第一個(gè)4K,專門用于存swap分區(qū)描述結(jié)構(gòu)的,而之后的磁盤塊,會(huì)被劃分為一個(gè)一個(gè)4K大小的頁槽(正好與普通頁大小一致),然后將它們標(biāo)以ID,如下:

每個(gè)頁槽可以保存一個(gè)頁的數(shù)據(jù),這樣,一個(gè)被換出的頁就可以寫入到磁盤中,系統(tǒng)也能夠?qū)⑦@些頁組織起來了。雖然是叫swap分區(qū),但是內(nèi)核似乎并不將swap分區(qū)當(dāng)做一個(gè)磁盤分區(qū)來看待,更像的是將其當(dāng)做一個(gè)文件來看待,因?yàn)檫@個(gè),每個(gè)swap分區(qū)都有一個(gè)address_space結(jié)構(gòu),這個(gè)結(jié)構(gòu)是每個(gè)磁盤文件都會(huì)有一個(gè)的,這個(gè)address_space結(jié)構(gòu)中最重要的是有一個(gè)基樹和一個(gè)address_space操作集。而這里swap分區(qū)有一個(gè),swap分區(qū)的address_space叫做swap cache,它的作用是從非文件頁在回寫到swap分區(qū)到此非文件頁被回收前的這段時(shí)間里,起到一個(gè)將swap類型的頁表項(xiàng)與此頁關(guān)聯(lián)的作用和同步的作用。在這個(gè)swap cache的基樹中,將此swap分區(qū)的所有頁槽組織在了一起。當(dāng)非活動(dòng)匿名頁lru鏈表中的一個(gè)頁需要寫入到swap分區(qū)時(shí),步驟如下:

  • swap分配一個(gè)空閑的頁槽
  • 根據(jù)這個(gè)空閑頁槽的ID,從swap分區(qū)的swap cache的基樹中找到此頁槽ID對應(yīng)的結(jié)點(diǎn),將此頁的頁描述符存入當(dāng)中
  • 內(nèi)核以頁槽ID作為偏移量生成一個(gè)swap頁表項(xiàng),并將這個(gè)swap頁表項(xiàng)保存到頁描述符中的private中
  • 對頁進(jìn)行反向映射,將所有映射了此頁的進(jìn)程頁表項(xiàng)改為此swap頁表項(xiàng)
  • 將此頁的mapping改為指向此swap分區(qū)的address_space,并將此頁設(shè)置為臟頁
  • 通過swap cache中的address_space操作集將此頁回寫到swap分區(qū)中
  • 回寫完成
  • 此頁要被回收,將此頁從swap cache中拿出來

當(dāng)一個(gè)進(jìn)程需要訪問此頁時(shí),系統(tǒng)則會(huì)將此頁從swap分區(qū)換入內(nèi)存中,具體步驟如下:

  • 一個(gè)進(jìn)行訪問了此頁,會(huì)先訪問到之前設(shè)置的swap頁表項(xiàng)
  • 產(chǎn)生缺頁異常,在缺頁異常中判斷此頁在swap分區(qū)中,而不在內(nèi)存中
  • 分配一個(gè)新頁
  • 根據(jù)進(jìn)程的頁表項(xiàng)中的swap頁表項(xiàng)找到對應(yīng)的頁槽和swap cache
  • 如果以頁槽ID在swap cache中沒有找到此頁,說明此頁已被回收,從分區(qū)中將此頁讀取進(jìn)來
  • 如果以頁槽ID在swap cache中找到了此頁,說明此頁還在內(nèi)存中,還沒有被回收,則直接映射此頁

這樣再此頁沒有被換出或者正在換出的情況下,所有映射了此頁的進(jìn)程又可以重新訪問此頁了,而當(dāng)此頁被完全換出到swap分區(qū)然后被回收后,此頁就會(huì)從swap cache中移除,之后如果進(jìn)程想要訪問此頁,就需要等此頁被完全換入之后才行了。也就是這個(gè)swap cache完全為了提高效率,在頁沒有被回收前,即使此頁已經(jīng)回寫到swap分區(qū)了,只要有進(jìn)映射此頁,就可以直接映射內(nèi)存中的頁,而不需要將頁從磁盤讀進(jìn)來。對于非活動(dòng)匿名頁lru鏈表上的頁進(jìn)行換入換出這里就算是說完了。記住對于非活動(dòng)匿名頁lru鏈表上的頁來說,當(dāng)此頁加入到swap cache中時(shí),那么就意味著這個(gè)頁已經(jīng)被要求換出,然后進(jìn)行回收了。

但是相反文件頁則不是這樣,接下來簡單說說映射了磁盤文件的文件頁的換入換出,實(shí)際上與非活動(dòng)匿名頁lru鏈表上的頁進(jìn)行換入換出是一模一樣的,因?yàn)槊總€(gè)磁盤文件都有一個(gè)自己的address_space,這個(gè)address_space就是swap分區(qū)的address_space,磁盤文件的address_space稱為page cache,接下來的處理就是差不多的,區(qū)別為以下三點(diǎn):

  • 對于磁盤文件來說,它的數(shù)據(jù)并不像swap分區(qū)這樣是連續(xù)的。
  • 當(dāng)文件數(shù)據(jù)讀入到一個(gè)頁時(shí),此文件頁就需要在文件的page cache中做關(guān)聯(lián),這樣當(dāng)其他進(jìn)程也需要訪問文件的這塊數(shù)據(jù)時(shí),通過page cache就可以知道此頁在不在內(nèi)存中了。
  • 并不會(huì)為映射了此文件頁的進(jìn)程頁表項(xiàng)生成一個(gè)新的頁表項(xiàng),會(huì)將所有映射了此頁的頁表項(xiàng)清空,因?yàn)樵谌表摦惓V型ㄟ^vma就可以判斷發(fā)生缺頁的頁是映射了文件的哪一部分,然后通過文件系統(tǒng)可以查到此頁在不在內(nèi)存中。而對于匿名頁的vma來說,則無法做到這一點(diǎn)。

4.3內(nèi)存分配過程

要說清楚內(nèi)存回收,就必須要先理清楚內(nèi)存分配過程,在調(diào)用alloc_page()或者alloc_pages()等接口進(jìn)行一次內(nèi)存分配時(shí),最后都會(huì)調(diào)用到__alloc_pages_nodemask()函數(shù),這個(gè)函數(shù)是內(nèi)存分配的心臟,對內(nèi)存分配流程做了一個(gè)整體的組織。主要需要注意的,就是在__alloc_pages_nodemask()中會(huì)進(jìn)行一次使用low閥值的快速內(nèi)存分配和一次使用min閥值的慢速內(nèi)存分配,快速內(nèi)存分配使用的函數(shù)是get_page_from_freelist(),這個(gè)函數(shù)是分配頁框的基本函數(shù),也就是說,在慢速內(nèi)存分配過程中,收集到和足夠數(shù)量的頁框后,也需要調(diào)用這個(gè)函數(shù)進(jìn)行分配。先簡單說明快速內(nèi)存分配和慢速內(nèi)存分配:

  • 快速內(nèi)存分配:是get_page_from_freelist()函數(shù),通過low閥值從zonelist中獲取合適的zone進(jìn)行分配,如果zone沒有達(dá)到low閥值,則會(huì)進(jìn)行快速內(nèi)存回收,快速內(nèi)存回收后再嘗試分配。
  • 慢速內(nèi)存分配:當(dāng)快速分配失敗后,也就是zonelist中所有zone在快速分配中都沒有獲取到內(nèi)存,則會(huì)使用min閥值進(jìn)行慢速分配,在慢速分配過程中主要做三件事,異步內(nèi)存壓縮、直接內(nèi)存回收以及輕同步內(nèi)存壓縮,最后視情況進(jìn)行oom分配。并且在這些操作完成后,都會(huì)調(diào)用一次快速內(nèi)存分配嘗試獲取頁框。

通過以下這幅圖,來說明流程:

說到內(nèi)存分配過程,就必須要說說中的preferred_zone和zonelist,preferred_zone可以理解為內(nèi)存分配時(shí),最希望從這個(gè)zone進(jìn)行分配,而zonelist理解為,當(dāng)沒辦法從preferred_zone分配內(nèi)存時(shí),則根據(jù)zonelist中zone的順序嘗試進(jìn)行分配,為什么會(huì)有這兩個(gè)參數(shù),是因?yàn)閚uma架構(gòu)導(dǎo)致的,我們知道,當(dāng)有多個(gè)node結(jié)點(diǎn)時(shí),CPU跨結(jié)點(diǎn)訪問內(nèi)存是效率比較低的工作,所以CPU會(huì)優(yōu)先在本node上的zone進(jìn)行內(nèi)存分配工作,如果本node上實(shí)在分配不出內(nèi)存,那就嘗試在離本node最近的node上分配,如果還是無法分配到,那就找再下一個(gè)node。這樣每個(gè)node會(huì)將其他node的距離進(jìn)行一個(gè)排序形成了其他node的一個(gè)鏈表,這個(gè)鏈表越前面的node就表示里本node越近,越后面的node就離本node越遠(yuǎn)。

而在32位系統(tǒng)中,每個(gè)node有3個(gè)zone,分別是ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA。每個(gè)區(qū)管理的內(nèi)存數(shù)量不一樣,導(dǎo)致每個(gè)區(qū)的優(yōu)先級不同,優(yōu)先級為ZONE_HIGHMEM > ZONE_NORMAL > ZONE_DMA,對于進(jìn)程使用的頁,系統(tǒng)優(yōu)先分配ZONE_HIGHMEM的頁框,如果ZONE_HIGHMEM無法分配頁框,則從ZONE_NORMAL進(jìn)行分配,當(dāng)然,對于內(nèi)核使用的頁來說,大部分只會(huì)從ZONE_NORMAL和ZONE_DMA進(jìn)行分配,這樣,將這個(gè)zone優(yōu)先級與node鏈表結(jié)合,就得到zonelist鏈表了,比如對于node0,它完整的zonelist鏈表就可能如下:

node0的管理區(qū)                                                                       node1的管理區(qū)

  ZONE_HIGHMEM(0) -> ZONE_NORMAL(0) -> ZONE_DMA(0) -> ZONE_HIGHMEM(1) -> ZONE_NORMAL(1) -> ZONE_DMA(1)

因?yàn)槊總€(gè)node都有自己完整的zonelist鏈表,所以對于node1,它的鏈表時(shí)這樣的

  node1的管理區(qū)                                                                       node0的管理區(qū)

  ZONE_HIGHMEM(1) -> ZONE_NORMAL(1) -> ZONE_DMA(1) -> ZONE_HIGHMEM(0) -> ZONE_NORMAL(0) -> ZONE_DMA(0)

  這樣得到了兩個(gè)node自己的zonelist,但是在內(nèi)存分配中,還不一定會(huì)使用node自己的zonelist,因?yàn)橛行﹥?nèi)存只希望從ZONE_NORMAL和ZONE_DMA中進(jìn)行分配,所以,在每次進(jìn)行內(nèi)存分配時(shí),都會(huì)此次內(nèi)存分配形成一個(gè)滿足的zonelist,比如:某次內(nèi)存分配在node0的CPU上執(zhí)行了,希望從ZONE_NORMAL和ZONEDMA區(qū)中進(jìn)行分配,那么就會(huì)形成下面這個(gè)鏈表

  node0的管理區(qū)                                      node1的管理區(qū)

  ZONE_NORMAL(0) -> ZONE_DMA(0) -> ZONE_NORMAL(1) -> ZONE_DMA(1)

  這樣就是preferred_zone和zonelist,preferred_zone一般都是指向zonelist中的第一個(gè)zone,當(dāng)然這個(gè)還會(huì)跟nodemask有關(guān),這個(gè)就不細(xì)說了。

4.4掃描控制結(jié)構(gòu)

之前說內(nèi)存壓縮的文章也有涉及這個(gè)結(jié)構(gòu),現(xiàn)在詳細(xì)說明一下,掃描控制結(jié)構(gòu)用于內(nèi)存回收和內(nèi)存壓縮,它的主要作用時(shí)保存對一次內(nèi)存回收或者內(nèi)存壓縮的變量和參數(shù),一些處理結(jié)果也會(huì)保存在里面,結(jié)構(gòu)如下:

/* 掃描控制結(jié)構(gòu),用于內(nèi)存回收和內(nèi)存壓縮 */
struct scan_control {
    /* 需要回收的頁框數(shù)量 */
    unsigned long nr_to_reclaim;

    /* 申請內(nèi)存時(shí)使用的分配標(biāo)志 */
    gfp_t gfp_mask;

    /* 申請內(nèi)存時(shí)使用的order值,因?yàn)橹挥猩暾垉?nèi)存,然后內(nèi)存不足時(shí)才會(huì)進(jìn)行掃描 */
    int order;

    /* 允許執(zhí)行掃描的node結(jié)點(diǎn)掩碼 */
    nodemask_t    *nodemask;

    /* 目標(biāo)memcg,如果是針對整個(gè)zone進(jìn)行的,則此為NULL */
    struct mem_cgroup *target_mem_cgroup;

    /* 掃描優(yōu)先級,代表一次掃描(total_size >> priority)個(gè)頁框 
     * 優(yōu)先級越低,一次掃描的頁框數(shù)量就越多
     * 優(yōu)先級越高,一次掃描的數(shù)量就越少
     * 默認(rèn)優(yōu)先級為12
     */
    int priority;

    /* 是否能夠進(jìn)行回寫操作(與分配標(biāo)志的__GFP_IO和__GFP_FS有關(guān)) */
    unsigned int may_writepage:1;

    /* 能否進(jìn)行unmap操作,就是將所有映射了此頁的頁表項(xiàng)清空 */
    unsigned int may_unmap:1;

    /* 是否能夠進(jìn)行swap交換,如果不能,在內(nèi)存回收時(shí)則不掃描匿名頁lru鏈表 */
    unsigned int may_swap:1;

    unsigned int hibernation_mode:1;

    /* 掃描結(jié)束后會(huì)標(biāo)記,用于內(nèi)存回收判斷是否需要進(jìn)行內(nèi)存壓縮 */
    unsigned int compaction_ready:1;

    /* 已經(jīng)掃描的頁框數(shù)量 */
    unsigned long nr_scanned;
    /* 已經(jīng)回收的頁框數(shù)量 */
    unsigned long nr_reclaimed;
};

結(jié)構(gòu)很簡單,主要就是保存一些參數(shù),在內(nèi)存回收和內(nèi)存壓縮時(shí)就會(huì)根據(jù)這個(gè)結(jié)構(gòu)中的這些參數(shù),做不同的處理,后面代碼會(huì)詳細(xì)說明。這里我們只說說會(huì)幾個(gè)特別的參數(shù):

  • priority:優(yōu)先級,這個(gè)參數(shù)主要會(huì)影響內(nèi)存回收時(shí)一次掃描的頁框數(shù)量、在shrink_lruvec()中回收到足夠頁框后是否繼續(xù)回收、內(nèi)存回收時(shí)的回寫、是否取消對zone進(jìn)行回收判斷而直接開始回收,一共四個(gè)地方。
  • may_unmap:是否能夠進(jìn)行unmap操作,如果不能進(jìn)行unmap操作,就只能對沒有進(jìn)程映射的頁進(jìn)行回收。
  • may_writepage:是否能夠進(jìn)行將頁回寫到磁盤的操作,這個(gè)值會(huì)影響臟的文件頁與匿名頁lru鏈表中的頁的回收,如果不能進(jìn)行回寫操作,臟頁和匿名頁lru鏈表中的頁都不能進(jìn)行回收(已經(jīng)回寫完成的頁除外,后面解釋)
  • may_swap:能否進(jìn)行swap交換,同樣影響匿名頁lru鏈表中的頁的回收,如果不能進(jìn)行swap交換,就不會(huì)對匿名頁lru鏈表進(jìn)行掃描,也就是在本次內(nèi)存回收中,完全不會(huì)回收匿名頁lru鏈表中的頁(進(jìn)程堆、棧、shmem共享內(nèi)存、匿名mmap共享內(nèi)存使用的頁)

在快速內(nèi)存回收、直接內(nèi)存回收、kswapd內(nèi)存回收中,這幾個(gè)值的設(shè)置不一定會(huì)一致,也導(dǎo)致了它們對不同類型的頁處理方式也不同。除了sc->may_writepage會(huì)影響頁的回寫外,還有進(jìn)行內(nèi)存分配時(shí)使用的分配標(biāo)志gfp_mask中的__GFP_IO和__GFP_FS會(huì)影響頁的回寫,具體如下:

  • 掃描到的非活動(dòng)匿名頁lru鏈表中的頁如果還沒有加入到swapcache中,需要有__GFP_IO標(biāo)記才允許加入swapcache和回寫。
  • 掃描到的非活動(dòng)匿名頁lru鏈表中的頁如果已經(jīng)加入到了swapcache中,需要有__GFP_FS才允許進(jìn)行回寫。
  • 掃描到的非活動(dòng)文件頁lru鏈表中的頁需要有__GFP_FS才允許進(jìn)行回寫。

這里還需要說說三個(gè)重要的內(nèi)核配置:

/proc/sys/vm/zone_reclaim_mode

這個(gè)參數(shù)只會(huì)影響快速內(nèi)存回收,其值有三種,

  • 0x1:開啟zone的內(nèi)存回收
  • 0x2:開啟zone的內(nèi)存回收,并且允許回寫
  • 0x4:開啟zone的內(nèi)存回收,允許進(jìn)行unmap操作

當(dāng)此參數(shù)為0時(shí),會(huì)導(dǎo)致快速內(nèi)存回收只會(huì)對最優(yōu)zone附近的幾個(gè)需要進(jìn)行內(nèi)存回收的zone進(jìn)行內(nèi)存回收(說快速內(nèi)存會(huì)解釋),而只要不為0,就會(huì)對zonelist中所有應(yīng)該進(jìn)行內(nèi)存回收的zone進(jìn)行內(nèi)存回收。

當(dāng)此參數(shù)為0x1(001)時(shí),就如上面一行所說,允許快速內(nèi)存回收對zonelist中所有應(yīng)該進(jìn)行內(nèi)存回收的zone進(jìn)行內(nèi)存回收。

當(dāng)此參數(shù)為0x2(010)時(shí),在0x1的基礎(chǔ)上,允許快速內(nèi)存回收進(jìn)行匿名頁lru鏈表中的頁的回寫操作。

當(dāng)此參數(shù)0x4(100)時(shí),在0x1的基礎(chǔ)上,允許快速內(nèi)存回收進(jìn)行頁的unmap操作。

/proc/sys/vm/laptop_mode

此參數(shù)只會(huì)影響直接內(nèi)存回收,只有兩個(gè)值:

  • 0:允許直接內(nèi)存回收對匿名頁lru鏈表中的頁進(jìn)行回寫操作,并且允許直接內(nèi)存回收喚醒flush內(nèi)核線程
  • 非0:直接內(nèi)存回收不會(huì)對匿名頁lru鏈表中的頁進(jìn)行回寫操作
/proc/sys/vm/swapiness

此參數(shù)影響進(jìn)行內(nèi)存回收時(shí),掃描匿名頁lru鏈表和掃描文件頁lru鏈表的比例,范圍是0~200,系統(tǒng)默認(rèn)是30:

  • 接近0:進(jìn)行內(nèi)存回收時(shí),更多地去掃描文件頁lru鏈表,如果為0,那么就不會(huì)去掃描匿名頁lru鏈表。
  • 接近200:進(jìn)行內(nèi)存回收時(shí),更多地去掃描匿名頁lru鏈表。

五、內(nèi)存回收實(shí)現(xiàn)方式

5.1頁面回收與LRU算法

頁面回收是 Linux 內(nèi)存回收機(jī)制的基礎(chǔ)環(huán)節(jié),其核心在于精準(zhǔn)地識別并釋放那些不再被頻繁使用的內(nèi)存頁面,而 LRU(Least Recently Used)算法則在這一過程中扮演著 “篩選器” 的關(guān)鍵角色 。LRU 算法基于一個(gè)簡單而有效的假設(shè):如果一個(gè)頁面在過去很長一段時(shí)間內(nèi)都未被訪問,那么在未來的短時(shí)間內(nèi),它被訪問的概率也相對較低。這就好比圖書館里的書籍,如果某本書籍長時(shí)間無人借閱,那么在接下來的一段時(shí)間里,它被借閱的可能性也不大,就可以考慮將其從常用書架上移除,為其他更受歡迎的書籍騰出空間。

在 Linux 系統(tǒng)中,內(nèi)核通過維護(hù)一個(gè)雙向鏈表來實(shí)現(xiàn) LRU 算法。鏈表中的每個(gè)節(jié)點(diǎn)都代表一個(gè)內(nèi)存頁面,每當(dāng)一個(gè)頁面被訪問時(shí),它就會(huì)被移動(dòng)到鏈表的頭部,表示它是最近被使用的頁面;而鏈表尾部的頁面則是最近最少使用的,當(dāng)系統(tǒng)需要回收內(nèi)存時(shí),就會(huì)優(yōu)先從鏈表尾部選擇頁面進(jìn)行回收 。以瀏覽器的頁面緩存為例,當(dāng)我們頻繁瀏覽不同的網(wǎng)頁時(shí),瀏覽器會(huì)將最近訪問的網(wǎng)頁頁面緩存到內(nèi)存中,采用 LRU 算法管理這些緩存頁面。

如果內(nèi)存不足,瀏覽器就會(huì)將鏈表尾部,也就是那些長時(shí)間未被訪問的網(wǎng)頁頁面緩存回收,釋放出內(nèi)存空間,以便緩存新的網(wǎng)頁頁面,確保瀏覽器能夠高效運(yùn)行。通過這種方式,LRU 算法能夠有效地管理內(nèi)存頁面,使得系統(tǒng)能夠及時(shí)回收不再使用的頁面,將釋放的內(nèi)存重新分配給其他急需內(nèi)存的進(jìn)程,從而提高內(nèi)存的使用效率,保障系統(tǒng)的穩(wěn)定運(yùn)行。

5.2頁面交換:內(nèi)存與磁盤的 “互動(dòng)”

頁面交換是 Linux 內(nèi)存回收機(jī)制應(yīng)對內(nèi)存不足的重要手段,它建立在虛擬內(nèi)存技術(shù)的基礎(chǔ)之上,實(shí)現(xiàn)了內(nèi)存與磁盤之間的數(shù)據(jù)交換,就像在倉庫與臨時(shí)存儲點(diǎn)之間搬運(yùn)貨物,以解決倉庫空間不足的問題。當(dāng)系統(tǒng)內(nèi)存緊張時(shí),那些不活躍的頁面,也就是長時(shí)間未被訪問的頁面,會(huì)被操作系統(tǒng)視為 “暫時(shí)不需要的貨物”,從物理內(nèi)存中移出,交換到磁盤上的交換分區(qū)(Swap Partition)中,這個(gè)過程被稱為 “換出”(Swap Out) 。交換分區(qū)就像是內(nèi)存的 “備份倉庫”,專門用于存儲這些被換出的頁面。

當(dāng)這些被換出的頁面在未來某個(gè)時(shí)刻又需要被訪問時(shí),操作系統(tǒng)會(huì)將其從交換分區(qū)重新調(diào)入內(nèi)存,這個(gè)過程被稱為 “換入”(Swap In) 。在 Linux 系統(tǒng)中,頁面交換由內(nèi)核的頁替換算法自動(dòng)執(zhí)行,常見的算法如 LRU 算法在這一過程中發(fā)揮著重要作用,它幫助系統(tǒng)確定哪些頁面是最不活躍的,應(yīng)該被優(yōu)先換出 。例如,當(dāng)我們在使用 Linux 系統(tǒng)進(jìn)行多任務(wù)處理時(shí),同時(shí)運(yùn)行多個(gè)大型程序,隨著內(nèi)存逐漸被占用,系統(tǒng)會(huì)將一些暫時(shí)不使用的程序頁面換出到交換分區(qū),如后臺運(yùn)行的數(shù)據(jù)庫程序中一些不常用的數(shù)據(jù)頁面。

當(dāng)這些程序再次需要這些頁面時(shí),系統(tǒng)又會(huì)及時(shí)將它們從交換分區(qū)換入內(nèi)存,確保程序能夠正常運(yùn)行。雖然頁面交換機(jī)制有效地增加了系統(tǒng)的可用內(nèi)存,但頻繁的頁面交換會(huì)導(dǎo)致系統(tǒng)的磁盤 I/O 負(fù)載過高,因?yàn)榇疟P的讀寫速度遠(yuǎn)遠(yuǎn)低于內(nèi)存,這就好比頻繁地在倉庫與臨時(shí)存儲點(diǎn)之間搬運(yùn)貨物,會(huì)耗費(fèi)大量的時(shí)間和精力,進(jìn)而影響系統(tǒng)的響應(yīng)速度。因此,在實(shí)際應(yīng)用中,需要合理地設(shè)置交換分區(qū)的大小和內(nèi)核的頁面交換算法,以及優(yōu)化系統(tǒng)的內(nèi)存使用方式,以避免過度使用交換分區(qū),保障系統(tǒng)的性能。

5.3內(nèi)存壓縮:向空間要效率

內(nèi)存壓縮是 Linux 內(nèi)存回收機(jī)制中一項(xiàng)旨在提高內(nèi)存使用效率、減少磁盤 I/O 的創(chuàng)新技術(shù),它通過運(yùn)用高效的壓縮算法,對那些不活躍的頁面進(jìn)行壓縮處理,從而在有限的內(nèi)存空間中存儲更多的數(shù)據(jù),就像將蓬松的物品壓縮成緊湊的狀態(tài),以節(jié)省存儲空間。當(dāng)系統(tǒng)內(nèi)存不足時(shí),傳統(tǒng)的頁面交換機(jī)制會(huì)將不活躍頁面寫入磁盤交換分區(qū),這一過程伴隨著大量的磁盤 I/O 操作,嚴(yán)重影響系統(tǒng)性能。而內(nèi)存壓縮機(jī)制則另辟蹊徑,它將不活躍頁面在內(nèi)存中直接進(jìn)行壓縮,然后存儲在內(nèi)存的特定區(qū)域,避免了頻繁的磁盤 I/O 。

在 Linux 系統(tǒng)中,內(nèi)存壓縮機(jī)制通常借助 zRAM 等技術(shù)來實(shí)現(xiàn)。zRAM 虛擬出一個(gè)塊設(shè)備,當(dāng)系統(tǒng)觸發(fā)內(nèi)存回收時(shí),會(huì)先從系統(tǒng)中查找不活躍的內(nèi)存頁面,然后將這些頁面發(fā)送到 zRAM 設(shè)備。zRAM 設(shè)備接收到頁面后,會(huì)使用特定的壓縮算法,如 lzo、lz4 等對頁面進(jìn)行壓縮,將壓縮后的數(shù)據(jù)存儲在內(nèi)存中 。當(dāng)進(jìn)程需要訪問這些被壓縮的頁面時(shí),系統(tǒng)會(huì)先從 zRAM 設(shè)備中讀取壓縮數(shù)據(jù),然后進(jìn)行解壓縮,將解壓縮后的頁面重新放置在內(nèi)存中供進(jìn)程使用 。

以手機(jī)系統(tǒng)為例,在運(yùn)行多個(gè)應(yīng)用程序時(shí),內(nèi)存資源容易緊張。采用內(nèi)存壓縮機(jī)制后,系統(tǒng)可以將后臺應(yīng)用程序中不活躍的頁面進(jìn)行壓縮,如壓縮圖片處理應(yīng)用在后臺時(shí)占用的大量圖像數(shù)據(jù)頁面,將其壓縮后存儲在內(nèi)存中,為前臺運(yùn)行的應(yīng)用程序騰出更多內(nèi)存空間,同時(shí)避免了將這些頁面交換到磁盤,減少了磁盤 I/O 操作,提高了系統(tǒng)的整體性能,使得手機(jī)在多任務(wù)處理時(shí)更加流暢。內(nèi)存壓縮機(jī)制在一定程度上緩解了內(nèi)存壓力,提高了系統(tǒng)性能,是 Linux 內(nèi)存回收機(jī)制中一項(xiàng)重要的優(yōu)化技術(shù)。

5.4匿名頁面丟棄:特殊情況下的內(nèi)存釋放

匿名頁面丟棄是 Linux 內(nèi)存回收機(jī)制在特定情況下采取的一種內(nèi)存釋放策略,主要針對那些不屬于文件系統(tǒng)緩存的匿名頁面,這些頁面通常由進(jìn)程的堆棧和堆分配產(chǎn)生,就像臨時(shí)搭建的帳篷,在不需要時(shí)可以拆除以騰出空間。當(dāng)系統(tǒng)內(nèi)存極度緊張,且其他內(nèi)存回收機(jī)制無法滿足內(nèi)存需求時(shí),匿名頁面丟棄機(jī)制就會(huì)啟動(dòng) 。

在這種情況下,操作系統(tǒng)會(huì)對匿名頁面進(jìn)行評估,選擇那些可以安全丟棄的頁面。對于進(jìn)程堆棧和堆分配的匿名頁面,如果這些頁面中的數(shù)據(jù)在后續(xù)操作中可以重新生成,或者對進(jìn)程的正常運(yùn)行沒有直接影響,那么它們就有可能被丟棄 。例如,在一些計(jì)算密集型的進(jìn)程中,堆棧中可能會(huì)臨時(shí)存儲一些中間計(jì)算結(jié)果,這些結(jié)果在計(jì)算完成后可以通過重新計(jì)算得到,當(dāng)系統(tǒng)內(nèi)存不足時(shí),這些匿名頁面就可以被丟棄,釋放出內(nèi)存空間 。

不過,匿名頁面丟棄機(jī)制的實(shí)施需要謹(jǐn)慎,因?yàn)殄e(cuò)誤地丟棄關(guān)鍵的匿名頁面可能會(huì)導(dǎo)致進(jìn)程崩潰或數(shù)據(jù)丟失。因此,Linux 系統(tǒng)在執(zhí)行匿名頁面丟棄操作時(shí),會(huì)嚴(yán)格遵循一定的規(guī)則和條件,確保丟棄的頁面不會(huì)對系統(tǒng)和進(jìn)程的正常運(yùn)行造成損害 。匿名頁面丟棄機(jī)制為 Linux 系統(tǒng)在極端內(nèi)存壓力下提供了一種有效的內(nèi)存釋放手段,保障了系統(tǒng)的基本運(yùn)行和關(guān)鍵進(jìn)程的正常執(zhí)行 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2013-04-01 10:07:19

Java內(nèi)存回收機(jī)制

2012-08-13 10:19:03

IBMdW

2011-07-04 13:12:04

JavaScript

2011-01-18 14:06:58

JavaScriptweb

2009-12-09 17:28:34

PHP垃圾回收機(jī)制

2010-09-26 16:42:04

JVM內(nèi)存組成JVM垃圾回收

2023-02-28 07:56:07

V8內(nèi)存管理

2011-07-04 16:48:56

JAVA垃圾回收機(jī)制GC

2017-06-12 17:38:32

Python垃圾回收引用

2019-09-27 09:13:55

Redis內(nèi)存機(jī)制

2011-05-26 15:41:25

java虛擬機(jī)

2021-08-27 09:35:18

邊緣安全

2017-08-17 15:40:08

大數(shù)據(jù)Python垃圾回收機(jī)制

2010-09-25 15:33:19

JVM垃圾回收

2017-03-03 09:26:48

PHP垃圾回收機(jī)制

2009-06-23 14:15:00

Java垃圾回收

2020-12-17 13:54:49

網(wǎng)絡(luò)安全

2021-12-07 08:01:33

Javascript 垃圾回收機(jī)制前端

2010-10-13 10:24:38

垃圾回收機(jī)制JVMJava
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 精品国产乱码一区二区三区a | 国产日韩一区二区三区 | 国产一区999 | 一区二区三区四区在线播放 | 国产精品69毛片高清亚洲 | 一区二区三区不卡视频 | 日韩一区二区久久 | 伊人精品视频 | www.久久精品视频 | 成人免费在线观看视频 | 久久伊人精品 | 成人精品一区二区三区中文字幕 | 国产精品呻吟久久av凹凸 | 久久久久久久电影 | 免费一区二区 | 欧美另类视频 | 99久热在线精品视频观看 | 日韩欧美高清 | 免费在线成人 | 亚洲成人一区 | 久草视频2 | 国产一区二区在线免费观看 | 日本中出视频 | 国产精品我不卡 | 欧美日本韩国一区二区三区 | 国产精品美女久久久久久免费 | 日韩电影免费观看中文字幕 | 四虎午夜剧场 | 国产精品毛片av | 成人福利在线视频 | 日日摸日日碰夜夜爽亚洲精品蜜乳 | 久久综合九色综合欧美狠狠 | 黄网站涩免费蜜桃网站 | 久久久久国产精品 | 精品一级电影 | 水蜜桃久久夜色精品一区 | 欧美精品一区三区 | 婷婷开心激情综合五月天 | 国产乱码精品一品二品 | 精品中文在线 | 亚洲一区 中文字幕 |