您的位置:首页 > 运维架构 > Linux

Linux页面回收与反向映射机制,LRU 链表

2014-04-30 13:41 232 查看
本文将会向大家介绍的如何在两个链表之间移动页面,以及LRU 缓存在其中起到的作用。

LRU 链表

在 Linux 中,操作系统对 LRU 的实现主要是基于一对双向链表:active链表和 inactive 链表,这两个链表是 Linux操作系统进行页面回收所依赖的关键数据结构,每个内存区域都存在一对这样的链表。顾名思义,那些经常被访问的处于活跃状态的页面会被放在active链表上,而那些虽然可能关联到一个或者多个进程,但是并不经常使用的页面则会被放到 inactive链表上。页面会在这两个双向链表中移动,操作系统会根据页面的活跃程度来判断应该把页面放到哪个链表上。页面可能会从 active 链表上被转移到
inactive链表上,也可能从 inactive 链表上被转移到 active链表上,但是,这种转移并不是每次页面访问都会发生,页面的这种转移发生的间隔有可能比较长。那些最近最少使用的页面会被逐个放到inactive 链表的尾部。进行页面回收的时候,Linux 操作系统会从 inactive链表的尾部开始进行回收。

用于描述内存区域的 struct zone()中关于这两个链表以及相关的关键字段的定义如下所示:

struct zone {

……

spinlock_t lru_lock;

struct list_head active_list;

struct list_head inactive_list;

unsigned long nr_active;

unsigned long nr_inactive;

……

}各字段含义如下所示:

lru_lock:active_list和 inactive_list 使用的自旋锁。

active_list:管理内存区域中处于活跃状态的页面。

inactive_list:管理内存区域中处于不活跃状态的页面。

nr_active:active_list链表上的页面数目。

nr_inactive:inactive_list链表上的页面数目。

如何在两个 LRU 链表之间移动页面

Linux 引入了两个页面标志符 PG_active 和 PG_referenced用于标识页面的活跃程度,从而决定如何在两个链表之间移动页面。PG_active用于表示页面当前是否是活跃的,如果该位被置位,则表示该页面是活跃的。PG_referenced用于表示页面最近是否被访问过,每次页面被访问,该位都会被置位。Linux必须同时使用这两个标志符来判断页面的活跃程度,假如只是用一个标志符,在页面被访问时,置位该标志符,之后该页面一直处于活跃状态,如果操作系统不清除该标志位,那么即使之后很长一段时间内该页面都没有或很少被访问过,该页面也还是处于活跃状态。为了能够有效清除该标志位,需要有定时器的支持以便于在超时时间之后该标志位可以自动被清除。然而,很多Linux
支持的体系结构并不能提供这样的硬件支持,所以 Linux 中使用两个标志符来判断页面的活跃程度。

Linux 2.6 中这两个标志符密切合作,其核心思想如下所示:

●如果页面被认为是活跃的,则将该页的 PG_active置位;否则,不置位。

●当页面被访问时,检查该页的 PG_referenced位,若未被置位,则置位之;若发现该页的PG_referenced 已经被置位了,则意味着该页经常被访问,这时,若该页在 inactive 链表上,则置位其 PG_active 位,将其移动到 active链表上去,并清除其 PG_referenced 位的设置;如果页面的 PG_referenced位被置位了一段时间后,该页面没有被再次访问,那么 Linux操作系统会清除该页面的 PG_referenced位,因为这意味着这个页面最近这段时间都没有被访问。

●PG_referenced 位同样也可以用于页面从active 链表移动到 inactive 链表。对于某个在 active链表上的页面来说,其 PG_active 位被置位,如果 PG_referenced 位未被置位,给定一段时间之后,该页面如果还是没有被访问,那么该页面会被清除其PG_active 位,挪到 inactive 链表上去。

Linux 中实现在 LRU 链表之间移动页面的关键函数如下所示(本文涉及的源代码均是基于 Linux 2.6.18.1 版本的):

●mark_page_accessed():当一个页面被访问时,则调用该函数相应地修改PG_active 和 PG_referenced。

●page_referenced():当操作系统进行页面回收时,每扫描到一个页面,就会调用该函数设置页面的PG_referenced 位。如果一个页面的 PG_referenced 位被置位,但是在一定时间内该页面没有被再次访问,那么该页面的PG_referenced 位会被清除。

●activate_page():该函数将页面放到active 链表上去。

●shrink_active_list():该函数将页面移动到inactive 链表上去。

LRU 缓存

前边提到,页面根据其活跃程度会在 active 链表和 inactive链表之间来回移动,如果要将某个页面插入到这两个链表中去,必须要通过自旋锁以保证对链表的并发访问操作不会出错。为了降低锁的竞争,Linux提供了一种特殊的缓存:LRU 缓存,用以批量地向 LRU 链表中快速地添加页面。有了 LRU缓存之后,新页不会被马上添加到相应的链表上去,而是先被放到一个缓冲区中去,当该缓冲区缓存了足够多的页面之后,缓冲区中的页面才会被一次性地全部添加到相应的LRU 链表中去。Linux采用这种方法降低了锁的竞争,极大地提升了系统的性能。

LRU 缓存用到了 pagevec 结构,如下所示 :

struct pagevec {

unsigned long nr;

unsigned long cold;

struct page *pages[PAGEVEC_SIZE];

};pagevec 这个结构就是用来管理 LRU缓存中的这些页面的。该结构定义了一个数组,这个数组中的项是指向 page结构的指针。一个 pagevec 结构最多可以存在 14 个这样的项(PAGEVEC_SIZE的默认值是 14)。当一个 pagevec 的结构满了,那么该 pagevec中的所有页面会一次性地被移动到相应的 LRU 链表上去。

用来实现 LRU 缓存的两个关键函数是 lru_cache_add() 和 lru_cache_add_active()。前者用于延迟将页面添加到 inactive 链表上去,后者用于延迟将页面添加到 active 链表上去。这两个函数都会将要移动的页面先放到页向量 pagevec 中,当 pagevec满了(已经装了 14个页面的描述符指针),pagevec 结构中的所有页面才会被一次性地移动到相应的链表上去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: