作者简介:许庆伟,Linux Kernel Security Researcher & Performance Developer
众所周知,Linux内核和CPU处理器负责将虚拟内存映射到物理内存。为了提高效率,在一个称为页的内存组中创建一个内存映射,其中每个页的大小根据处理器的实际情况而来。尽管大多数处理器也支持更大的页,但默认通常是4 KB,。内核可以从页空闲列表中为物理内存页的申请提供分配,并且为了提高效率,为每个DRAM组和CPU均设计了维护这些请求的方案。内核程序可以通过分配器(比如slab分配器)从这些空闲列表中使用内存。
典型的内存页面使用生命周期的具体步骤如下:
内存申请分配频繁: 对于繁忙的应用程序,用户层的内存分配可能每秒发生数百万次,并且Load、Store指令和MMU查找更加频繁,甚至每秒可以发生数十亿次。
定期激活kswapd,扫描不活动页面和活动页面的LRU列表,以便找到可用的内存。当空闲内存超过低阈值时,它将被唤醒,当空闲内存超过高阈值时,它将回到睡眠状态。
Kswapd也会协调后台的页面召回动作,这些操作不会直接降低应用程序的性能,除了存在CPU和磁盘I/O竞争的风险。如果kswapd无法快速地释放内存,它将超过可调的最小阈值并开始直接回收; 在这种模式下,分配被阻塞(暂停),并同步等待页面被释放。
调用内核shrinker函数触发直接回收: 这些释放的内存可能会保留在cache中,包括内核slab。
Swap提供了一种在内存不足情况下的操作模式: 进程可以继续分配内存,但要将不经常使用的页面交换到Swap中,缺点就是会使应用程序运行变慢得多。
一些产线上的系统需要在不交换的情况下这样操作,对于那些关键系统来说可能有许多冗余(和健康的)服务器,更倾向于使用启动了Swap的服务器。(例如在Netflix云上的实例。)
如果非Swap的内存不足,内核oom killer将选择牺牲一个进程。为了避免这种情况,请将应用程序配置为永远不超过系统的内存限制。
Linux 内核的oom killer是释放内存的最后手段: 通过规则找到需要被kill掉的进程,,并通过杀死它们来牺牲它们。Linux提供了调整系统和每个进程中OOM killer的方法。
随着时间的推移,剩余的内存变得越来越碎片化,使得内核很难根据需要分配更大的连续block。此时内核开始将page压缩并移动,从而释放出连续的空间。
Linux将空闲内存用于文件系统Cache,并在需要时将其恢复到空闲状态。这样会导致在Linux启动后,系统上报的可用内存趋于零,导致用户担心这种情况。通过调整参数vm.swappiness, Linux可以选择从文件系统Cache或者通过Swap来释放内存。
传统的性能工具提供了许多基于容量的内存使用统计信息,包括每个进程和系统范围内使用了多少虚拟内存和物理内存。分析内存的使用情况超出了基本知识的范围,例如page fault率、库中的分配、运行时或应用程序需要为每个分配内置工具,或者可以使用像Valgrind这样的虚拟机分析器,但是可能导致目标应用程序在检测期间运行速度慢10倍以上。这个时候BPF工具更高效,性能损耗更低的优势就体现出来了。
此外,还有一些用于内存分析的BPF工具: kmem、kpages、slabratetop、numamove
oomkill是一个BCC和bpftrace工具,用于跟踪低内存时oom killer事件并打印详细信息(如平均负载等)。平均负载为OOM上的系统状态提供了一些额外的上下文,显示系统是变得繁忙还是稳定。
该工具使用kprobes跟踪oom_kill_process()函数并打印各种详细信息,读取/proc/loadavg可以获得平均负载。在调试OOM事件时,可以根据需要添加功能客制化工具,以便打印其他详细信息。此外,该工具没有使用oom的tracking points,该功能可以显示关于如何选择task的更详细信息。
memleak同样是一个BCC工具,它可以跟踪内存分配和释放事件以及堆栈的分配信息。随着时间的推移,它可以显示尚未被释放的分配信息。
仅凭借Memleak无法确定这些异常分配是否为真正的内存泄漏(指的是未被引用且永远不会释放的已分配内存),是异常的内存增长还是长期稳定的分配。为了区分它们,需要进一步研究和理解源代码。当然还有很多用于内存分析的小工具。这里就不一一列举。详情请查看Brendan Gregg的相关书籍,在书里你可以找到相关答案。
5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在上面的【人人都是极客】公众号内回复「peter」,即可免费获取!!
记得点击分享、赞和在看,给我充点儿电吧