内核和处理器负责将虚拟内存映射到物理内存。为了提高效率,会在称为页面的内存组中创建内存映射,其中每个页面的大小是处理器的详细信息。尽管大多数处理器也支持更大的容量,但通常有4 KB,Linux称其为 hugepage大页面。内核可以从其自己的空闲列表中为物理内存页面请求提供服务,内核为每个DRAM组和CPU维护这些请求以提高效率。内核自己的软件也通常通过内核分配器(例如slab分配器)从这些空闲列表中消耗内存。
内存页和交换
典型的用户内存页面的生命周期如图7-2所示,其中列举了以下步骤:
1. 应用程序从内存分配请求开始(例如,libc malloc() )。
2. 分配库可以从其自己的空闲列表中为内存请求提供服务,或者它可能需要扩展虚拟内存来容纳。根据分配库,它将:
1. 通过调用brk() syscall并将堆内存用于分配来扩展堆的大小。
2. 通过mmap() 系统调用创建一个新的内存段。
3. 稍后,应用程序尝试通过存储和加载指令使用分配的内存范围,这涉及调用处理器内存管理单元(MMU)进行虚拟到物理地址的转换。至此,虚拟内存的谎言就暴露出来了:该地址没有映射!这会导致称为页面错误的MMU错误。
4. 页面错误由内核处理,内核建立从其物理内存可用列表到虚拟内存的映射,然后将该映射通知MMU以供以后查找。现在,该过程占用了额外的物理内存页面。进程使用的物理内存量称为其驻留集大小(RSS)。
5. 当系统上的内存需求过多时,内核页面输出守护程序(kswapd)可能会寻找可用的内存页面。它将释放三种类型的内存中的一种(尽管只有(c)如图7-2所示,因为它显示了用户内存页面的生命周期):
1. 从磁盘读取但未修改的文件系统页面(称为“由磁盘支持”):可以立即释放这些页面,并在需要时简单地重新读取。这些页面是应用程序可执行的文本,数据和文件系统元数据。
2. 已修改的文件系统页面:这些是“脏”的,必须先写入磁盘,然后才能释放它们。
3. 应用程序内存页面:由于它们没有文件来源,因此被称为匿名内存。如果正在使用交换设备,则可以先将它们存储在交换设备上来释放它们。将页面写到交换设备称为交换(在Linux上)。
内存分配请求通常是频繁的活动:对于繁忙的应用程序,用户级别的分配每秒可能发生数百万次。加载和存储指令以及MMU查找更加频繁。它们每秒可能发生数十亿次。在图7-2中,这些箭头以粗体显示。其他活动相对较少:brk()和mmap()调用,页面错误和页面退出(较亮的箭头)。
page-out daemon页面输出守护程序
定期激活页面输出守护程序(kswapd)以扫描非活动和活动页面的LRU列表,以寻找可用的内存。如图7-3所示,当空闲内存越过低阈值时它将被唤醒,而当空闲内存越过高阈值时将回到睡眠状态。
Tool
Type | Description | |
dmesg | Kernel log | OOM killer event details |
swapon | Kernel statistics | Swap device usage |
free | Kernel statistics | System-wide memory usage |
ps | Kernel statistics | Process statistics, including memory usage |
pmap | Kernel statistics | Process memory usage by segment |
vmstat | Kernel statistics | Various statistics, including memory |
sar | Kernel statistics | Can show page fault and page scanner rates |
perf | Software events, hardware statistics, hardware sampling | Memory-related PMC statistics and event sampling |
用于内存分析相关的BPF工具
Tool
Source | Target | Description | |
oomkill | BCC/BT | OOM | Shows extra info on OOM kill events 显示oom相关的事件 |
memleak | BCC | Sched | Shows possible memory leak code paths 显示可能的内存泄漏代码路径 |
mmapsnoop | Book | Syscalls | Traces mmap(2) calls system-wide 跟踪系统范围内的mmap调用 |
brkstack | Book | Syscalls | Shows brk() calls with user stack traces 显示带有用户堆栈跟踪的brk()调用 |
shmsnoop | BCC | Syscalls | Traces shared memory calls with details 跟踪共享内存调用的详细信息 |
faults | Book | Faults | Shows page faults, by user stack trace 通过用户堆栈跟踪显示页面错误 |
ffaults | Book | Faults | Shows page faults, by filename 通过文件名显示页面错误 |
vmscan | Book | VM | Measures VM scanner shrink and reclaim times 测量vm scaner的收缩和回收时间 |
drsnoop | BCC | VM | Traces direct reclaim events, showing latency 跟踪直接回收事件,显示延迟 |
swapin | Book | VM | Shows swap-ins by process 按进程显示swap情况 |
hfaults | Book | Faults | Shows huge page faults, by process 按进程显示巨页错误情况 |
此外,还有几个用于内存分析的BPF工具: kmem 、kpages 、 slabratetop 、 numamove
oomkill
oomkill是一个BCC和bpftrace工具,用于跟踪内存不足杀手事件并打印详细信息(例如平均负载)。平均负载为OOM时的系统状态提供了一些额外的上下文,显示了系统是否正在变得忙碌或稳定。
仅memleak不能告诉您这些分配是否是真正的内存泄漏(内存泄漏:指的是没有引用并且永远不会释放的已分配内存),内存增长还是长期分配。为了区分它们,需要研究和理解代码路径。