摘要:Linux操作系統(tǒng)使用頁(yè)緩存機(jī)制來(lái)對(duì)塊設(shè)備上的文件進(jìn)行緩存,頁(yè)緩存可以無(wú)限增大,直到物理內(nèi)存達(dá)到內(nèi)存水線。在這種情況下,當(dāng)頁(yè)緩存使用過(guò)多的時(shí)候,內(nèi)核中一些原子性的內(nèi)存申請(qǐng)會(huì)失敗,導(dǎo)致一些業(yè)務(wù)執(zhí)行延時(shí)甚至失敗。文章通過(guò)對(duì)內(nèi)核中內(nèi)存管理模塊進(jìn)行改造,對(duì)頁(yè)緩存的申請(qǐng)進(jìn)行控制,使得頁(yè)緩存使用有最高限度。如果達(dá)到最高限度,新的頁(yè)緩存申請(qǐng)會(huì)替換掉頁(yè)緩存里舊的頁(yè)面。
關(guān)鍵詞:Linux操作系統(tǒng);頁(yè)緩存;主動(dòng)回收
中圖分類號(hào):TP31" 文獻(xiàn)標(biāo)志碼:A
0 引言
Linux內(nèi)核中有很多種類的內(nèi)存緩存,用于緩存各種需要頻繁從外設(shè)(如塊設(shè)備的磁盤等)進(jìn)行訪問(wèn)的資源(如文件、inode等),以減少外設(shè)的訪問(wèn),提高系統(tǒng)響應(yīng)時(shí)間,本文涉及的頁(yè)緩存機(jī)制就是其中之一[1]。
很明顯這些緩存方式都是屬于以空間來(lái)?yè)Q取時(shí)間的設(shè)計(jì),那么就一定會(huì)在邏輯上存在一個(gè)問(wèn)題,就是當(dāng)空間也在某種場(chǎng)景下成為一種瓶頸的時(shí)候,那么該如何平衡空間和時(shí)間。
本文從Linux的內(nèi)存機(jī)制尤其是緩存機(jī)制的分析開始,指出在何種場(chǎng)景下會(huì)出現(xiàn)頁(yè)緩存帶來(lái)的內(nèi)存不足的問(wèn)題,提出一種基于主動(dòng)回收的頁(yè)緩存限制方法來(lái)解決此類問(wèn)題。
1 Linux內(nèi)核的內(nèi)存管理
1.1 頁(yè)框管理及回收機(jī)制
Linux的物理內(nèi)存按頁(yè)來(lái)進(jìn)行管理,每個(gè)頁(yè)都需要有一個(gè)頁(yè)描述符,類型為struct page,所有的頁(yè)描述符都存放在mem_map數(shù)組中。此數(shù)組在pg_data節(jié)點(diǎn)初始化時(shí)由初始內(nèi)存分配器alloc_bootmem_node接口分配占用空間[2]。
在不考慮非一致內(nèi)存的情況下,整個(gè)內(nèi)存情況被描述成一個(gè)內(nèi)存節(jié)點(diǎn)pg_data,這個(gè)內(nèi)存節(jié)點(diǎn)被分為3個(gè)管理區(qū):DMA區(qū)、NORMAL區(qū)、HIGHMEM區(qū)。每個(gè)區(qū)管理本區(qū)地址范圍內(nèi)的所有頁(yè)面,尤其是空閑頁(yè)面,空閑頁(yè)面按照伙伴算法進(jìn)行分配和釋放。每個(gè)內(nèi)存區(qū)有3個(gè)水平值:pages_high、pages_low、pages_min。用這3個(gè)閾值來(lái)判定目前空閑頁(yè)面的水平,從而決定需要做何動(dòng)作。內(nèi)存頁(yè)面回收時(shí)機(jī)如圖1所示。
圖1說(shuō)明了在申請(qǐng)內(nèi)存時(shí),空閑內(nèi)存到達(dá)哪個(gè)水平時(shí),需要做的動(dòng)作。當(dāng)空閑頁(yè)面小于page_low時(shí),喚醒回收線程kswapd;當(dāng)小于page_min時(shí),同步調(diào)用回收過(guò)程try_to_free_pages;當(dāng)空閑頁(yè)面回到page_low水平時(shí),kswapd線程再度睡眠。
1.2 Linux的磁盤緩存機(jī)制
磁盤上的數(shù)據(jù)需要被用到時(shí),都需要先讀到內(nèi)存里,這就是磁盤緩存的作用。Linux下的磁盤緩存有:目錄項(xiàng)高速緩存、索引節(jié)點(diǎn)inode高速緩存,塊緩存和頁(yè)緩存[3]。前兩者比較專用且數(shù)據(jù)量也并不大,因此本節(jié)只討論后兩者。
塊設(shè)備是指最小存儲(chǔ)訪問(wèn)粒度為塊的設(shè)備。一般最小的IO和存儲(chǔ)單位為塊,比如硬盤扇區(qū),一般為512字節(jié),因此塊設(shè)備的緩存也是以塊為單位的,這就是塊緩存,早期的Linux內(nèi)核和Unix內(nèi)核都只有塊緩存。
但現(xiàn)代的塊設(shè)備一般可以支持直接按頁(yè)面(4k)的IO粒度,比如:在塊設(shè)備IO請(qǐng)求結(jié)構(gòu)BIO中,是直接可以指定一個(gè)頁(yè)面的,而且內(nèi)核下物理內(nèi)存的管理也是以頁(yè)面為粒度[4]。這樣,對(duì)塊設(shè)備的緩存不如以頁(yè)為單位。以頁(yè)為單位的緩存就是頁(yè)緩存。目前,Linux內(nèi)核主要使用頁(yè)緩存機(jī)制,但塊緩存機(jī)制仍然需要保留,理由如下。
(1)在磁盤空間比較緊張的情況下,碎片也比較多,有些文件可能不能連續(xù)存儲(chǔ)在相鄰的塊里,這樣會(huì)使得頁(yè)緩存粒度太大,必須使用塊緩存,因?yàn)閴K設(shè)備的最小存儲(chǔ)單位是塊。
(2)文件系統(tǒng)的super block超級(jí)塊和inode塊,只能按塊來(lái)訪問(wèn)。另外,對(duì)于不利用文件系統(tǒng)而直接訪問(wèn)設(shè)備塊的軟件,也要提供接口。
塊緩存雖然比頁(yè)小,但由于物理內(nèi)存以頁(yè)為單位管理,因此塊緩存也放在頁(yè)里,這種頁(yè)叫做緩存區(qū)頁(yè);塊緩存因?yàn)橐詨K為單位,因此不需要跟文件關(guān)聯(lián),需要跟塊設(shè)備關(guān)聯(lián),如圖2所示。
一個(gè)緩沖區(qū)頁(yè)的private字段指向頁(yè)內(nèi)塊緩存的頭,本頁(yè)所有塊緩存的頭是一個(gè)單鏈表,每個(gè)塊緩存頭的b_page字段指向緩存區(qū)頁(yè),塊緩存頭的b_bdev字段指向塊設(shè)備描述符,這樣可以完整地把塊緩存組織起來(lái)。
頁(yè)緩存和塊緩存所占用物理頁(yè)框也都被鏈接入內(nèi)核active和inactive隊(duì)列,以用于內(nèi)存回收。
磁盤上需要有磁盤文件,系統(tǒng)才能進(jìn)行文件的存放和讀取。因此,在討論塊設(shè)備的緩存時(shí),需要考慮不同文件系統(tǒng)的實(shí)現(xiàn)。
嵌入式系統(tǒng)下可執(zhí)行文件一般直接放入Linux的根文件系統(tǒng)中,Linux的根文件系統(tǒng)用到了如下3種文件系統(tǒng):Ramfs、Jffs2和Squashfs。Ramfs是基于內(nèi)存的文件系統(tǒng),本項(xiàng)目一般使用initrd形式的根文件系統(tǒng)。后兩者則都是基于Flash的文件系統(tǒng),Linux直接加載Flash上已經(jīng)被燒結(jié)好的Jffs2/Squashfs文件系統(tǒng)分區(qū)作為根文件系統(tǒng)。
Ramfs:Ramfs的存儲(chǔ)介質(zhì)就是內(nèi)存,所有文件系統(tǒng)的數(shù)據(jù)都直接存放在頁(yè)緩存里面,或者換句話說(shuō),頁(yè)緩存就是Ramfs的最終存放介質(zhì)。因此,Ramfs系統(tǒng)只用到了頁(yè)緩存。
Squashfs:只讀并且壓縮的文件系統(tǒng),即存放在Flash上的數(shù)據(jù)是經(jīng)過(guò)壓縮的,在讀到內(nèi)存中被系統(tǒng)使用之前要經(jīng)過(guò)解壓縮,同樣被寫進(jìn)去之前也要經(jīng)過(guò)壓縮。Squashfs把Flash上的未解壓的裸數(shù)據(jù)先讀到塊緩存中,然后解壓到頁(yè)緩存中被系統(tǒng)使用,寫時(shí)反之。因此,Squashfs既使用了頁(yè)緩存,也使用了塊緩存。
Jffs2:常用于Flash上的日志型文件系統(tǒng),也可以壓縮,但其解壓和壓縮過(guò)程直接使用Kmalloc申請(qǐng)內(nèi)存進(jìn)行,不使用塊緩存。因此,Jffs2文件系統(tǒng)只使用了頁(yè)緩存。
1.3 頁(yè)緩存管理
頁(yè)緩存機(jī)制是Linux內(nèi)核對(duì)塊設(shè)備上存儲(chǔ)文件的一種內(nèi)存緩存機(jī)制,在Linux內(nèi)核需要讀取塊設(shè)備上的文件時(shí),先將文件內(nèi)容緩存在頁(yè)緩存的頁(yè)面中,后續(xù)再使用該文件時(shí)則不需要從塊設(shè)備上重新讀取,從而節(jié)省時(shí)間[5]。
圖3列出了一個(gè)用戶態(tài)進(jìn)程的2個(gè)線性區(qū)(分別為代碼段和數(shù)據(jù)段)共同映射了一個(gè)文件的情況。2個(gè)線性區(qū)數(shù)據(jù)結(jié)構(gòu)vm_area共同指向一個(gè)文件數(shù)據(jù)結(jié)構(gòu)file,file結(jié)構(gòu)的address_space結(jié)構(gòu)里含有所有本文件已經(jīng)被讀入內(nèi)存的頁(yè)框數(shù)據(jù)結(jié)構(gòu)page,只要把vm_area線性區(qū)中的線性地址與物理頁(yè)框的映射關(guān)系加入進(jìn)程頁(yè)表,就可以直接訪問(wèn)了。其中,文件的數(shù)據(jù),即文件的代碼和全局?jǐn)?shù)據(jù),存放在頁(yè)緩存的物理頁(yè)框中[6]。
頁(yè)緩存是文件的緩存,需要與某個(gè)文件相關(guān),這是依靠一個(gè)address_space對(duì)象來(lái)實(shí)現(xiàn),而且一個(gè)文件一般也會(huì)有很多頁(yè)緩存存在,一個(gè)文件所擁有的所有頁(yè)緩存是通過(guò)一個(gè)基數(shù)樹來(lái)管理的,如圖4所示。
圖4是Linux的頁(yè)緩存機(jī)制基本原理,Linux內(nèi)核設(shè)計(jì)頁(yè)緩存可以使用所有剩余物理內(nèi)存空間,如果內(nèi)存不足時(shí),依靠回收機(jī)制進(jìn)行回收,一般有2種回收時(shí)機(jī)。(1)在內(nèi)存申請(qǐng)的時(shí)候,如果空閑內(nèi)存低于低水線(page_low)時(shí),則喚醒回收線程kswapd進(jìn)行異步回收。(2)在內(nèi)存申請(qǐng)的時(shí)候,如果空閑內(nèi)存低于最小水線(page_min)時(shí),申請(qǐng)內(nèi)存的上下文可以等待的話,則進(jìn)行同步調(diào)用和同步回收。
因此,可以看到,Linux對(duì)于頁(yè)緩存的設(shè)計(jì)是非常靈活高效的。但同時(shí)它也存在很大的問(wèn)題,頁(yè)緩存可以無(wú)限增大,直到物理內(nèi)存達(dá)到內(nèi)存水線,此時(shí)內(nèi)核中一些原子性的內(nèi)存申請(qǐng)(因?yàn)槠洳荒苤苯舆M(jìn)行內(nèi)存回收)就會(huì)失敗,導(dǎo)致一些業(yè)務(wù)執(zhí)行延時(shí)甚至失敗。這在一些對(duì)可靠性要求較高的網(wǎng)絡(luò)通信設(shè)備中可能是無(wú)法接受的。
2 Linux頁(yè)緩存機(jī)制的問(wèn)題
2.1 頁(yè)緩存機(jī)制帶來(lái)的問(wèn)題
Linux的頁(yè)緩存機(jī)制可以使用所有剩余物理內(nèi)存,以提高文件訪問(wèn)效率,但也會(huì)有一些不合適的場(chǎng)景,尤其是在一些嵌入式的Linux環(huán)境中。
例如:在一些家庭網(wǎng)關(guān)或家用路由器應(yīng)用中,開啟了UPNP媒體業(yè)務(wù)的情況下,媒體掃描業(yè)務(wù)需要讀取磁盤上的大量媒體文件,從而占用大量的頁(yè)緩存空間,最終將物理內(nèi)存占滿。在內(nèi)存水線配置較低的情況下,內(nèi)核中的原子性(不能睡眠)內(nèi)存分配因?yàn)閮?nèi)存不足而失敗,如下為內(nèi)存失敗時(shí)的內(nèi)核日志:
sirq-tasklet/0: page allocation failure. order:0, mode:0x20
Call Trace:[lt;80008aacgt;]…
Mem-info:
DMA per-cpu:
CPU 0: Hot: hi: 0, btch: 1 usd: 0 Cold: hi: 0, btch: 1 usd: 0
Normal per-cpu:
CPU 0: Hot: hi: 0, btch: 1 usd: 0 Cold: hi: 0, btch: 1 usd: 0
Active:1698 inactive:2649 dirty:0 writeback:0 unstable:0
free:33 slab:1417 mapped:233 pagetables:113 bounce:0
DMA free:96kB min:88kB low:720kB high:808kB active:2588kB inactive:4036kB present:16256kB pages_scanned:0 all_unreclaimable? no
lowmem_reserve[]: 0 15
Normal free:36kB min:88kB low:720kB high:808kB active:4204kB inactive:6560kB present:16256kB pages_scanned:0 all_unreclaimable? no
lowmem_reserve[]: 0 0
DMA: 0*4kB 0*8kB 0*16kB 1*32kB 1*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 96kB
Normal: 1*4kB 0*8kB 0*16kB 1*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 36kB
Free swap: 0kB
8192 pages of RAM
0 pages of HIGHMEM
1055 reserved pages
1893 pages shared
0 pages swap cached
如上,出現(xiàn)內(nèi)存不足時(shí),內(nèi)存幾乎被耗盡,原因是頁(yè)緩存占用內(nèi)存較多(Active:1698 inactive:2649),內(nèi)存水線設(shè)置也比較接近,回收不及時(shí),導(dǎo)致內(nèi)核中原子性的(mode:0x20)內(nèi)存申請(qǐng)失敗。
2.2 傳統(tǒng)規(guī)避方法和局限性
要解決上述問(wèn)題,在原生的Linux系統(tǒng)條件下,有以下2種方法。方法一:內(nèi)核文件/proc/sys/vm/drop_caches中寫入1,可以觸發(fā)內(nèi)核主動(dòng)進(jìn)行page cache的回收;方法二:提高內(nèi)存水線設(shè)置,更加提前觸發(fā)內(nèi)核進(jìn)行內(nèi)存回收。方法一可以回收所有不用的頁(yè)緩存,但也存在2個(gè)問(wèn)題,(1)回收的時(shí)機(jī)無(wú)法準(zhǔn)確確定;(2)釋放全部過(guò)期緩存,對(duì)性能也有影響。方法二是被動(dòng)方法,加快回收,但水線設(shè)置值很難抉擇,而且影響全局,設(shè)置不好也會(huì)導(dǎo)致系統(tǒng)忙于回收等效率問(wèn)題??梢?,以上2種現(xiàn)有辦法都無(wú)法完美解決上述問(wèn)題。
3 基于主動(dòng)回收的Linux頁(yè)緩存限制方法
3.1 頁(yè)緩存限制方法和主動(dòng)回收
如果對(duì)頁(yè)緩存有主動(dòng)限制辦法,比如限制頁(yè)緩存最多能使用多少內(nèi)存,問(wèn)題就比較容易解決了。為此設(shè)計(jì)如下改造方法,其原理如下:增加一個(gè)內(nèi)核sysfs文件/proc/sys/vm/pagecache_ratio,此文件設(shè)置一個(gè)百分比,含義為最多頁(yè)緩存可以占用物理內(nèi)存的比率;內(nèi)存管理系統(tǒng)初始化的時(shí)候,根據(jù)上述比率計(jì)算每個(gè)區(qū)域能使用的最多頁(yè)緩存頁(yè)數(shù);增加一種內(nèi)存頁(yè)面申請(qǐng)標(biāo)志GFP_PAGECACHE,只要是申請(qǐng)頁(yè)緩存頁(yè)面的動(dòng)作都標(biāo)注此標(biāo)志,但要除去比如ramfs,tmpfs等必須占用頁(yè)緩存的內(nèi)存文件系統(tǒng)申請(qǐng);在頁(yè)面申請(qǐng)操作中,如果是GFP_PAGECACHE類型的申請(qǐng),判斷頁(yè)緩存是否超過(guò)預(yù)定限制。如果超過(guò),則不能申請(qǐng)到,觸發(fā)內(nèi)存回收;內(nèi)存回收時(shí),如果發(fā)現(xiàn)只是頁(yè)緩存超限,則只回收頁(yè)緩存頁(yè)面,不回收匿名頁(yè)面;如上,限制的基本原理就是利用現(xiàn)有的內(nèi)存回收機(jī)制,單獨(dú)提前為頁(yè)緩存進(jìn)行控制。
另外,在各使用場(chǎng)景中,需要根據(jù)自身情況多加考慮。標(biāo)注申請(qǐng)頁(yè)緩存頁(yè)面的動(dòng)作一般是由各文件系統(tǒng)決定的,因此,需要考量所在系統(tǒng)都使用了哪些文件系統(tǒng),這些文件系統(tǒng)的頁(yè)緩存頁(yè)面操作是使用的什么接口,保證在申請(qǐng)時(shí)加入GFP_PAGECACHE標(biāo)志。
3.2 試驗(yàn)結(jié)果分析
修改Linux內(nèi)核源碼,加入上述的頁(yè)緩存限制功能后,在一個(gè)小內(nèi)存(32 M)的家用路由器設(shè)備上進(jìn)行試驗(yàn)。
(1)對(duì)頁(yè)緩存限制進(jìn)行配置。
# cat /proc/sys/vm/pagecache_ratio
32
如上,表示配置頁(yè)緩存最多能使用物理內(nèi)存的32%;
計(jì)算后系統(tǒng)內(nèi)存的數(shù)量和頁(yè)緩存的限制如下。
pages" present 8128
max pagecache pages: 2600
pagecaches alloc failed: 48
總內(nèi)存大小為8128頁(yè)面,頁(yè)緩存最大限制為2600/8128=32%,符合上面的設(shè)置。
(2)上電后查看free內(nèi)存和頁(yè)緩存使用內(nèi)存。
Free內(nèi)存:
MemFree:" 2860 kB
nr_file_pages 2275
nr_file_ramfs 0
nr_file_tmpfs 0
pagecaches alloc failed: 48
2275個(gè)頁(yè)面(須減去ramfs和tmpfs的占用數(shù))小于2600的限制,頁(yè)緩存申請(qǐng)失敗次數(shù)不為0,說(shuō)明限制生效。
如上,通過(guò)對(duì)頁(yè)緩存的限制,限制了系統(tǒng)緩存的最大峰值,保證了剩余內(nèi)存的數(shù)量,極大地減少了內(nèi)核中緊急業(yè)務(wù)原子性申請(qǐng)內(nèi)存失敗的情況。
在上述家用路由器設(shè)備的實(shí)際測(cè)試過(guò)程中,未限制之前,開啟媒體解析和播放服務(wù)后,啟動(dòng)數(shù)據(jù)業(yè)務(wù)必然出現(xiàn)原子內(nèi)存申請(qǐng)失敗告警現(xiàn)象,導(dǎo)致媒體服務(wù)無(wú)法交付使用。使用頁(yè)緩存限制后,同樣開啟媒體服務(wù),啟動(dòng)內(nèi)核數(shù)據(jù)業(yè)務(wù),大負(fù)荷運(yùn)行若干天,不再出現(xiàn)原子內(nèi)存分配失敗告警問(wèn)題。
4 結(jié)語(yǔ)
綜上所述,利用本文提出的頁(yè)緩存限制方法后,對(duì)空閑內(nèi)存缺少導(dǎo)致的原子內(nèi)存申請(qǐng)失敗問(wèn)題有根本性的改善。根據(jù)內(nèi)存壓力情況和文件讀取效率進(jìn)行限制比率調(diào)整:如果內(nèi)存壓力較大,可以調(diào)低頁(yè)緩存限制比率,這樣頁(yè)緩存占用內(nèi)存會(huì)降低;如果文件讀取效率降低,但是內(nèi)存壓力較小,可以調(diào)高頁(yè)緩存限制比率,這樣頁(yè)面替換速度會(huì)降低,提高文件讀取命中效率。
在嵌入式設(shè)備中,內(nèi)存資源一般都是有限的。在這種情況下,出現(xiàn)內(nèi)存壓力時(shí),在顯式的內(nèi)存裁剪不再奏效時(shí),應(yīng)該首先考慮對(duì)系統(tǒng)緩存進(jìn)行限制,以可以接受的效率降低來(lái)?yè)Q取更大的空間。
參考文獻(xiàn)
[1]汪敏.Linux中多種內(nèi)存共享機(jī)制及其應(yīng)用探究[J].無(wú)線互聯(lián)科技,2023(4):1-4,22.
[2]吳懿.基于ARM的嵌入式Linux的內(nèi)存優(yōu)化技術(shù)研究與實(shí)現(xiàn)[D].南京:南京航空航天大學(xué),2011.
[3]趙興華.開啟Linux新內(nèi)核特性實(shí)現(xiàn)內(nèi)存管理優(yōu)化[J].網(wǎng)絡(luò)安全和信息化,2024(7):163-165.
[4]楊淵,鄒祖?zhèn)?軟硬協(xié)同的嵌入式系統(tǒng)存儲(chǔ)可靠性增強(qiáng)設(shè)計(jì)[J].太赫茲科學(xué)與電子信息學(xué)報(bào),2024(2):219-226.
[5]俞丁翠,羅龍飛,宋云鵬,等.面向高密度閃存的內(nèi)存頁(yè)大小探索[J].計(jì)算機(jī)工程與科學(xué),2024(7):1167-1174.
[6]郭鋒,王宏偉,黃保壘,等.嵌入式操作系統(tǒng)中基于MIPS處理器的內(nèi)存管理機(jī)制實(shí)現(xiàn)[J].無(wú)線互聯(lián)科技,2020(11):109-110,118.
(編輯 王永超)
Restriction method of Linux page cache based on active reclaiming
WANG" Lei, ZHANG" Bo, XIE" Tiemin
(Nanjing Branch of Triples (Shenzhen) Communication Technology Co., Ltd., Nanjing 211100, China)
Abstract: The Linux operating system uses a page cache mechanism to cache files on block devices, and the page cache can be infinitely increased until the physical memory reaches the memory watermark. In this case, when the page cache is used excessively, some atomic memory requests in the kernel may fail, resulting in delayed or even failed. The article modifies the memory management module in the kernel to control the use of page cache, so as to maximize the use of page cache. If the maximum limit is reached, the new page cache application will replace the old pages in the page cache.
Key words: Linux operation system; page cache; active reclaiming