吉游网提供最新游戏下载和手游攻略!

深入解析Linux内核:页框回收机制(Page Frame Reclamation)全攻略

发布时间:2024-09-24浏览:93

大家好,今天小编来为大家解答深入解析Linux内核:页框回收机制(Page Frame Reclamation)全攻略这个问题,很多人还不知道,现在让我们一起来看看吧!

每当讨论页替换策略,提及最多的就是基于LRU(Least Recently Used)的算法,但严格来说这是不对的因为这些lists并不是严格按照LRU的顺序来维护的。在Linux中LRU有两个list组成,分别是active_list和inactive_list。avtive_list的目标是包含所有进程工作使用的page而inactive_list的目标是包含回收候选者的page。因为所有可回收的page都包含在这两个list中,因此任何进程的page都有可能被回收,而不是仅仅回收属于出错进程的page,因此页回收策略是全局的。

这两个队列就像是一个简单的LRU 2Q,这两个被维护的队列分别叫Am和A1。在使用LRU 2Q时,第一次被分配的page将被放入到一个名叫A1的FIFO队列。如果某个page在A1中并且被人引用,然后改page就会被放入到一个正规的LRU管理的队列Am中。这就是像使用lru_cache_add()将page放置到inactive_list(A1)中,然后使用mark_page_accessed()将page移动到active_list(Am)中。LRU 2Q中的算法描述了这两个list的size如何调节但是Linux使用了一种简单的方法:通过使用refill_inactive()函数将page从active_list的地步移动到inactive_list中来保证active_list的size是整个page cache的2/3. 下图说明了这两个list如何结构化,page如何在这两个list中移动:

2Q算法中描述两个list中预设Am是一个LRU list但是在Linux中更像是一个时钟算法,其中周期就是active list的size。当一个page到达active list的底部的时候,就会来检查reference flag会被检查,如果被置位,该page将会active list的顶部然后接着检查下一个page。如果reference bit被清除,则需要将该page移动到inactive_list

尽管Move-To-Front的启示意味着这两个列表像是在一LRU的方式在运行但是Linux替换策略和LRU还是有很多的不同,不能将其认为是一个stack算法。尽管我们可以忽略分析多程序系统的问题和每个进程使用的memory size是不同的这个事实,但是替换策略还是不能满足inclusion property是因为每个page在list中的位置取决于list的大小和上次引用的时间。这些list并没有按优先级排序因为这会使得每次对page的引用都要更新list。当被换出进程地址空间时,这两个列表几乎被忽略,因为与被换出决策相关的是page在进程虚拟地址空间中的位置而不是在page list中的位置。(这句话不太好理解)

总结下,Linux中替换算法并不是和LRU的行为一样并且被一个benchmark在实际测试中表现不错。当前仅仅有两种case在该算法下表现得比较糟糕。

first:当要被回收的候选page是匿名page时

在这种case中,Linux在线性扫描进程页表来搜索要回收的page之前,将持续检查大量的page,但是这种场景相当少。

second:单个进程中有许多文件映射的page在inactive_list中,并且经常被写入。

该进程和kswapd进程可能进入一种不断地转换这些page的循环中并把它们放入到inactive_list中但不释放任何东西。在这种case下,只有很少的page能够从active_list移动到inactive_list因为这两个列表大小比例始终没有很大变化。

Page Cache

page cache是一组数据结构其包含一些pages如有文件映射或者块设备映射或者swap的page。当前有四种最基本类型的page存在于page cache中:

通过读取内存映射的文件而产生page fault的page

被称作buffer page的page:从文件系统或者块设备中读取的数据块到特定的page

存在于swap cache中的匿名page

属于共享内存空间的page,其和匿名page的处理方式差不多。它们唯一的不同点是共享page被加入到swap cache后当它第一次被写入,它在其存储介质的空间上会立刻保留。

存在page cache的理由是减少不必要的磁盘访问。从磁盘中读取的page被存储在page hash table中,属于struct address_space.在每次访问磁盘前,都会在page cache中搜索其在磁盘中的偏移。下面的API是用来操作page cache的:

void add_to_page_cache(struct page * page, struct address_space * mapping, unsigned long offset)

通过调用lru_cache_add()将page加入到LRU中,然后再将page计入到inode queue和page hash table中

void add_to_page_cache_unique(struct page * page, struct address_space *mapping, unsigned long offset, struct page **hash)

该函数和上一个函数很相似,除了会检查该page是否已经在page cache中存在,该函数要求其调用者不能是由pagecache_lock自旋锁

void remove_inode_page(struct page *page)

该函数调用remove_page_from_inode_queue()从inode中删除page,并调用remove_page_from_hash_queue()从page cache中删除page

struct page * page_cache_alloc(struct address_space *x)

alloc_pages()的封装,使用x->gfp_mask

int page_cache_read(struct file * file, unsigned long offset)

如果offset对应的page不在page cache中时,会增加一个page,必要时会通过address_space_operations->readpage从磁盘读取数据

void page_cache_release(struct page *page)

_free_page()函数的别名,当一个page的引用计数下降为0,则释放该page

Page Cache Hash Table

有一个需求:page cache中的page需要快速被找到。为了实现此需求,page被插入到一个page_hash_table。page->next_hash和page->pprev_hash被用来决绝冲突。(貌似kernel2.6中已经没有page_hash_table)

在kernel2.6中使用索引树来进行page存储用于快速查找。

Adding Pages to the Page Cache

从文件或者块设别中读出来的page通常会被添加到page cache从而避免更多的磁盘IO。大多数文件系统使用generic_file_read()

当作它们的file_operations->read()函数。一般来讲文件系统是通过page来执行它们的IO操作。下面就来说明下generic_file_read()是如何操作的以及它是如何将page添加到page cache。

对于普通的IO来说,generic_file_read()在调用do_generic_read()之中会先做一些基本的检查。通过find_get_page()来查询该page是否已经在page cache中存在,如果不存在,调用page_cache_alloc_cold()从cpu code list中分配page。然后再调用add_to_page_cache_lru()将page加入到page cache同时将page加入到lru list中。如果page一旦在page cache中存在,就会调用page_cache_readahead()从磁盘中读取数据。

没用从进程空间映射的匿名page将被添加到swap cache中,这将在后续的章节来讨论。匿名page在尝试将它们换出之前,它们没有address_space作为一个映射或者是一个文件偏移将它们加入到page cache中。所以这些page仍然留在LRU list中。一旦进入page cache,匿名page和file backed page的真正不同点是匿名page将swapper_space作为address_space。

共享内存的pages在下列两种case下会被加入到page cache中。

第一种case是:当page第一次从swap中获取或者第一次被分配并且第一次被引用的时候加入到page cache。即shmem_getpage()中完成。

第二种case是当swap code调用shmem_unuse()。当一个swap area正在被deactive,并且发现一个page并且在swapper_address中并且没有一个进程在使用。

Reclaiming Pages from the LRU Lists

函数shrink_cache()是替换算法的一部分,它从inactive_list获取page并决定如何将它们换出。该函数是一个大循环,从inactive_list底部最多扫描max_scan个page来释放nr_pages个page,直到inactive_list为空。

每种不同类型的page,释放的时候具体的做法不同。具体的处理顺序如下:

page被lock了且PG_launder被置位:该page在IO中被lock,因此需要跳过此page。但是如果PG_launder被置位,这就意味着该page是第二次被发现上锁了,因此最好等待IO完成然后不管它。如果一个page通过page_cache_get()被引用因此该page不会被过早地释放并调用wait_on_page()进入睡眠知道IO完成。一旦IO结束调用 page_cache_release()来减少它的引用计数。当引用计数为0,则该page可以被回收了。

page是网页且没有被任何进程映射且没有buffer且属于文件映射或者设备:因为该page属于一个文件映射或者设备映射,因此它拥有合法的writepage()函数可用:page->mapping->a_ops->writepage.PG_dirty被清空并且PG_launder被置位说明它准备IO。在调用writepage()前需要调用page_cache_get()来增加它的引用计数来。值得注意的是这种case同样适用于处于swap cache中的匿名page。该page仍然在LRU中,当它再次被发现后,如果IO完成那么就将它简单滴释放,并且page被回收。如果IO没有完成,kernel会等待IO完成。

page拥有buff:将会调用try_to_release_page()来引用它,并尝试将它释放。如果成功了,那么它是一个匿名page,它将从LRU中移除并减少它的引用计数。匿名page存在buff只有一种情况:它指向一个swap file因为该page需要写出block-sized chunk。换句话说如果该page直接有一个file映射,那么就将其简单地减少它的引用计数,如果引用计数为0,则可以直接释放了。

匿名page且被映射到多个进程:调用swap_out()

没有进程在引用的page:如果page在swap cache中,那么将从swap cache删除。如果是一个file的一部分,那么它将从page cache中删除和释放。

Shrinking all caches

shrink_caches()的作用是释放多个cache。在Linux2.6中一次遍历各个zone,然后调用shrink_zone()来回收页框。

用户评论

﹎℡默默的爱

这篇文章说得真不错!终于 paham 了 Linux 内核那复杂的页框回收机制. 简洁易懂又清晰明了,完美地解释了 kernel_getpage 和 pagecache 这两种策略以及它们的优势和劣势。我以前总觉得Linux 内核的内存管理很神秘,现在终于看明白了。

    有5位网友表示赞同!

々爱被冰凝固ゝ

这篇博客写的真好!简单易懂,还结合例子,让我一下子就理解了页框回收机制的工作原理。之前总是感觉 Linux 内核的页框回收很复杂,看了你的文章,发现其实并不难懂

    有20位网友表示赞同!

一笑抵千言

作为小白,我是佩服 banget 你能用这么通俗易懂的方式解释一个linux kernel 里高深的机制!太感谢了,以后遇到类似的问题就能轻松理解了

    有9位网友表示赞同!

伪心

我对这种回收机制一直不太理解,看了你的博客终于豁然开朗了! 就比如那个页面缓存,原来是用来提高系统性能的,真是个不错的机制啊

    有16位网友表示赞同!

七夏i

感觉这个页框回收机制跟垃圾回收机制有点像吧?都是为了释放内存空间。不过 linux 内核的操作流程确实比 java 的虚机复杂很多啊

    有19位网友表示赞同!

一生荒唐

作者写的很详细,但是有些概念我感觉还是不太容易理解,比如 Kernel_getpage 和 pagecache 这种策略究竟是怎样作用的?能不能用更形象的例子解释一下?

    有18位网友表示赞同!

你的眸中有星辰

这个页框回收机制听起来很有意思,不过我看了一些博客文章后发现还真的比较复杂。可能需要花更多的时间学习才能真正理解它

    有20位网友表示赞同!

苍白的笑〃

我个人觉得这篇文章对页框回收机制的描述还是比较笼统的,更希望能深入一些对不同类型的页面进行回收策略的分析和比较 。

    有13位网友表示赞同!

情如薄纱

这篇博客虽然讲得很好,但是对于我来说实用性不如强。我希望能够看到针对实际应用场景的内存管理技巧,比如如何提高内存使用效率或者解决某些特定问题

    有10位网友表示赞同!

゛指尖的阳光丶

Linux 内核的页框回收机制确实很复杂, 希望能看到更多具体的代码示例和分析,这样才能更好地理解他的实现细节

    有19位网友表示赞同!

凝残月

我一直认为内核维护是个很重要的工作,这个博客让我对Linux内存管理有了更深入的了解。希望以后可以学习更多的内核级优化技巧。

    有15位网友表示赞同!

夏日倾情

页面缓存机制确实很巧妙!它能让系统在执行大量读操作的时候提升性能很多。我想知道这种机制会不会带来一定的缺点呢?比如会增加内存压力吗?

    有14位网友表示赞同!

命运不堪浮华

这个博客让我对 Linux 内核的页框回收机制有了大致的认识,但是我还是觉得需要学习更多相关内容才能掌握它

    有13位网友表示赞同!

醉婉笙歌

我觉得作者总结得很好!内核页框回收确实是一个重要的概念,一定要理解清楚。

    有8位网友表示赞同!

安之若素

文章描述很清晰,图解也很到位,帮助我更好地理解了 Linux 内核的内存管理机制。

    有9位网友表示赞同!

一样剩余

看了这篇文章后,感觉linux 内核的设计真是太巧妙了了!每一部分都有其独特的逻辑和作用,让人佩服不已

    有12位网友表示赞同!

热点资讯