本篇文章主要是就swappiness的一个源码上的解析(基于kernel版本 v4.14-13151-g5a787756b809),仅为个人见解,有不足欢迎相互交流。
关于Swap和swappiness
Swap(交换分区)是操作系统就内存不足的一个缓解。当内存紧张时候,会适当的根据一些配置值和当前的统计值进行一次判断,会把一些anon内存(分配出去的内存)交换到Swap分区中。
Swappiness是系统的一个参数,可以调节swap的使用优先级。Linux文档描述如下:
swappiness
This control is used to define how aggressive the kernel will swap
memory pages. Higher values will increase aggressiveness, lower values
decrease the amount of swap. A value of 0 instructs the kernel not to
initiate swap until the amount of free and file-backed pages is less
than the high water mark in a zone.
The default value is 60.
翻译过来就是
这个参数是定义内核交换内存页的×××性(aggressive)。更大的值将增加×××性,较低的值会减少swap的数量。0值会命令内核不要使用swap,只有当free和文件使用的内存页数量少于一个zone的高水位,才会使用swap。
默认值是60。
关于这里的aggressive,看的是云里雾里。只知道这个值大概意义。在一些环境,用户一直抱怨为什么Swap使用量这么多,明明还有挺多的available内存。
Linux内存申请
Linux 内存申请一般来说会打上一些flag标志,会对申请流程产生一些影响,这里不细讲。主要是讲一般情况下(用户态的申请和大部分内核态的社区都是可以等待内存释放的)的内存申请。
__alloc_pages 一般第一次遍历每一个内存区域(zone)寻找第一个可用的足够的内存块。如果一个区域满了,那么会寻找下一个区域。单数如果 CPUSETS被设置了,他就会触发内存reclaim回收。
这里Swappiness主要是在内存reclaim时候生效。
Reclaim的方式
基本上Reclaim的方式为一个是将file相关的内存进行回收,一个是将anon部分内存(即被分配出去的内存)交换到Swap分区。
Linux的内存使用的一个宗旨是尽可能使用内存。在文件被读写的时候,文件的cache会一直保留在系统内存中,一直到内存不够时候,没有主动释放这部分内存的逻辑。这样在下次读取被缓存的文件时候可以直接从内存读取,不必从磁盘进行IO操作,这样文件读取速度会更加快速。
造成的结果是其实available的内存还很多的情况下,仍然会有内存不够,触发Reclaim逻辑,将一部分内存交换到Swap分区。
Swappiness生效方式
Swappiness是在get_scan_count函数使用的。
如下代码显示:Swap满时候,这个参数无影响。
2195 / If we have no swap space, do not bother scanning anon pages. /
2196 if (!sc->may_swap || mem_cgroup_get_nr_swap_pages(memcg) <= 0) {
2197 scan_balance = SCAN_FILE;
2198 goto out;
2199 }
在Cgroup的mem还没达到limit时候,并且Swappiness为0,也仅仅扫描file cache部分。即不会考虑交换出去。
2201 /
2202 Global reclaim will swap to prevent OOM even with no
2203 swappiness, but memcg users want to use this knob to
2204 disable swapping for individual groups completely when
2205 using the memory controller's swap limit feature would be
2206 too expensive.
2207 */
2208 if (!global_reclaim(sc) && !swappiness) {
2209 scan_balance = SCAN_FILE;
2210 goto out;
2211 }
当系统接近OOM时候,并且swapiness非0,那么会平等的扫描anon和file的内存。
2213 /
2214 Do not apply any pressure balancing cleverness when the
2215 system is close to OOM, scan both anon and file equally
2216 (unless the swappiness setting disagrees with swapping).
2217 */
2218 if (!sc->priority && swappiness) {
2219 scan_balance = SCAN_EQUAL;
2220 goto out;
2221 }
当内存达到limit时候,会只释放申请的内存。这里结合前面提到的分支,可以知道,当Swappiness为0时候,没有达到limit只释放file cache,当达到limit时候,才考虑切换内存到swap中。
/*
* Prevent the reclaimer from falling into the cache trap: as
* cache pages start out inactive, every cache fault will tip
* the scan balance towards the file LRU. And as the file LRU
* shrinks, so does the window for rotation from references.
* This means we have a runaway feedback loop where a tiny
* thrashing file LRU becomes infinitely more attractive than
* anon pages. Try to detect this based on file LRU size.
*/
if (global_reclaim(sc)) {
unsigned long pgdatfile;
unsigned long pgdatfree;
int z;
unsigned long total_high_wmark = 0;
pgdatfree = sum_zone_node_page_state(pgdat->node_id, NR_FREE_PAGES);
pgdatfile = node_page_state(pgdat, NR_ACTIVE_FILE) +
node_page_state(pgdat, NR_INACTIVE_FILE);
for (z = 0; z < MAX_NR_ZONES; z++) {
struct zone *zone = &pgdat->node_zones[z];
if (!managed_zone(zone))
continue;
total_high_wmark += high_wmark_pages(zone);
}
if (unlikely(pgdatfile + pgdatfree <= total_high_wmark)) {
/*
* Force SCAN_ANON if there are enough inactive
* anonymous pages on the LRU in eligible zones.
* Otherwise, the small LRU gets thrashed.
*/
if (!inactive_list_is_low(lruvec, false, memcg, sc, false) &&
lruvec_lru_size(lruvec, LRU_INACTIVE_ANON, sc->reclaim_idx)
>> sc->priority) {
scan_balance = SCAN_ANON;
goto out;
}
}
}
当inactive的cache页足够的时候,只释放file cache。
/*
* If there is enough inactive page cache, i.e. if the size of the
* inactive list is greater than that of the active list *and* the
* inactive list actually has some pages to scan on this priority, we
* do not reclaim anything from the anonymous working set right now.
* Without the second condition we could end up never scanning an
* lruvec even if it has plenty of old anonymous pages unless the
* system is under heavy pressure.
*/
if (!inactive_list_is_low(lruvec, true, memcg, sc, false) &&
lruvec_lru_size(lruvec, LRU_INACTIVE_FILE, sc->reclaim_idx) >> sc->priority) {
scan_balance = SCAN_FILE;
goto out;
}
这里强调一下,swappiness的一般作用这里开始涉及。是把anon_prio设成相应的swappiness,file_prio 设成200-anon_prio。
scan_balance = SCAN_FRACT;
/*
* With swappiness at 100, anonymous and file have the same priority.
* This scanning priority is essentially the inverse of IO cost.
*/
anon_prio = swappiness;
file_prio = 200 - anon_prio;
这里进一步使用anon_prio和file_prio来获取ap和fp
/*
* The amount of pressure on anon vs file pages is inversely
* proportional to the fraction of recently scanned pages on
* each list that were recently referenced and in active use.
*/
ap = anon_prio * (reclaim_stat->recent_scanned[0] + 1);
ap /= reclaim_stat->recent_rotated[0] + 1;
fp = file_prio * (reclaim_stat->recent_scanned[1] + 1);
fp /= reclaim_stat->recent_rotated[1] + 1;
具体其他的细节或者后续的算法,留待后续分析。
总结
Swappiness的控制方式主要是在内存紧张时候才会触发(这里是指free的内存低)。具体如下:
- 当swappiness为0,那么在available内存充足情况,只释放file cache,当available内存不足情况下,那么会将一些内存交换到swap空间。
- Swappiness不为0,那么他的值大小主要是控制每次内存紧张时候,切换到swap和文件缓存释放的比例。
注意:大部分人误以为是控制内存剩余比例到swappiness值时,去切换内存到swap,这个是错误的。