一个物理页被分配之后,page其所挂载的链表从buddy空闲链表中摘除,会进入到active 或者inactive链表中,当物理内存water mark存在压力时会根据LRU(least recently unsed)策略将很久没有使用的物理页内存swap或者写入到磁盘中,以便将该页空闲出来供其分配使用。

在各种服务器中应用程序 为了提高性能,大都使用内存密集性策略,因此随着长时间使用或者较多服务时会出现内存压力,因此采用合理策略从正在使用的物理页中选出合适的物理内存swap到磁盘中,以供分配使用,显得特别重要。

内核对swap策略是 选择least recently used(LRU)即最近最少使用的页面将其内容置换到磁盘中以腾出空闲物理页给其分配使用。

LRU链表管理结构

内核LRU链表管理结构如下:

  • 每个node 节点pg_data_t内 struct lruvec结构用于管理本节点内LRU链表。(注意在4.8版本之前LRU是基于zone,即LRU链表不是挂在pd_data_t下面也是挂载在zone结构内,主要是基于zone LRU管理方法会造成不同zone之前 物理页老化速率不一样,该patch由Mel Gorman 修改引入)

采用zone LRU链表方案主要是由于历史原因:在32位系统内存在ZONE_HIGH,内核申请内存时尽量从ZONE_HIGH中申请,这对ZONE_HIGH回收优先级要高于ZONE_NORMA。而现今在64位cpu中 已经不存在ZONE_HIGH,故采用node 节点LRU更加让人容易理解。

The reason we have zone-based reclaim is that we used to have large highmem zones in common configurations and it was necessary to quickly find ZONE_NORMAL pages for reclaim. Today, this is much less of a concern as machines with lots of memory will (or should) use 64-bit kernels. Combinations of 32-bit hardware and 64-bit hardware are rare. Machines that do use highmem should have relatively low highmem:lowmem ratios than we worried about in the past.Conceptually, moving to node LRUs should be easier to understand. The page allocator plays fewer tricks to game reclaim and reclaim behaves similarly on all nodes.

  • pg_data_t节点内负责对lru管理的结构位struct lruvec。
  • struct lruvec 结构中将LRU链表根据匿名页和文件页对LRU进行了详细划分。

LRU 链表被分为五种类型:

  • LRU_INACTIVE_ANON: 匿名页inactive LRU链表,主要记录已经分配但是不是很活页物理页。
  • LRU_ACTIVE_ANON: 匿名active LRU链表,主要记录刚分配或者非常活页的物理页,最近一段时间内被使用用。
  • LRU_INACTIVE_FILE: 文件页(包含page cache)inactive LRU链表,主要记录用于保存文件内容的物理页,最近没有使用过。
  • LRU_ACTIVE_FILE:文件页(包含page cache)active LRU链表,主要记录用于保存文件内容的物理页,最近一段时间内被使用过。
  • LRU_UNEVICTABLE: 不允许被置换出去物理页LRU链表,在次链表中的页面标志位都包含PG_unevictable。

  • LRU 链表主要是用于管理物理page,当内存不足时以便能够快速将合适的物理页swap到磁盘中。LRU链表本身就是物理页管理的一部分,因此要理解LRU 首先需要看懂 page lru以及如何对page进行管理,这样分析代码才不至于云里雾里。

struct pglist_data

struct pglist_data结构中包含对lru链表管理:

typedef struct pglist_data {... ...spinlock_t        lru_lock;struct lruvec      __lruvec;... ...
}
  • lru_lock: lruvec锁,在最新的内核版本中,该锁已经被移到struct lruvec结构中(https://lwn.net/Articles/809783/)。
  • struct lruvec        __lruvec:基于node 节点管理的lru链表结构。

struct lruvec

struct lruvec 链表管理结构:

struct lruvec {struct list_head      lists[NR_LRU_LISTS];/** These track the cost of reclaiming one LRU - file or anon -* over the other. As the observed cost of reclaiming one LRU* increases, the reclaim scan balance tips toward the other.*/unsigned long          anon_cost;unsigned long         file_cost;/* Non-resident age, driven by LRU movement */atomic_long_t           nonresident_age;/* Refaults at the time of last reclaim cycle */unsigned long           refaults;/* Various lruvec state flags (enum lruvec_flags) */unsigned long          flags;
#ifdef CONFIG_MEMCGstruct pglist_data *pgdat;
#endif
};
  • struct list_head    lists[NR_LRU_LISTS]:LRU链表共有五种LRU_INACTIVE_ANON、LRU_ACTIVE_ANON、LRU_INACTIVE_FILE、LRU_ACTIVE_FILE、LRU_UNEVICTABLE。
  • unsigned long       anon_cost:用于记录跟踪匿名页回收
  • unsigned long        file_cost:于记录跟踪文件页回收
  • atomic_long_t         nonresident_age:不会长期驻留的物理页数目
  • unsigned long            refaults:上次回收周期内的fault 数目
  • unsigned long            flags:LRU状态标志位
  • struct pglist_data *pgdat:该结构归属的node节点。

pagevec_add

pagevec_add将page 加入到pvec中数组:

/** Add a page to a pagevec.  Returns the number of slots still available.*/
static inline unsigned pagevec_add(struct pagevec *pvec, struct page *page)
{pvec->pages[pvec->nr++] = page;return pagevec_space(pvec);
}
  • 将page 保存到pvec->pages[ pvec->nr]中,并且pvec->nr++指向下一个空闲索引。
  • pagevec_space:计算还有pvec->pages数组剩余多少个空闲位置,如果为零说明该数组已经满,后续需要执行加入lru动作。

pagevec_space

pagevec_space 计算pvec->pages数组剩余多少个空闲位置:

static inline unsigned pagevec_space(struct pagevec *pvec)
{return PAGEVEC_SIZE - pvec->nr;
}
  • 如果没有空余位置 就返回0,否则返回空闲位置数量。

struct page lru

struct page 结构中有一个比较重要的成员struct list_head lru:

struct page {... ...union {struct {  /* Page cache and anonymous pages */struct list_head lru;...};... ...}... ...
} _struct_page_alignment;
  • struct list_head lru: 该结构是一个双向链表,当该page用作匿名页和page cahe时用于记录该page所位于挂载到哪个链表中,处于不同的链表 该page作用不同。使用page时时刻需要注意所处链表位置,理解该链表也对理解物理页以及LRU等很多特性非常重要。
  • struct list_head 定义如下:
struct list_head {struct list_head *next, *prev;
};

page lru走向

LRU链表走向图如下:

从上述page 管理走向图中可以看到整个page一个走向能够更加说明LRU链表:


  1. page 处于空闲未分配状态时,page中的lru 按照相应所属order挂载到buddy中order级别的链表中,前后指针都指向的都是上一个以及下一个空闲页。
  2. 当page 被buddy分配出去之后,首先进入到lru_pvecs结构中,该结构为per cpu级别即每个cpu核一个lru_pvecs结构,lru_pvecs结构中包含一个PAGEVEC_SIZE(目前为15)的数组,用于将分配出去的物理页加入到该数组中管理。当该数组被填满之后,会将其一次性都加入到active链表中。(注意此时page lru都为NULL,不属于任何链表)(内核根据page使用用途对物理页主要分为匿名页和文件页,两种相应物理页类型都有相应的active 和 inactive 链表)。
  3. 当lru_pvecs中数组被填满之后,物理页page中的LRU会首先加入到匿名/文件 active链表头部,代表此页刚刚被使用。
  4. 当程序或者系统经过一段时间运行之后,物理页可能在该时间端内没有被使用恰好该物理位于active链表尾部,那么该链表将会加入到匿名/文件 inactive链表中(此时并不直接将物理页swap out,而是加入到inactive,再次给予机会即二次机会法)。
  5. 在匿名/文件 inactive链表中,当内存水位较低 处于压力时,位于inactive 链表中尾部的物理页还是最近没有被使用过,那么该物理page中的内容将会被置换。
  6. 物理页中内容被置换到磁盘中,物理页将会重新进入buddy中等待被再次分配使用。

page处于空闲状态

当page 处于空闲状态时,page 归buddy进行统一管理,此时page 挂载到合适的order链表中:

可以分析buddy中的add_to_free_list函数,当page空闲时,将会调用该函数将该page加入到合适的free list链表中:

/* Used for pages not on another list */
static inline void add_to_free_list(struct page *page, struct zone *zone,unsigned int order, int migratetype)
{struct free_area *area = &zone->free_area[order];list_add(&page->lru, &area->free_list[migratetype]);area->nr_free++;
}
  • 调用list_add函数,将该page中的lru加入到指定的free list链表中area->free_list[migratetype],此时是将该page加入到链表头部。

除了上述函数,还有add_to_free_list_tail接口,将page加入到free list链表尾部:

/* Used for pages not on another list */
static inline void add_to_free_list_tail(struct page *page, struct zone *zone,unsigned int order, int migratetype)
{struct free_area *area = &zone->free_area[order];list_add_tail(&page->lru, &area->free_list[migratetype]);area->nr_free++;
}
  • 调用list_add_tail,将空闲page加入到free list尾部。

当物理页被buddy分配出去时,需要调用del_page_from_free_list接口将其从free list链表中将其删除:

static inline void del_page_from_free_list(struct page *page, struct zone *zone,unsigned int order)
{/* clear reported state and update reported page count */if (page_reported(page))__ClearPageReported(page);list_del(&page->lru);__ClearPageBuddy(page);set_page_private(page, 0);zone->free_area[order].nr_free--;
}
  • list_del:将page_lru中pre和next都置NULL,此时该page不属于任何链表中。

lru_pvecs

当页面被分配出去之后,会调用lru_cache_add接口,该接口并不会马上加入到对应的lru 链表中,而是会首先进入到lru_pvecs 中的数组,当lru_pvecs中物理页达到PAGEVEC_SIZE数量是,才会将其加入到对应LRU链表中。

lru_pvecs结构为每个cpu专属即per cpu,每个cpu都有自己的lru_pvecs数据,与其他不共享,采用per cpu而不是全局结构,可以避免对严重锁竞争导致性能下降问题,同时也可以避免cpu 之间cache一致性问题,该结构定义如下:

/** The following struct pagevec are grouped together because they are protected* by disabling preemption (and interrupts remain enabled).*/
struct lru_pvecs {local_lock_t lock;struct pagevec lru_add;struct pagevec lru_deactivate_file;struct pagevec lru_deactivate;struct pagevec lru_lazyfree;
#ifdef CONFIG_SMPstruct pagevec activate_page;
#endif
};
static DEFINE_PER_CPU(struct lru_pvecs, lru_pvecs) = {.lock = INIT_LOCAL_LOCK(lock),
};
  • lru_pvecs 变量定义为per cpu。
  • struct lru_pvecs结构中分别分为lru_add、lru_deactivate_file、lru_deactivate、lru_lazyfree等四种 PAGEVEC_SIZE数组缓存。

该结构整体如下:

对struct lru_pvecs结构中结构中存在的四个需要特别说明pagevec:

  • lru_add、lru_deactivate_file、lru_deactivate、lru_lazyfree、activate_page中的物理页分别会进入到不同的lru链表中

lru_add:处于该数组的物理页会,当数组满时,会根据page中标记为加入到合适 list lru中。

lru_deactivate_file: 该页属于文件page cache,当数组满时, 直接将该页直接将数组中所有页加入文件inactive list中,可以加快页面释放速度。

lru_deactivate:该页属于匿名页,当数组满时, 直接将该页直接将数组中所有页加入匿名inactive list中,可以加快页面释放速度

lru_lazyfree:惰性释放,将page 移到文件inactive list中,可以加快页面释放速度

activate_page: 将页面加入到对应文件或匿名active list中

  • 处于lru_pvecs结构中的物理页,此时page lru中prev和next都为NULL,不属于任何链表中。
  • lru_pvecs相当于cpu专属缓存,只有当数组达到PAGEVEC_SIZE数量是,才会一次性将其加入到对应LRU链表中,这样可以减少操作 pgdata中的LRU链表锁竞争问题。

page 加入/移除 LRU链表

当lru_pvecs中page 数量满足PAGEVEC_SIZE,将会启动将所有链表加入到LRU链表中:

pagevec_lru_move_fn

pagevec_lru_move_fn提供了将PAGEVEC_SIZE中所有page,加入到LRU中通用操作函数:

static void pagevec_lru_move_fn(struct pagevec *pvec,void (*move_fn)(struct page *page, struct lruvec *lruvec, void *arg),void *arg)
{int i;struct pglist_data *pgdat = NULL;struct lruvec *lruvec;unsigned long flags = 0;for (i = 0; i < pagevec_count(pvec); i++) {struct page *page = pvec->pages[i];struct pglist_data *pagepgdat = page_pgdat(page);if (pagepgdat != pgdat) {if (pgdat)spin_unlock_irqrestore(&pgdat->lru_lock, flags);pgdat = pagepgdat;spin_lock_irqsave(&pgdat->lru_lock, flags);}lruvec = mem_cgroup_page_lruvec(page, pgdat);(*move_fn)(page, lruvec, arg);}if (pgdat)spin_unlock_irqrestore(&pgdat->lru_lock, flags);release_pages(pvec->pages, pvec->nr);pagevec_reinit(pvec);
}
  • for (i = 0; i < pagevec_count(pvec); i++) :操作所有PAGEVEC_SIZE物理页
  • struct pglist_data *pagepgdat = page_pgdat(page):获取物理页所属节点pglist_data
  • move_fn: 四个lru_add、lru_deactivate_file、lru_deactivate、lru_lazyfree、activate_page不同的pagevec,会加入到不同的链表中,lru_add、lru_deactivate_file、lru_deactivate、lru_lazyfree对应move_fn依次为:
struct pageve func
lru_add __pagevec_lru_add
lru_deactivate_file lru_deactivate_file_fn
ru_deactivate lru_deactivate_fn
lru_lazyfree lru_lazyfree
activate_page __activate_page

add_page_to_lru_list

add_page_to_lru_list将page加入到对应LRU list中:

static __always_inline void add_page_to_lru_list(struct page *page,struct lruvec *lruvec, enum lru_list lru)
{update_lru_size(lruvec, lru, page_zonenum(page), hpage_nr_pages(page));list_add(&page->lru, &lruvec->lists[lru]);
}
  • list_add:将page加入到&lruvec->lists[lru]中链表头。

add_page_to_lru_list_tail

add_page_to_lru_list_tail将page 加入到对应LRU 链表尾:

static __always_inline void add_page_to_lru_list_tail(struct page *page,struct lruvec *lruvec, enum lru_list lru)
{update_lru_size(lruvec, lru, page_zonenum(page), hpage_nr_pages(page));list_add_tail(&page->lru, &lruvec->lists[lru]);
}

del_page_from_lru_list

del_page_from_lru_list将page 从指定LRU list中删除:

static __always_inline void del_page_from_lru_list(struct page *page,struct lruvec *lruvec, enum lru_list lru)
{list_del(&page->lru);update_lru_size(lruvec, lru, page_zonenum(page), -hpage_nr_pages(page));
}

lru_cache_add

lru_cache_add为对外部模块提供将page 加入到lru函数:

/*** lru_cache_add - add a page to a page list* @page: the page to be added to the LRU.** Queue the page for addition to the LRU via pagevec. The decision on whether* to add the page to the [in]active [file|anon] list is deferred until the* pagevec is drained. This gives a chance for the caller of lru_cache_add()* have the page added to the active list using mark_page_accessed().*/
void lru_cache_add(struct page *page)
{struct pagevec *pvec;VM_BUG_ON_PAGE(PageActive(page) && PageUnevictable(page), page);VM_BUG_ON_PAGE(PageLRU(page), page);get_page(page);local_lock(&lru_pvecs.lock);pvec = this_cpu_ptr(&lru_pvecs.lru_add);if (!pagevec_add(pvec, page) || PageCompound(page))__pagevec_lru_add(pvec);local_unlock(&lru_pvecs.lock);
}
  • PageActive(page) && PageUnevictable(page),: 对page 中flags字段进行检查(page flag可以详见《inux内核那些事之struct page》),其中PG_active和PG_unevictable不能同时设置,设置PG_active的标记可以加入文件或者匿名active list,而PG_unevictable标记为不可回收页面,触发内存回收时不能被回收,将会加入到LRU_UNEVICTABLE lru链表。
  • PageLRU(page):该page 被设置PG_lru,说明该页已经加入到LRU中,不能再次加入LRU,因此如果设置PG_lru标记位将会报错。
  • get_page(page):将page 引用计数+1 因为后面会将该page 加入到pvecs或者lru中。
  • local_lock(&lru_pvecs.lock);:本地cpu加锁,不需要全局锁,因此lru_pvecs每个cpu一份。
  • pvec = this_cpu_ptr(&lru_pvecs.lru_add):获取当前cpu的lru_prvcs.lru_add。
  • pagevec_add:调用pagevec_add,首先将该page 加入到lru_prvcs,做个缓存。
  • 当lru_prvcs.lru_add中数组满时没有多余空间,将调用__pagevec_lru_add,将lru_prvcs.lru_add中缓存的所有page 都加入到lru中,这样做的好处就是可以避免频繁获取lru,如果频繁获取lru锁,将会增加锁竞争,导致性能下降
  • local_unlock(&lru_pvecs.lock)释放本地锁。

__pagevec_lru_add

__pagevec_lru_add触发将pvec数组中所有page 加入到lru中:

void __pagevec_lru_add(struct pagevec *pvec)
{pagevec_lru_move_fn(pvec, __pagevec_lru_add_fn, NULL);
}
  • pagevec_lru_move_f中func 为 __pagevec_lru_add_f, pagevec_lru_move_f为一个通用函数,不同pevec中的数组使用的func不同

__pagevec_lru_add_fn

__pagevec_lru_add_fn根据page 中flag将其加入到对应LRU list中:

static void __pagevec_lru_add_fn(struct page *page, struct lruvec *lruvec,void *arg)
{enum lru_list lru;int was_unevictable = TestClearPageUnevictable(page);int nr_pages = hpage_nr_pages(page);VM_BUG_ON_PAGE(PageLRU(page), page);/** Page becomes evictable in two ways:* 1) Within LRU lock [munlock_vma_page() and __munlock_pagevec()].* 2) Before acquiring LRU lock to put the page to correct LRU and then*   a) do PageLRU check with lock [check_move_unevictable_pages]*   b) do PageLRU check before lock [clear_page_mlock]** (1) & (2a) are ok as LRU lock will serialize them. For (2b), we need* following strict ordering:** #0: __pagevec_lru_add_fn       #1: clear_page_mlock** SetPageLRU()             TestClearPageMlocked()* smp_mb() // explicit ordering   // above provides strict*                   // ordering* PageMlocked()          PageLRU()*** if '#1' does not observe setting of PG_lru by '#0' and fails* isolation, the explicit barrier will make sure that page_evictable* check will put the page in correct LRU. Without smp_mb(), SetPageLRU* can be reordered after PageMlocked check and can make '#1' to fail* the isolation of the page whose Mlocked bit is cleared (#0 is also* looking at the same page) and the evictable page will be stranded* in an unevictable LRU.*/SetPageLRU(page);smp_mb__after_atomic();if (page_evictable(page)) {lru = page_lru(page);if (was_unevictable)__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);} else {lru = LRU_UNEVICTABLE;ClearPageActive(page);SetPageUnevictable(page);if (!was_unevictable)__count_vm_events(UNEVICTABLE_PGCULLED, nr_pages);}add_page_to_lru_list(page, lruvec, lru);trace_mm_lru_insertion(page, lru);
}
  • page_evictable:如有页面不是lock或者flag没有设置PG_unevictable标记位,则page_lru根据page中是否设置PG_active标记位,如果设置则会加入到activelist,如果没有设置则加入unactive list。
  • 如有页面是lock或者flag设置PG_unevictable标记位,则将会加入unevictable list中即不能被swapout出去,此时清除page flag中的PG_active 标记位并设置PG_unevictable标记位。
  • 调用add_page_to_lru_list 加入到对应lru list中。

参考资料

Move LRU page reclaim from zones to nodes v9 [LWN.net]

Better active/inactive list balancing [LWN.net]

per lruvec lru_lock for memcg [LWN.net]

https://www.kernel.org/doc/Documentation/vm/unevictable-lru.txt

Linux中的内存回收 [一] - 知乎

linux那些事之LRU(1)相关推荐

  1. linux那些事之LRU(3)

    继续承接<linux那些事之LRU(2)>,shrink_active_list()函数中需要将调用isolate_lru_pages函数将active LRU中从链表尾部隔离出nr_to ...

  2. linux那些事之LRU(4)

    当物理内存实际比较紧张时,内存水位处于较低water level时,会触发间接回收内存kswapd或者直接回收内存,从inactive LRU list中将不常用的内存 置换到内存中,整个调用过程大概 ...

  3. linux那些事之follow_page

    follow_page()函数是内核中用于根据虚拟地址查找对应的物理页函数,函数定义如下: struct page *follow_page(struct vm_area_struct *vma, u ...

  4. linux那些事之page fault(AMD64架构)(user space)(2)

    do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...

  5. linux那些事之early pape fault

    由linux那些事之中断与异常(AMD64架构)_2>分析可知,在kernel启动过程中首先安装的early中断dt_setup_early_handler中,主要是对page fault中断支 ...

  6. linux那些事之TLB(Translation-Lookaside Buffer)无效操作

    TLB 为了加速虚拟地址转换物理地址过程,CPU内部一般都集成TLB硬件单元,通过缓存存取虚拟地址与物理地址映射关系,避免再通过MMU 通过多级查表引入多次内存开销,直接将映射关系存储到硬件单元中,本 ...

  7. linux那些事之pin memory相关API

    内核中为pin memory 用户空间申请物理内存除了get_user_pages() API函数之外,还有其他相关一系列函数,主要位于mm\gup.c 主要都是针对get_user_pages进行的 ...

  8. 网吧软件正版化,别拿Linux说事

    <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> 5月15日,媒体盛传微软开始对东莞市网吧行业"下手" ...

  9. linux那些事之 page translation(硬件篇)

    Page Translation 以<AMD64 Architecture Programmer's manual volums>从硬件角度说明一个虚拟地址如何转成对应物理页.AM64 地 ...

最新文章

  1. IOS第三天(@property与@synthesize的用法)
  2. 036_Unicode对照表二
  3. bugku 闪得好快
  4. linux提示符目录变为~,Linux终端提示符路径长度的修改方法
  5. jquery 过滤html代码,jquery – 如何使指令使用过滤的HTML属性?
  6. Redis-HyperLogLog
  7. 二元函数图像生成器_常见的损失函数(loss function)
  8. Oracle shared_pool_reserved_size参数设置说明
  9. 编程之美之寻找发帖“水王” 的算法问题
  10. [转]ios面试题收集(二)
  11. 51nod1130---斯特林公式
  12. 计算机组成原理完整学习笔记(一):计算机系统概论
  13. OSEK Os的任务调度
  14. 成都百知教育关于Shopee 平台政策规则解读!
  15. 2022年九款大数据数据分析软件工具推荐
  16. css:clac计算
  17. 华为遭到英国政府调查。网友: 全世界都在针对华为!
  18. 网络技能大赛-高职组计算机网络应用竞赛竞赛-服务器JCOS部署02(一根网线不连接交换机)[附:过期后操作]
  19. 深度解析:电商直播基地运营及盈利模式
  20. ROS path [0]=/opt/ros/melodic/share/ros、path [1] 、path [2]

热门文章

  1. Oracle的Endgame,或被Amazon收购或自生自灭?
  2. centos7限制普通用户访问单一目录下的单一文件
  3. ActiveMQ使用spring JmsTemplate发送消息(一)
  4. 国外著名java技术资料网站
  5. Deep Learning for Brain MRI Segmentation: State of the Art and Future Directions
  6. flash 基础语法
  7. [转载]敏捷开发之Scrum扫盲篇
  8. spring+ibatis事务管理配置
  9. ajaxFileUpload plugin上传文件 chrome、Firefox中出现SyntaxE
  10. SSL *** vs IPSEC ***