内存管理-40-watermark内存水位
基于msm-5.4
模块内调用路径:
postcore_initcall //page_alloc.c 【】内核初始化 init_per_zone_wmark_min //page_alloc.c /proc/sys/vm/extra_free_kbytes //【】sysctl节点配置。 /proc/sys/vm/min_free_kbytes //【】sysctl节点配置,会同时更新 user_min_free_kbytes。 min_free_kbytes_sysctl_handler //page_alloc.c /proc/sys/vm/watermark_scale_factor //【】sysctl节点配置。 watermark_scale_factor_sysctl_handler //page_alloc.c setup_per_zone_wmarks //page_alloc.c __setup_per_zone_wmarks //page_alloc.c zone->_watermark[WMARK_MIN] = tmp; zone->watermark_boost = 0; zone->_watermark[WMARK_LOW] = min_wmark_pages(zone) + low + tmp; zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + low + tmp * 2;
一、概述
1. Linux内核内存水位(watermark)是用于管理内存分配和回收的关键机制。水位分为三个层次:min、low和high。用于判断系统当前的内存压力状态,并决定是否需要进行内存回收。
min水位:
当系统剩余的空闲内存(free memory)降到min水位以下时,表明内存非常紧张。在这种情况下,内存分配请求会被阻塞,直到内存回收完成,以防止发生OOM(Out of Memory)错误。min水位是一个临界值,低于这个值时,内存分配器会同步等待内存回收,即触发direct reclaim。
low水位:
当系统剩余的空闲内存降到low水位以下但仍在min水位以上时,表明内存面临一定的压力。在这种情况下,内核会唤醒kswapd线程进行异步内存回收,以逐步恢复空闲内存。low水位是一个警戒值,低于这个值时,kswapd会被唤醒,但内存分配请求不会被阻塞,也就是是异步回收。
high水位:
当系统剩余的空闲内存恢复到high水位以上时,表明内存压力已经缓解。kswapd线程会停止内存回收操作,系统恢复正常运行。high水位是一个安全值,内存回收的目标是将空闲内存恢复到这个水平。
2. 内存水位的实际应用
避免性能下降:
通过合理设置内存水位,可以避免因内存不足而导致的性能下降。
例如,在内存紧张时,提前唤醒 kswapd 进行内存回收,可以减少直接内存回收(direct reclaim)的频率,从而降低进程的内存分配延迟。
提高系统稳定性:
内存水位机制确保系统在内存不足时能够及时采取措施,避免OOM错误的发生。通过调整水位值,可以平衡内存使用和系统性能,提高系统的整体稳定性。
适应不同业务场景:
对于不同的业务场景,可以通过调整内存水位来优化内存管理。例如,对于需要大量缓存的业务,可以适当提高low水位,以更早地进行内存回收,保持更多的空闲内存。
二、相关数据结构
1. struct zone
struct zone { unsigned long _watermark[NR_WMARK]; //3 unsigned long watermark_boost; ... }; enum zone_watermarks { //include/linux/mmzone.h WMARK_MIN, //0 WMARK_LOW, //1 WMARK_HIGH, //2 NR_WMARK //3 }; #define min_wmark_pages(z) (z->_watermark[WMARK_MIN] + z->watermark_boost) #define low_wmark_pages(z) (z->_watermark[WMARK_LOW] + z->watermark_boost) #define high_wmark_pages(z) (z->_watermark[WMARK_HIGH] + z->watermark_boost) #define wmark_pages(z, i) (z->_watermark[i] + z->watermark_boost)
cat /proc/zoneinfo 中可以看到各个zone的水位值,打印的min/low/high值分别是 min_wmark_pages(z)/low_wmark_pages(z)/ high_wmark_pages(z) 的值,单位是pages.
成员介绍:
1.1 对 zone->watermark_boost 的赋值:
online_pages //memory_hotplug.c 【】内存热插路径更新 __offline_pages //memory_hotplug.c 【】内存热拔路径更新 postcore_initcall //【】内核启动后初始化 init_per_zone_wmark_min //page_alloc.c setup_per_zone_wmarks //page_alloc.c __setup_per_zone_wmarks zone->watermark_boost = 0; //初始化为0 __rmqueue //page_alloc.c __rmqueue_fallback //page_alloc.c steal_suitable_fallback //page_alloc.c boost_watermark //page_alloc.c zone->watermark_boost = min(zone->watermark_boost + pageblock_nr_pages, max_boost); module_init(kswapd_init) kswapd_init //vmscan.c kswapd_run //vmscan.c pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d:0", nid) //vmscan.c kswapd //vmscan.c balance_pgdat //vmscan.c zone->watermark_boost -= min(zone->watermark_boost, zone_boosts[i]);
zone->watermark_boost 初始化为0,看来其值应该是临时赋值的。推测其作用应该是临时或动态地增加某个zone的水位线值,以便在特定情况下更好地控制内存分配和回收。例如,当系统检测到内存压力较大时,可能会临时提高 WMARK_LOW 或 WMARK_HIGH 的值,以减少 kswapd 的唤醒频率或避免直接回收的发生。这种动态调整可以在系统负载变化时提供更灵活的内存管理策略,从而优化系统性能。
在某些特定的场景下,如高负载的应用启动或大量内存分配请求时,系统可能会使用 zone->watermark_boost 来临时增加水位线,防止内存不足导致的性能下降。这种机制可以帮助系统在短时间内更好地应对突发的内存需求,避免不必要的内存回收操作。
从对 zone->watermark_boost 的使用上看,主要是在判断内存水位和kswapd内存回收时使用。
三、水位更新逻辑
1. init_per_zone_wmark_min()
其调用路径见前文。
int __meminit init_per_zone_wmark_min(void) //page_alloc.c { unsigned long lowmem_kbytes; int new_min_free_kbytes; /* 计算此ZONE_DMA和ZONE_NORMAL中超出high水线的页面数之和,由于此时high水位线还没初始化,因此等于各zone的页面数之和 */ lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10); /* 只是对数值开平方,不对单位KB开平方,结果还取KB为单位 */ new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16); /* user_min_free_kbytes 默认是-1,但是通过sysctl更新 min_free_kbytes 时也会更新它 */ if (new_min_free_kbytes > user_min_free_kbytes) { /* 系统让 min_free_kbytes 的初始化值介于128k~64M之间,但是之后通过sysctl接口设置就没这个限制 */ min_free_kbytes = new_min_free_kbytes; if (min_free_kbytes < 128) min_free_kbytes = 128; if (min_free_kbytes > 65536) min_free_kbytes = 65536; } else { pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n", new_min_free_kbytes, user_min_free_kbytes); } /* 计算每个zone的min、low、high水位值,其中min是按比例分配给各个zone的,low和high是在min的基础上加delta值得到。*/ setup_per_zone_wmarks(); /* 根据处理器数量和每个zone的内存量来计算阈值 */ refresh_zone_stat_thresholds(); /* 初始化 lowmem_reserve, 见前面博客 */ setup_per_zone_lowmem_reserve(); return 0; }
1.1 setup_per_zone_wmarks()
void setup_per_zone_wmarks(void) { /* 静态spinlock进行保护,确保串行设置 */ static DEFINE_SPINLOCK(lock); spin_lock(&lock); __setup_per_zone_wmarks();- spin_unlock(&lock); }
1.1.1 __setup_per_zone_wmarks()
static void __setup_per_zone_wmarks(void) //page_alloc.c { /* 将KB转换为page单位,min_free_kbytes 初始化时系统计算出来的值,也可以通过sysctl文件进行设置 */ unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10); /* extra_free_kbytes 默认是0,可通过sysctl文件进行配置 */ unsigned long pages_low = extra_free_kbytes >> (PAGE_SHIFT - 10); unsigned long lowmem_pages = 0; struct zone *zone; unsigned long flags; /* 统计除了ZONE_HIGHMEM这个zone外,buddy管理的总内存页数 */ for_each_zone(zone) { if (!is_highmem(zone)) lowmem_pages += zone_managed_pages(zone); //zone->managed_pages } /* 针对每个zone设置min low high水位线 */ for_each_zone(zone) { u64 tmp, low; spin_lock_irqsave(&zone->lock, flags); /* 每个zone按其managed_pages的页数等比例划分pages_min #### */ tmp = (u64)pages_min * zone_managed_pages(zone); do_div(tmp, lowmem_pages); /* vm_total_pages 表示所有zones中高于high水位的页面数之和,与min相比,相当于分母变小了 */ low = (u64)pages_low * zone_managed_pages(zone); do_div(low, vm_total_pages); // /* 64位机器上不会有highmem区域,因此不考虑该情况 */ if (is_highmem(zone)) { ... } else { /* 对于低端内存,将每个zone等比例划分的值直接赋值给 _watermark[MIN] */ zone->_watermark[WMARK_MIN] = tmp; } /* * mult_frac(x, y, z) = x * y / z * * watermark_scale_factor默认是10, 可通过sysctl设置, 其主要控制high/low/min之间的delta值,其值越大delta就越大。 */ tmp = max_t(u64, tmp >> 2, mult_frac(zone_managed_pages(zone), watermark_scale_factor, 10000)); /* * 每次设置之前都现将 watermark_boost 清0了,tmp是high/low/min之间的delta值, * 若 extra_free_kbytes 没有指定则low=0, 此时若 watermark_scale_factor 的值较少,则low=5/4*min, high=6/4*min. */ zone->watermark_boost = 0; zone->_watermark[WMARK_LOW] = min_wmark_pages(zone) + low + tmp; zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + low + tmp * 2; spin_unlock_irqrestore(&zone->lock, flags); } /* * 设置完内存水位线后,会更新 totalreserve_pages 的值,这个值用于评估系统正常运行时需要使 * 用的内存,该值会作用于overcommit时,判断当前是否允许此次内存分配。见《内存管理-39-lowmem_reserve低端预留内存》 */ calculate_totalreserve_pages(); }
1.2 refresh_zone_stat_thresholds()
void refresh_zone_stat_thresholds(void) { struct pglist_data *pgdat; struct zone *zone; int cpu; int threshold; /* 先将每cpu变量 pgdat->per_cpu_nodestats 清0 */ for_each_online_pgdat(pgdat) { for_each_online_cpu(cpu) { per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold = 0; } } /* 对每一个有实际物理页面的zone, */ for_each_populated_zone(zone) { struct pglist_data *pgdat = zone->zone_pgdat; unsigned long max_drift, tolerate_drift; /* 根据处理器数量和每个区域的内存量来计算阈值。内存多意味着可以将更新推迟更长时间,处理器越多则会导致更多争用。*/ threshold = calculate_normal_threshold(zone); for_each_online_cpu(cpu) { int pgdat_threshold; per_cpu_ptr(zone->pageset, cpu)->stat_threshold = threshold; /* Base nodestat threshold on the largest populated zone. */ pgdat_threshold = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold; per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold = max(threshold, pgdat_threshold); } /* * 翻译: 仅当存在 NR_FREE_PAGES 报告低水位线正常但实际上分配可能突破最低水位线的危险时,才设置 percpu_drift_mark */ tolerate_drift = low_wmark_pages(zone) - min_wmark_pages(zone); max_drift = num_online_cpus() * threshold; if (max_drift > tolerate_drift) zone->percpu_drift_mark = high_wmark_pages(zone) + max_drift; } }
1.2.1 refresh_zone_stat_thresholds()的作用?
此函数用于更新zone的状态阈值。这些阈值主要用于内存管理中的各种决策,如内存分配、回收等。
1.2.2 zone->percpu_drift_mark 的作用?
zone->percpu_drift_mark 用于管理 zone 中的 per-CPU 页分配器(per-CPU pageset)。具体作用如下:
(1) 防止 per-CPU 页分配器的偏差过大:
在多处理器系统中,每个 CPU 都有一个自己的 per-CPU pageset,用于快速分配和回收内存页。这些 per-CPU pageset 会在内存紧张时与全局的 zone 页池进行同步。
percpu_drift_mark 用于监控 per-CPU pageset 中的页数量与全局 zone 页池中的页数量之间的偏差。当 per-CPU pageset 中的页数量超过 percpu_drift_mark 时,内核会采取措施减少这种偏差,例如将多余的页归还给全局 zone 页池。
(2) 确保内存分配的公平性和一致性:
通过设置 percpu_drift_mark,内核可以确保每个 CPU 的 per-CPU pageset 不会占用过多的内存资源,从而避免某些 CPU 占用大量内存而其他 CPU 无页可用的情况。
这有助于维持系统整体的内存分配公平性和一致性,特别是在高并发和多任务环境中。
(3) 优化内存分配性能:
per-CPU pageset 的设计目的是提高内存分配的性能,因为 CPU 可以快速访问本地的内存页,而不需要跨 CPU 访问全局 zone 页池。
percpu_drift_mark 通过限制 per-CPU pageset 中的页数量,可以防止因过度积累而导致的性能下降。当 per-CPU pageset 中的页数量接近 percpu_drift_mark 时,内核会及时调整,确保内存分配的高效性。
(4) 动态调整:
percpu_drift_mark 的值可以根据系统的内存状况动态调整。例如,当系统内存紧张时,可以减小 percpu_drift_mark 的值,迫使 per-CPU pageset 更快地归还页给全局 zone 页池。
这种动态调整机制有助于内核更好地应对不同负载下的内存需求,提高系统的整体稳定性。
四、使用逻辑
1. zone_watermark_fast()
static inline bool zone_watermark_fast(struct zone *z, unsigned int order, unsigned long mark, int classzone_idx, unsigned int alloc_flags, gfp_t gfp_mask) //page_alloc.c { /* 本zone的空闲页面个数 */ long free_pages = zone_page_state(z, NR_FREE_PAGES); //zone->vm_stat[NR_FREE_PAGES] long cma_pages = 0; #ifdef CONFIG_CMA /* 如果分配不能使用 CMA 区域,则不要使用空闲的 CMA 页面 */ if (!(alloc_flags & ALLOC_CMA)) cma_pages = zone_page_state(z, NR_FREE_CMA_PAGES); //zone->vm_stat[NR_FREE_CMA_PAGES] #endif /* * 翻译: 仅针对 0 阶(分配单个页面)进行快速检查。如果失败,则需要考虑预留内存。有一种极端情况, * 即检查通过但只有高order原子预留是空闲的。如果调用者是 !atomic 的,那么它将毫无意义地搜索空 * 闲列表。这种极端情况会比较慢,但无害。 * * 这是针对0阶分配的快速检查。若不允许分配CMA内存还要从空闲页中剔除cma page,然后和水位+预留进 * 行相比。这说明CMA内存也是算在free_pages里面的。 */ if (!order && (free_pages - cma_pages) > mark + z->lowmem_reserve[classzone_idx]) return true; /* 下面就是非0阶分配或空闲页面数量低于测试水位的情况了 */ if (__zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags, free_pages)) return true; /* * Ignore watermark boosting for GFP_ATOMIC order-0 allocations * when checking the min watermark. The min watermark is the * point where boosting is ignored so that kswapd is woken up * when below the low watermark. * 翻译:检查min水位时,忽略 GFP_ATOMIC order-0 分配的水位boost。min水位是忽略boost的点, * 因此当低于low水位时,kswapd 会被唤醒。 * * 分配最初传入的是 ALLOC_WMARK_LOW。 * * 对于0阶原子分配,且有水位boost,且分配标志是WMARK_MIN,将水位调整到min水位,然后重新测试. */ if (unlikely(!order && (gfp_mask & __GFP_ATOMIC) && z->watermark_boost && ((alloc_flags & ALLOC_WMARK_MASK) == WMARK_MIN))) { mark = z->_watermark[WMARK_MIN]; return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags, free_pages); } return false; }
1.1 __zone_watermark_ok()
/* zone_watermark_fast: (z, order, mark, classzone_idx, alloc_flags, free_pages) */ bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark, int classzone_idx, unsigned int alloc_flags, long free_pages) //page_alloc.c { long min = mark; int o; /* 若有这两个标志,则表示需要尽力分配 */ const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM)); /* 检查分配出去2^order个page之后的free pages是否满足水位mark的要求(free_pages 可能会变成负数 - 没关系) */ free_pages -= (1 << order) - 1; /* ALLOC_HIGH==__GFP_HIGH,针对高优先级的分配,则击穿水位,将水线值降低一半 */ if (alloc_flags & ALLOC_HIGH) min -= min / 2; //降低至原来的1/2 /* * 翻译: 如果调用者没有 ALLOC_HARDER 权限,则从空闲页面中减去高原子预留。这将高估原子预留的大小,但可以 * 避免搜索。 * * 只有设置了ALLOC_HARDER,才能从 free_list[MIGRATE_HIGHATOMIC]的链表中进行页面分配,否则减去。 * * 若此次分配请求没有alloc_harder权限,则空闲页的数量减去高端预留内存; 若有alloc_harder权限,则继续往下击穿水位: * ALLOC_HARDER: min是原来的3/8 * ALLOC_OOM: min是原来的2/8 */ if (likely(!alloc_harder)) { /* 这里是没有设置alloc_harder的情况 */ free_pages -= z->nr_reserved_highatomic; } else { /* * 翻译: OOM 受害者可以比普通的 ALLOC_HARDER 用户更加努力,因为它肯定会很快进入退出路径并释放内存。 * 它在释放路径期间进行的任何分配都将很小且短暂。 */ if (alloc_flags & ALLOC_OOM) min -= min / 2; //降低至原来的1/4 else min -= min / 4; //降低至原来的3/8 } #ifdef CONFIG_CMA /* 若此次分配不能使用CMA内存,还要从空闲页面中减去CMA页面 */ if (!(alloc_flags & ALLOC_CMA)) free_pages -= zone_page_state(z, NR_FREE_CMA_PAGES); #endif /* * 翻译: 检查 0 阶分配请求的水位。如果不满足这些条件,那么高阶请求也无法继续,即使恰好有合适的页面可用。 * * 如果空闲页面free pages已经小于等于预留内存和限制水位之和了,说明此次分配请求不满足wartmark要求,返回false */ if (free_pages <= min + z->lowmem_reserve[classzone_idx]) return false; /* 若没有触及水位限制,且是0阶分配,返回true. 0阶的分配请求就不用担心没有大块内存供使用了 */ if (!order) return true; /* * 翻译: 对于高阶请求,从其order开始,检查至少一个合适的页面块是可用的。 * * 对于高阶分配请求,虽然上面水位检查已经过了,但是不一定有大块内存供其使用,还需要继续判断一下。 */ for (o = order; o < MAX_ORDER; o++) { struct free_area *area = &z->free_area[o]; int mt; /* 若此order的空闲列表上没有页面了,则继续下一轮循环,去查看更高阶的空闲链表 */ if (!area->nr_free) continue; /* 此阶上有空闲内存块,在 UNMOVABLE, MOVABLE, RECLAIMABLE 链表上只要有一个链表不为空,就返回true */ for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) { #ifdef CONFIG_CMA /* 这里主要是想先判断前三个迁移类型,在循环外延后判断CMA类型 */ if (mt == MIGRATE_CMA) continue; #endif /* !list_empty(&area->free_list[mt]); */ if (!free_area_empty(area, mt)) return true; } #ifdef CONFIG_CMA /* 此阶上有空闲内存块,但是不是上面三种迁移类型的。如果要分配的是CMA内存,且CMA内存链表不为空,也返回true */ if ((alloc_flags & ALLOC_CMA) && !free_area_empty(area, MIGRATE_CMA)) { return true; } #endif /* 若是harder分配,且高优先级原子分配的HIGHATOMIC内存链表不为空,也返回true。否则继续遍历更高阶内存块了 */ if (alloc_harder && !list_empty(&area->free_list[MIGRATE_HIGHATOMIC])) return true; } /* 在当前zone的free_area[]中没有找到可以满足当前order分配的内存块,返回false */ return false; }
若此函数返回false,就表示空闲页面不满足水位要求,或没有满足order的大块连续物理内存。
若 alloc_flags 中包含:
ALLOC_HIGH: 则水位要求降低至原来的4/8。
ALLOC_HARDER: 则水位要求降低至原来的3/8。
ALLOC_OOM: 则水位要求降低至原来的2/8。
ALLOC_HARDER|ALLOC_OOM: 包含这两个标志位中的任意一个,就可以访问 zone->nr_reserved_highatomic。
ALLOC_CMA: 包含这个标志可以从CMA类型的空闲链表中分配,否则空闲页面中还要减去CMA类型的内存。
五、zone->watermark_boost 作用
TODO
1. zone->watermark_boost 的作用?
zone->watermark_boost 是一个用于临时提升内存水位线的机制,主要目的是为了应对某些特定情况下的内存压力。具体作用如下:
(1) 临时提升水位线:
zone->watermark_boost 会在系统面临高内存压力时,临时提高各个内存区域(zone)的水位线(watermark),从而减少内存分配失败的风险。
这种提升是动态的,通常在系统检测到内存紧张时自动进行。
提升后的水位线可以使得内核更早地启动内存回收操作,如 kswapd 或 direct reclaim,以防止内存耗尽导致系统崩溃或其他严重问题。
(2) 应用场景:
突发性内存需求:当系统突然需要大量内存时,watermark_boost 可以快速提升水位线,确保有足够的空闲内存供分配。
内存泄漏检测:在某些情况下,watermark_boost 可以用来检测内存泄漏。通过提升水位线,系统可以更容易地识别出哪些进程占用了过多内存。
负载均衡:在多核或多节点系统中,watermark_boost 可以帮助平衡不同内存区域的使用情况,避免某个区域因内存不足而影响整体性能。
(3) 实现细节:
watermark_boost 的值通常是一个相对较小的增量,用于微调水位线。
在内存分配过程中,内核会检查当前的水位线加上 watermark_boost 后是否满足要求,如果满足则继续分配,否则启动内存回收操作。
提升后的水位线会在内存压力缓解后逐渐恢复到正常水平。
(4) 与 extra_free_kbytes 和 watermark_scale_factor 的区别:
extra_free_kbytes 是一种静态的方式,通过在 /proc/sys/vm/extra_free_kbytes 中设置一个固定的值来增加 low 与 min 之间的差值。
watermark_scale_factor 也是一种静态的方式,通过设置一个比例因子来动态调整 low 和 high 水位线,但它是基于内存总量的比例来计算的。
watermark_boost 则是一种动态的方式,它在特定情况下临时提升水位线,而不是固定增加某个值或比例。
通过这些机制,Linux 内核能够更好地管理内存,确保系统的稳定性和性能。
六、内存水线的作用
TODO
1. 内存水位线的作用:
当内存使用量低于 WMARK_MIN 时,系统会触发直接回收(direct reclaim)。
当内存使用量在 WMARK_MIN 和 WMARK_LOW 之间时,kswapd 进程会被唤醒,进行后台内存回收。
当内存使用量低于 WMARK_HIGH 时,系统会尽量避免触发直接回收,以减少对系统性能的影响。
七、调试汇总
1. 文件接口
1.1 /proc/sys/vm/min_free_kbytes
通过此文件可以直接配置水位相关的 min_free_kbytes 全局变量的值,默认取值范围大于0, 对应的handler是 min_free_kbytes_sysctl_handler() 它里面会更新 user_min_free_kbytes 的值,并调用 setup_per_zone_wmarks() 来更新每个zone的水位。
min_free_kbytes 主要用于调整 zone->_watermark[WMARK_MIN] 水位的值。
1.2 /proc/sys/vm/extra_free_kbytes
通过此文件修改水位相关的 extra_free_kbytes 全局变量的值,默认取值范围大于0, 对应的handler也是 min_free_kbytes_sysctl_handler(),也会触发水位的更新。
extra_free_kbytes 的值只会更改low与min之间的delta值,不会更新high与low之间的delta值,详见 __setup_per_zone_wmarks()。
1.3 /proc/sys/vm/watermark_boost_factor
通过此文件修改水位相关的 watermark_boost_factor 全局变量的值,默认取值范围大于0, 默认值是1500, 对应的handler是 watermark_boost_factor_sysctl_handler(), 其只是对这个全局变量进行赋值。
在 boost_eligible()/boost_watermark() 两个函数中会用到,推测应该会影响到 zone->watermark_boost。
1.4 /proc/sys/vm/watermark_scale_factor
通过此文件修改水位相关的 watermark_scale_factor 全局变量的值,默认取值范围是1--1000, 即占万分之一 -- 10%的 zone->managed_pages 的页面数量。对应的handler是 watermark_scale_factor_sysctl_handler(), 这个函数中也会调用 setup_per_zone_wmarks() 触发每个zone的水位的更新。
watermark_scale_factor 只在 __setup_per_zone_wmarks() 中使用,用于控制high/low/min三者之间的delta值,其值越大三者间的delta只也就越大,详见 __setup_per_zone_wmarks()。
2. trace接口
无
八、总结
1. 根据 alloc_flags 中指定的不同分配优先级(ALLOC_HIGH/ALLOC_HARDER/ALLOC_OOM),水位是可以击穿的(水位限制要求分别是原来的4/8、3/8、2/8),里面若指定了 ALLOC_CMA 才能从CMA空闲链表中分配内存,若指定了 ALLOC_HARDER 或 ALLOC_OOM 才可以从高优先级原子预留空闲链表中分配内存。
2. systcl接口可以调整min水线的值,和min/low/high三者之间的delta值。
参考:
linux内存回收(二)--直接内存回收机制:https://zhuanlan.zhihu.com/p/454418249
posted on 2024-11-09 11:48 Hello-World3 阅读(28) 评论(0) 编辑 收藏 举报