传统内存统计方式
总内存计算
total = used + free + kernel_used
因为kernel_used 不能统计出来,所以这里直接加对不上。
Kernel的动态内存分配通过以下几种接口:
alloc_pages/__get_free_page: 以页为单位分配
vmalloc: 以字节为单位分配虚拟地址连续的内存块
kmalloc: 以字节为单位分配物理地址连续的内存块,它是以slab为基础的,使用slab层的general caches — 大小为2^n,名称是kmalloc-32、kmalloc-64等。
- 通过slab层分配的内存会被精确统计。
- 通过vmalloc、kmalloc分配的内存也有统计。
- 而通过alloc_pages分配的内存不会自动统计,除非调用alloc_pages的内核模块或驱动程序主动进行统计,否则我们只能看到free memory减少了。
所以,kernel在分配内存时并没有完整记录,所以无法精确统计。
系统实际可使用内存
free不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以这部分可回收的内存加上MemFree才是系统可用的内存。
所以应该看的是available。
available = free + 可回收内存
传统统计方式在我们使用场景的缺陷
- 传统方式必须依赖free工具
- 我们必须轮询调用free然后解析
- 即使系统内存足够用,我们也得一直查询
内存水位标记watermark介绍
Linux为内存的使用设置了三种内存水位标记:high、low、min。他们所标记的含义分别为:
- 剩余内存在high以上表示内存剩余较多,目前内存使用压力不大。
- high-low的范围表示目前剩余内存存在一定压力。
- low-min表示内存开始有较大使用压力,剩余内存不多了。
- min是最小的水位标记,当剩余内存达到这个状态时,就说明内存面临很大压力。
- 小于min这部分内存,内核是保留给特定情况下使用的,一般不会分配。
linux内存回收机制
当系统剩余内存低于watermark[low]的时候,内核就会触发kswapd内核线程执行,进行内存回收。
如果一个任务需要分配内存,内存消耗导致剩余内存达到了或超过了watermark[min]时,就会触发直接回收(direct reclaim)
watermark相关值如何计算
依据 /proc/sys/vm/min_free_kbytes
$ cat /proc/sys/vm/min_free_kbytes 67584
这个参数本身决定了系统中每个zone的watermark[min]的值大小。
然后内核根据min的大小并参考每个zone的内存大小分别算出每个zone的low水位和high水位值。
一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。
所以,当前系统watermark[min] 的值为 67584 / 1024 = 66 M。
查看各个zone中的min
系统内存区域总共分为5个zone:DMA、DMA32、Normal,Movable,Device
查看zone中的min值命令如下:
cat /proc/zoneinfo
这些值以page为单位的,所以计算时要乘以4K,得出的67576基本等于min_free_kbytes的值67584
所以,计算方式:(16+996+15882)*4=67576。
首先得到总的min_free_kbytes,然后根据各个zone的物理大小,计算出各自z
one的min。
low和high值的计算
一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。
low的值
high的值,下面的值我们删除了一些,是经过删减的显示
$ cat /proc/zoneinfo | grep high high 24 high 1494 high 23822
验算:
15882/4=3970.5, 15882+3970 = 19852
15882/2 = 7941 ,15882+7941 = 23823
进过验算,确实是这样
内存回收策略
$ cat /proc/sys/vm/swappiness 60
这个文件是个百分比,默认值60,范围0~100.
swappiness的值是用来控制内存回收时,回收的匿名页更多一些还是回收的file cache更多一些 。
如果swappiness设置为100,那么匿名页和文件将用同样的优先级进行回收。
eBPF采集内存剩余空间原理
我们不需要时时刻刻去采集内存剩余空间,只有在内存剩余空间在watermark[low]时,才触发采集动作,这样可以有效避免大量无用的内存剩余数据。
# BPF program
prog = """
#include <uapi/linux/ptrace.h>
#include <linux/mmzone.h>
#include <linux/sched.h>
#ifdef CONFIG_MMU
#define ALLOC_OOM 0x08
#else
#define ALLOC_OOM ALLOC_NO_WATERMARKS
#endif
#define ALLOC_HARDER 0x10 /* try to alloc harder */
#define ALLOC_HIGH 0x20 /* __GFP_HIGH set */
#define ALLOC_CPUSET 0x40 /* check for correct cpuset */
#define ALLOC_CMA 0x80 /* allow allocations from CMA areas */
// define output data structure in C
struct data_t {
u32 pid;
u32 order;
u64 ts;
long free_pages;
long min;
long lowmem_reserve;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
static long __zone_watermark_unusable_free(struct zone *z,
unsigned int order, unsigned int alloc_flags)
{
const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));
long unusable_free = (1 << order) - 1;
/*
* If the caller does not have rights to ALLOC_HARDER then subtract
* the high-atomic reserves. This will over-estimate the size of the
* atomic reserve but it avoids a search.
*/
if (likely(!alloc_harder))
unusable_free += z->nr_reserved_highatomic;
#ifdef CONFIG_CMA
/* If allocation can't use CMA areas don't use free CMA pages */
if (!(alloc_flags & ALLOC_CMA))
unusable_free += zone_page_state(z, NR_FREE_CMA_PAGES);
#endif
return unusable_free;
}
int kprobe__zone_watermark_ok(struct pt_regs *ctx, struct zone *z, unsigned int order, unsigned long mark,
int highest_zoneidx, unsigned int alloc_flags, long free_pages)
{
//bpf_trace_printk("free_pages before = %ld, order = %ld \\n", free_pages, order);
long min = mark;
int o;
const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));
struct data_t data = {};
/* free_pages may go negative - that's OK */
/* 计算内存剩余空间 */
free_pages -= __zone_watermark_unusable_free(z, order, alloc_flags);
//bpf_trace_printk("free_pages after = %ld, order = %ld \\n", free_pages, order);
if (alloc_flags & ALLOC_HIGH)
min -= (unsigned long)min / 2;
if (unlikely(alloc_harder)) {
if (alloc_flags & ALLOC_OOM)
min -= (unsigned long)min / 2;
else
min -= (unsigned long)min / 4;
}
/*
* Check watermarks for an order-0 allocation request. If these
* are not met, then a high-order request also cannot go ahead
* even if a suitable page happened to be free.
*/
/* 当剩余内存数量大于 min与备份空间和,不进行上报流程,所以,这里进行了筛选 */
if (free_pages > min + z->lowmem_reserve[highest_zoneidx]) {
//bpf_trace_printk("free_pages after watermark check = %ld\\n", free_pages);
return 0;
}
//bpf_trace_printk("free_pages after watermark check = %ld, order = %ld \\n", free_pages, order);
//bpf_trace_printk("free_pages after watermark min = %ld, lowmem_reserve = %ld \\n", min, z->lowmem_reserve[highest_zoneidx]);
u64 id = bpf_get_current_pid_tgid();
data.pid = id >> 32; // PID is higher part
data.ts = bpf_ktime_get_ns() / 1000;
data.free_pages = free_pages;
data.order = order;
data.min = min;
data.lowmem_reserve = z->lowmem_reserve[highest_zoneidx];
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
# load BPF program
b = BPF(text=prog)
# Attach Kprobe to the target function
# 我们hook __zone_watermark_ok 函数
b.attach_kprobe(event="__zone_watermark_ok", fn_name="kprobe__zone_watermark_ok")
# Print trace output
print("Tracing __zone_watermark_ok function. Hit Ctrl+C to exit.")
# header
print("%-20s %-20s %-16s %-16s %-10s %-16s %-16s" % ("TIME(s)", "COMM", "PID", "FREE(MB)", "ORDER", "MIN(MB)", "RESERVE(MB)"))
# process event
start = 0
def print_event(cpu, data, size):
event = b["events"].event(data)
time_s = (float(event.ts)) / 1000000
printb(b"%-20.9f %-20s %-16d %-16d %-10d %-16d %-16d" % (time_s, event.comm, event.pid, event.free_pages * 4 / 1024, event.order, event.min * 4 / 1024, event.lowmem_reserve * 4 / 1024))
# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
原创文章,转载请注明出处