本文为旧的lru算法,多代lru算法与旧算法的区别可稳步:linux内存回收mglru算法-CSDN博客
1.shrink_node_memcgs
node_reclaim->__node_reclaim->shrink_node,这里调用shrink_node_memcgs回收后,要判断下是否分配内存速度比回收速度快太多,这种情况可能会尝试停一段时间等回收完成(reclaim_throttle),然后再看是否需要继续回收(should_continue_reclaim)。实际入口shrink_node_memcgs如下:
shrink_node_memcgs():
for each memory control group:/*** 按照指定的预留min/low内存,从上层节点瓜分实际分到的min/low预留内存大小。* 具体见:https://blog.csdn.net/qq_37517281/article/details/134466144*/mem_cgroup_calculate_protection()if (mem_cgroup_below_min(target_memcg, memcg)) {// 本group分配的未达到min预留内存,尝试下个groupcontinue;} else if (mem_cgroup_below_low(target_memcg, memcg)) {// 如果指定不对未达到low限制的group回收内存,则只标记下skipif (!sc->memcg_low_reclaim) {sc->memcg_low_skipped = 1;continue;}}// 尝试回收页与slabshrink_lruvec();shrink_slab();/*** 计算些时回收压力: 回归页/扫描页数×100* 大于60是中等压力,大于95是严重压力。*/vmpressure();
2.shrink_lruvec
shrink_lruvec 中会计算本次要扫描的页数,然后触发实际的扫描和页回收(shrink_list):实现如下:
shrink_lruvec():// 计算要扫描页数get_scan_count(lruvec, sc, nr);while 未扫描足够的页数且文件页或匿名页数量都不是0:for 2个 active lru list(file and anon):// 如果指定了may_deactivate,则尝试将部分hot 页移至cold 链表中shrink_active_list();for 2个 inactive lru list(file and anon):shrink_inactive_list();// 如果匿名页的cold页少,尝试deactivate 一些老的hot匿名页。shrink_active_list();
3.get_scan_count
scan 四个lru链表页数计算方法如下(get_scan_count):
get_scan_count():/*** 先判断回收文件页还是匿名页,只扫描文件页的情况:* a.没有swap空间* b.一个cgroup的memory.swappness为0(swapness表示对换出匿名页与重加载文件页的cost的加权,cost 指scan过程中发现的不可换出的页数+换出页×32)* c.指定了回收缓存文件页(cache_trim_mode)* 只扫描匿名页的情况:* a.文件页很少* 同时回收文件页和匿名页的情况:* a.即将oom时,这时的priority为0(后面扫描时,对所有可扫描页的扫描比例为priority分之一)* 其它情况下,会计算文件页与匿名页扫描比例,比例的原始值控制在1/3 - 2/3之间* (当anon_cost为0时,ap 与 fp 的未加权的原始比是1:2),* 取决于扫描时的cost(scan过程中发现的不可换出的页数+换出页×32)* 乘上一个换出匿名页与加载文件页的io cost比(swapness/200)**/total_cost = sc->anon_cost + sc->file_cost;anon_cost = total_cost + sc->anon_cost;file_cost = total_cost + sc->file_cost;total_cost = anon_cost + file_cost;ap = swappiness * (total_cost + 1);ap /= anon_cost + 1;fp = (200 - swappiness) * (total_cost + 1);fp /= file_cost + 1;fraction[0] = ap;fraction[1] = fp;denominator = ap + fp;// 计算 4 个 lru list (active/inactive × anon/file)扫描数量: if (!sc->memcg_low_reclaim && low > min) {protection = low;sc->memcg_low_skipped = 1;} else {protection = min;}// scan = lru页数-组内预留页数scan = lruvec_size - lruvec_size * protection / (cgroup_size + 1);// priority 默认为 12scan >>= sc->priority;scan = mem_cgroup_online(memcg) ?div64_u64(scan * fraction[file], denominator) :DIV64_U64_ROUND_UP(scan * fraction[file],denominator);
4.shrink_active_list
将active 页移至inactive页的方法(shrink_active_list):
shrink_active_list():/*** 处理一批本cpu上的页状态转换请求(cpu_fbatches),和对页标记可否回收的请求(mlock_fbatch)* 1、新访问的页和最新分配的页会记在fbatches->lru_add上,处理函数是 lru_add_fn。* 2、writeback的页放在lru_rotate.fbatch,处理函数是 lru_move_tail_fn,将其放在inactive 的tail上,等待回收(页回收是从tail开始的)* 3、madvice 为don't need的页,或是free了的设备页、文件页,放在cpu_fbatches.lru_deactivate_file,处理函数是 lru_deactivate_file_fn * 会将dirty/writeback/clean页放inactive的head上* 4、匿名页比如内核自己使用并释放的页,放在 cpu_fbatches.lru_deactivate,处理函数是 lru_deactivate_fn 将页框放在 active 的head上* 5、huge page 是lazyfree的,放在cpu_fbatches.lru_lazyfree上,处理函数是 lru_lazyfree_fn 会将页当成clean页,放在inactive 的头上。*/lru_add_drain();// 从后向前扫描,跳过高于指定zone的区和cma的区,从这中间的区找可以unmap的页框,或未map的页框isolate_lru_folios();for 每个扫描出的页框:// 判断这个页框没有mlock的vma,且它的mapping也没有标记unevictableif (unlikely(!folio_evictable(folio))) {folio_putback_lru(folio);continue;}// 如果buffer的总数太多,且这个页框中有buffer,则尝试解除映射if (unlikely(buffer_heads_over_limit)) {if (folio_test_private(folio) && folio_trylock(folio)) {if (folio_test_private(folio))filemap_release_folio(folio, 0);folio_unlock(folio);}}// 尝试把code文件页加入active队列,把其它页加入inactive队列list_add(&folio->lru, &l_inactive);// 给evictable的页引用减1,引用不为0的加入到lru的inactive或active的lru中。引用为0则尝试释放,对页框中有超过1页的情况,调用页框绑定的destroy;对于只有一页的情况,攒起来在后面两个函数内释放。move_folios_to_lru()// 释放只有一页的页框mem_cgroup_uncharge_list();free_unref_page_list();
5.shrink_inactive_list
shrink_inactive_list():// 处理一批本cpu上的页状态转换请求(cpu_fbatches),和对页标记可否回收的请求(mlock_fbatch)lru_add_drain();// 从lru中切出要扫描的页isolate_lru_folios();// 将一些页换出shrink_folio_list();// 将没有迁移成功、没有换出成功、没有写回文件的页重新加回原lru链表,并将ref减1move_folios_to_lru();// 统计costlru_note_cost(lruvec, file, stat.nr_pageout, nr_scanned - nr_reclaimed);// 将ref减1后为0的页框释放掉mem_cgroup_uncharge_list(&folio_list);free_unref_page_list(&folio_list);
6.shrink_folio_list
shrink_inactive_list会将部分页回收、送入swap区,或迁移到其它numa node上。具体实现如下:
shrink_folio_list():
if (folio_test_writeback(folio)) {/*** 如果已经标记了writeback和reclasim,说明它在等io,* 这时需要activate它并继续scan来找其它inactive list上可以回收的folio* (如果是swap区的,将swapcache相关项清理掉)*/if (current_is_kswapd() &&folio_test_reclaim(folio) &&test_bit(PGDAT_WRITEBACK, &pgdat->flags)) {stat->nr_immediate += nr_pages;goto activate_locked;} else if (writeback_throttling_sane(sc) ||!folio_test_reclaim(folio) ||!may_enter_fs(folio, sc->gfp_mask)) {/*** 如果本次扫描不没指定可访问文件系统(__GFP_FS or __GFP_IO),* 且这个页还没标记可回收,就标记一下这个页框可回收并继续scan* 可能在下一次扫描时回收*/folio_set_reclaim(folio);stat->nr_writeback += nr_pages;goto activate_locked;} else {/*** 如果已经标记了页框回收,则已经扫描一遍了,只能等writeback结束*/folio_unlock(folio);folio_wait_writeback(folio);/* then go back and try same folio again */list_add_tail(&folio->lru, folio_list);continue;}
}
folio_check_references();
// 如果没有引用,则考虑可否直接回收,先尝试迁移到最近距离的下一个node(demote_folio_list,下一个node记录在全局的node_demotion中)
if (do_demote_pass &&(thp_migration_supported() || !folio_test_large(folio))) {list_add(&folio->lru, &demote_folios);folio_unlock(folio);continue;
}
// 看是否是匿名可进swap区的页(有页面映射且没有人为它建过swapcache),为它建立swapcache项
if (folio_test_anon(folio) && folio_test_swapbacked(folio)) {if (!folio_test_swapcache(folio)) {// 添加一个swap区的映射entryadd_to_swap();}
}
// 对于文件页和没有swapcache的匿名页,需要给它解除页表映射项
if (folio_mapped(folio)) {try_to_unmap(folio, flags);
}
// 对于dirty的页,只有kswapd进程可以直接回收,其它进程只能柡注reclaim,以防止stack overflow
if (folio_test_dirty(folio)) {// kswapd进程来回收了,需要把tlb刷下去try_to_unmap_flush_dirty();pageout();
}
// 如果这个页框还有buffer,尝试解除clean的和没有映射的buffer
if (folio_has_private(folio)) {filemap_release_folio(folio, sc->gfp_mask)
}