基于主动回收的Linux页缓存限制方法

2024-02-15 00:00:00王磊张波谢铁民
无线互联科技 2024年24期

摘要:Linux操作系统使用页缓存机制来对块设备上的文件进行缓存,页缓存可以无限增大,直到物理内存达到内存水线。在这种情况下,当页缓存使用过多的时候,内核中一些原子性的内存申请会失败,导致一些业务执行延时甚至失败。文章通过对内核中内存管理模块进行改造,对页缓存的申请进行控制,使得页缓存使用有最高限度。如果达到最高限度,新的页缓存申请会替换掉页缓存里旧的页面。

关键词:Linux操作系统;页缓存;主动回收

中图分类号:TP31" 文献标志码:A

0 引言

Linux内核中有很多种类的内存缓存,用于缓存各种需要频繁从外设(如块设备的磁盘等)进行访问的资源(如文件、inode等),以减少外设的访问,提高系统响应时间,本文涉及的页缓存机制就是其中之一[1

很明显这些缓存方式都是属于以空间来换取时间的设计,那么就一定会在逻辑上存在一个问题,就是当空间也在某种场景下成为一种瓶颈的时候,那么该如何平衡空间和时间。

本文从Linux的内存机制尤其是缓存机制的分析开始,指出在何种场景下会出现页缓存带来的内存不足的问题,提出一种基于主动回收的页缓存限制方法来解决此类问题。

1 Linux内核的内存管理

1.1 页框管理及回收机制

Linux的物理内存按页来进行管理,每个页都需要有一个页描述符,类型为struct page,所有的页描述符都存放在mem_map数组中。此数组在pg_data节点初始化时由初始内存分配器alloc_bootmem_node接口分配占用空间[2

在不考虑非一致内存的情况下,整个内存情况被描述成一个内存节点pg_data,这个内存节点被分为3个管理区:DMA区、NORMAL区、HIGHMEM区。每个区管理本区地址范围内的所有页面,尤其是空闲页面,空闲页面按照伙伴算法进行分配和释放。每个内存区有3个水平值:pages_high、pages_low、pages_min。用这3个阈值来判定目前空闲页面的水平,从而决定需要做何动作。内存页面回收时机如图1所示。

图1说明了在申请内存时,空闲内存到达哪个水平时,需要做的动作。当空闲页面小于page_low时,唤醒回收线程kswapd;当小于page_min时,同步调用回收过程try_to_free_pages;当空闲页面回到page_low水平时,kswapd线程再度睡眠。

1.2 Linux的磁盘缓存机制

磁盘上的数据需要被用到时,都需要先读到内存里,这就是磁盘缓存的作用。Linux下的磁盘缓存有:目录项高速缓存、索引节点inode高速缓存,块缓存和页缓存[3。前两者比较专用且数据量也并不大,因此本节只讨论后两者。

块设备是指最小存储访问粒度为块的设备。一般最小的IO和存储单位为块,比如硬盘扇区,一般为512字节,因此块设备的缓存也是以块为单位的,这就是块缓存,早期的Linux内核和Unix内核都只有块缓存。

但现代的块设备一般可以支持直接按页面(4k)的IO粒度,比如:在块设备IO请求结构BIO中,是直接可以指定一个页面的,而且内核下物理内存的管理也是以页面为粒度[4。这样,对块设备的缓存不如以页为单位。以页为单位的缓存就是页缓存。目前,Linux内核主要使用页缓存机制,但块缓存机制仍然需要保留,理由如下。

(1)在磁盘空间比较紧张的情况下,碎片也比较多,有些文件可能不能连续存储在相邻的块里,这样会使得页缓存粒度太大,必须使用块缓存,因为块设备的最小存储单位是块。

(2)文件系统的super block超级块和inode块,只能按块来访问。另外,对于不利用文件系统而直接访问设备块的软件,也要提供接口。

块缓存虽然比页小,但由于物理内存以页为单位管理,因此块缓存也放在页里,这种页叫做缓存区页;块缓存因为以块为单位,因此不需要跟文件关联,需要跟块设备关联,如图2所示。

一个缓冲区页的private字段指向页内块缓存的头,本页所有块缓存的头是一个单链表,每个块缓存头的b_page字段指向缓存区页,块缓存头的b_bdev字段指向块设备描述符,这样可以完整地把块缓存组织起来。

页缓存和块缓存所占用物理页框也都被链接入内核active和inactive队列,以用于内存回收。

磁盘上需要有磁盘文件,系统才能进行文件的存放和读取。因此,在讨论块设备的缓存时,需要考虑不同文件系统的实现。

嵌入式系统下可执行文件一般直接放入Linux的根文件系统中,Linux的根文件系统用到了如下3种文件系统:Ramfs、Jffs2和Squashfs。Ramfs是基于内存的文件系统,本项目一般使用initrd形式的根文件系统。后两者则都是基于Flash的文件系统,Linux直接加载Flash上已经被烧结好的Jffs2/Squashfs文件系统分区作为根文件系统。

Ramfs:Ramfs的存储介质就是内存,所有文件系统的数据都直接存放在页缓存里面,或者换句话说,页缓存就是Ramfs的最终存放介质。因此,Ramfs系统只用到了页缓存。

Squashfs:只读并且压缩的文件系统,即存放在Flash上的数据是经过压缩的,在读到内存中被系统使用之前要经过解压缩,同样被写进去之前也要经过压缩。Squashfs把Flash上的未解压的裸数据先读到块缓存中,然后解压到页缓存中被系统使用,写时反之。因此,Squashfs既使用了页缓存,也使用了块缓存。

Jffs2:常用于Flash上的日志型文件系统,也可以压缩,但其解压和压缩过程直接使用Kmalloc申请内存进行,不使用块缓存。因此,Jffs2文件系统只使用了页缓存。

1.3 页缓存管理

页缓存机制是Linux内核对块设备上存储文件的一种内存缓存机制,在Linux内核需要读取块设备上的文件时,先将文件内容缓存在页缓存的页面中,后续再使用该文件时则不需要从块设备上重新读取,从而节省时间[5

图3列出了一个用户态进程的2个线性区(分别为代码段和数据段)共同映射了一个文件的情况。2个线性区数据结构vm_area共同指向一个文件数据结构file,file结构的address_space结构里含有所有本文件已经被读入内存的页框数据结构page,只要把vm_area线性区中的线性地址与物理页框的映射关系加入进程页表,就可以直接访问了。其中,文件的数据,即文件的代码和全局数据,存放在页缓存的物理页框中[6

页缓存是文件的缓存,需要与某个文件相关,这是依靠一个address_space对象来实现,而且一个文件一般也会有很多页缓存存在,一个文件所拥有的所有页缓存是通过一个基数树来管理的,如图4所示。

图4是Linux的页缓存机制基本原理,Linux内核设计页缓存可以使用所有剩余物理内存空间,如果内存不足时,依靠回收机制进行回收,一般有2种回收时机。(1)在内存申请的时候,如果空闲内存低于低水线(page_low)时,则唤醒回收线程kswapd进行异步回收。(2)在内存申请的时候,如果空闲内存低于最小水线(page_min)时,申请内存的上下文可以等待的话,则进行同步调用和同步回收。

因此,可以看到,Linux对于页缓存的设计是非常灵活高效的。但同时它也存在很大的问题,页缓存可以无限增大,直到物理内存达到内存水线,此时内核中一些原子性的内存申请(因为其不能直接进行内存回收)就会失败,导致一些业务执行延时甚至失败。这在一些对可靠性要求较高的网络通信设备中可能是无法接受的。

2 Linux页缓存机制的问题

2.1 页缓存机制带来的问题

Linux的页缓存机制可以使用所有剩余物理内存,以提高文件访问效率,但也会有一些不合适的场景,尤其是在一些嵌入式的Linux环境中。

例如:在一些家庭网关或家用路由器应用中,开启了UPNP媒体业务的情况下,媒体扫描业务需要读取磁盘上的大量媒体文件,从而占用大量的页缓存空间,最终将物理内存占满。在内存水线配置较低的情况下,内核中的原子性(不能睡眠)内存分配因为内存不足而失败,如下为内存失败时的内核日志:

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

如上,出现内存不足时,内存几乎被耗尽,原因是页缓存占用内存较多(Active:1698 inactive:2649),内存水线设置也比较接近,回收不及时,导致内核中原子性的(mode:0x20)内存申请失败。

2.2 传统规避方法和局限性

要解决上述问题,在原生的Linux系统条件下,有以下2种方法。方法一:内核文件/proc/sys/vm/drop_caches中写入1,可以触发内核主动进行page cache的回收;方法二:提高内存水线设置,更加提前触发内核进行内存回收。方法一可以回收所有不用的页缓存,但也存在2个问题,(1)回收的时机无法准确确定;(2)释放全部过期缓存,对性能也有影响。方法二是被动方法,加快回收,但水线设置值很难抉择,而且影响全局,设置不好也会导致系统忙于回收等效率问题。可见,以上2种现有办法都无法完美解决上述问题。

3 基于主动回收的Linux页缓存限制方法

3.1 页缓存限制方法和主动回收

如果对页缓存有主动限制办法,比如限制页缓存最多能使用多少内存,问题就比较容易解决了。为此设计如下改造方法,其原理如下:增加一个内核sysfs文件/proc/sys/vm/pagecache_ratio,此文件设置一个百分比,含义为最多页缓存可以占用物理内存的比率;内存管理系统初始化的时候,根据上述比率计算每个区域能使用的最多页缓存页数;增加一种内存页面申请标志GFP_PAGECACHE,只要是申请页缓存页面的动作都标注此标志,但要除去比如ramfs,tmpfs等必须占用页缓存的内存文件系统申请;在页面申请操作中,如果是GFP_PAGECACHE类型的申请,判断页缓存是否超过预定限制。如果超过,则不能申请到,触发内存回收;内存回收时,如果发现只是页缓存超限,则只回收页缓存页面,不回收匿名页面;如上,限制的基本原理就是利用现有的内存回收机制,单独提前为页缓存进行控制。

另外,在各使用场景中,需要根据自身情况多加考虑。标注申请页缓存页面的动作一般是由各文件系统决定的,因此,需要考量所在系统都使用了哪些文件系统,这些文件系统的页缓存页面操作是使用的什么接口,保证在申请时加入GFP_PAGECACHE标志。

3.2 试验结果分析

修改Linux内核源码,加入上述的页缓存限制功能后,在一个小内存(32 M)的家用路由器设备上进行试验。

(1)对页缓存限制进行配置。

# cat /proc/sys/vm/pagecache_ratio

32

如上,表示配置页缓存最多能使用物理内存的32%;

计算后系统内存的数量和页缓存的限制如下。

pages" present 8128

max pagecache pages: 2600

pagecaches alloc failed: 48

总内存大小为8128页面,页缓存最大限制为2600/8128=32%,符合上面的设置。

(2)上电后查看free内存和页缓存使用内存。

Free内存:

MemFree:" 2860 kB

nr_file_pages 2275

nr_file_ramfs 0

nr_file_tmpfs 0

pagecaches alloc failed: 48

2275个页面(须减去ramfs和tmpfs的占用数)小于2600的限制,页缓存申请失败次数不为0,说明限制生效。

如上,通过对页缓存的限制,限制了系统缓存的最大峰值,保证了剩余内存的数量,极大地减少了内核中紧急业务原子性申请内存失败的情况。

在上述家用路由器设备的实际测试过程中,未限制之前,开启媒体解析和播放服务后,启动数据业务必然出现原子内存申请失败告警现象,导致媒体服务无法交付使用。使用页缓存限制后,同样开启媒体服务,启动内核数据业务,大负荷运行若干天,不再出现原子内存分配失败告警问题。

4 结语

综上所述,利用本文提出的页缓存限制方法后,对空闲内存缺少导致的原子内存申请失败问题有根本性的改善。根据内存压力情况和文件读取效率进行限制比率调整:如果内存压力较大,可以调低页缓存限制比率,这样页缓存占用内存会降低;如果文件读取效率降低,但是内存压力较小,可以调高页缓存限制比率,这样页面替换速度会降低,提高文件读取命中效率。

在嵌入式设备中,内存资源一般都是有限的。在这种情况下,出现内存压力时,在显式的内存裁剪不再奏效时,应该首先考虑对系统缓存进行限制,以可以接受的效率降低来换取更大的空间。

参考文献

[1]汪敏.Linux中多种内存共享机制及其应用探究[J].无线互联科技,2023(4):1-4,22.

[2]吴懿.基于ARM的嵌入式Linux的内存优化技术研究与实现[D].南京:南京航空航天大学,2011.

[3]赵兴华.开启Linux新内核特性实现内存管理优化[J].网络安全和信息化,2024(7):163-165.

[4]杨渊,邹祖伟.软硬协同的嵌入式系统存储可靠性增强设计[J].太赫兹科学与电子信息学报,2024(2):219-226.

[5]俞丁翠,罗龙飞,宋云鹏,等.面向高密度闪存的内存页大小探索[J].计算机工程与科学,2024(7):1167-1174.

[6]郭锋,王宏伟,黄保垒,等.嵌入式操作系统中基于MIPS处理器的内存管理机制实现[J].无线互联科技,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