我看远山,远山悲悯
写在前面
博文内容为 使用 BPF
工具跟踪 Linux 用户态小内存分配(brk,sbrk)
理解不足小伙伴帮忙指正 :),生活加油
我看远山,远山悲悯
持续分享技术干货,感兴趣小伙伴可以关注下 ^_^
brk 内存分配简单概述 一般来说,应用程序的数据存放于堆内存
中,堆内存通过brk(2)
系统调用进行扩展,对于比较常见的 libc
分配器的 malloc
等函数,在内存分配,小内存块使用 brk
分配,一般在空闲列表耗尽时,会上移堆顶指针
,扩展虚拟地址空间
,对于大块内存
,直接调用我们上篇博文讲的 mmap
方式,创建独立的内存段
,一般按页对齐
,直接映射进程虚拟地址空间
。
通过跟踪 brk(2)
调用,可以展示对应的用户态调用栈信息
,已经调用次数统计。同时还有一个sbrk(2)变体调用
。在Linux中,sbrk(2)
是以库函数形式实现的,内部仍然使用 brk(2)
系统调用。
跟踪 brk(2)
调用的方式有很多,可以通过静态跟踪 tracepoint
对 syscall:syscall_enter_brk
内核跟踪点来跟踪,用 BCC版本的trace(8)
来获取每个事件的信息,也可以用stackcount(8)
来获取频率统计信息,还可以用bpfrace
版本的单行程序来获取,甚至可以用perf(1)命令获取。
下面的实验使用的环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$hostnamectl Static hostname: liruilongs.github.io Icon name: computer-vm Chassis: vm 🖴 Machine ID: 7deac2815b304f9795f9e0a8b0ae7765 Boot ID: becf4dd2ec01440ea40c992c5484b5b2 Virtualization: vmware Operating System: Rocky Linux 9.4 (Blue Onyx) CPE OS Name: cpe:/o:rocky:rocky:9::baseos Kernel: Linux 5.14.0-427.20.1.el9_4.x86_64 Architecture: x86-64 Hardware Vendor: VMware, Inc. Hardware Model: VMware Virtual Platform Firmware Version: 6.00 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
这里先准备一个测试脚本,调用 malloc
函数多次分配内存,观察 sbrk(0)
的变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat ./malloc_free.c // demo3_malloc_free.c int main () { printf ("PID = %d\n" , getpid()); printf ("Before malloc: brk = %p\n" , sbrk(0)); // 分配大块内存(可能触发 brk 增长) void *ptr1 = malloc (12 * 1024); // 12KB printf ("After malloc 12KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr2 = malloc(120* 1024); // 120KB printf ("After malloc 120KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr3 = malloc(4 * 1024); // 4KB printf ("After malloc 4KB: brk = %p\n" , sbrk(0)); sleep(30); return 0; }
sbrk(0)
为当前堆顶指针,每次分配内存,堆顶指针都会增加,这里分配了 12KB,120KB,4KB
,观察堆顶指针的变化。
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$vim malloc_free.c ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$gcc -g malloc_free.c -o malloc_free ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./malloc_free PID = 2916 Before malloc: brk = 0x1ae2000 After malloc 12KB: brk = 0x1ae2000 After malloc 120KB: brk = 0x1b03000 After malloc 4KB: brk = 0x1b03000 ^C
可以看到上面的输出,只有在分配120KB
的时候,堆顶指针
发生了变化(0x1ae2000 -> 0x1b03000),说明进行了堆内存
的扩展,brk(2)
系统调用被调用了。其他位置虽然也有调用,但是并不是进行了堆扩展。
trace trace 命令是一个 BCC 工具,可以对多个数据源进行跟踪。这里我们使用它来跟踪 内核态跟踪点 sys_enter_brk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./malloc_free PID = 3098 Before malloc: brk = 0x15cf000 After malloc 12KB: brk = 0x15cf000 After malloc 120KB: brk = 0x15f0000 After malloc 4KB: brk = 0x15f0000 ^C ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$/usr/share/bcc/tools/trace -U 't:syscalls:sys_enter_brk "brk(0x%lx)", args->brk' PID TID COMM FUNC - 3098 3098 malloc_free sys_enter_brk brk(0x0) brk+0xb [ld-linux-x86-64.so.2] [unknown] [ld-linux-x86-64.so.2] 3098 3098 malloc_free sys_enter_brk brk(0x0) brk+0xb [libc.so.6] 3098 3098 malloc_free sys_enter_brk brk(0x15cf000) brk+0xb [libc.so.6] 3098 3098 malloc_free sys_enter_brk brk(0x15f0000) brk+0xb [libc.so.6] ^C
我们来分析一下上面的输出
brk (0x15f0000) 调用
:对应于程序中第二次 120KB
的内存分配,移动了 brk
指针来扩大堆空间。
剩下的 brk
调用,前面两次调用,可能是程序启动时的初始化调用。第三次调用可能是 libc 的内部管理
stackcount 我们通过 stackcount
来统计 brk
调用的次数,确认上面的输出
stackcount(8)
也是一个综合工具,可以对导致某事件发生的函数调用栈进行计数
。和trace(8)
一样,事件源可以是内核态或用户态函数、内核跟踪点或者USDT探针
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./malloc_free PID = 2918 Before malloc: brk = 0x1ca4000 After malloc 12KB: brk = 0x1ca4000 After malloc 120KB: brk = 0x1cc5000 After malloc 4KB: brk = 0x1cc5000 ^C ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$/usr/share/bcc/tools/stackcount -TPU t:syscalls:sys_enter_brk Tracing 1 functions for "t:syscalls:sys_enter_brk" ... Hit Ctrl-C to end. ^C 15:15:12 brk [unknown] b'malloc_free' [2918] 1 brk b'malloc_free' [2918] 3 Detaching...
可以看到调用栈,总共有 4 次 brk 调用,其中 3 次直接来自应用程序,1 次通过未知库路径(可能是动态链接器)
brkstack brkstack 是一个 bpftrace 工具,可以跟踪堆内存分配,包括堆内存的分配和释放。它使用 bpftrace
的 tracepoint
机制,跟踪内核中的 sys_enter_brk
事件。
代码地址
https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch07_Memory/brkstack.bt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #!/usr/local/bin/bpftrace /* * brkstack - Count brk(2) syscalls with user stacks. * * See BPF Performance Tools, Chapter 7, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License" ). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 26-Jan-2019 Brendan Gregg Created this. */ tracepoint:syscalls:sys_enter_brk { @[ustack, comm] = count(); }
代码比较简单,实际上和上面的工具类似,可以看作是上面两个工具的结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./malloc_free PID = 2978 Before malloc: brk = 0x14d7000 After malloc 12KB: brk = 0x14d7000 After malloc 120KB: brk = 0x14f8000 After malloc 4KB: brk = 0x14f8000 ^C ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$./brkstack.bt Attaching 1 probe... ^C @[2978, __brk+11 0x7f7fc5a42b68 , malloc_free]: 1 @[2978, brk+11 , malloc_free]: 3 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$
进行了一次堆扩展,所以调用了一次,但是包含着最后三次的中,这里的 Demo 是分配的三次内存,会不会对应 三次 brk 调用? 可以修改上面的脚本验证这一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat malloc_free.c // demo3_malloc_free.c int main () { printf ("PID = %d\n" , getpid()); printf ("Before malloc: brk = %p\n" , sbrk(0)); // 分配大块内存(可能触发 brk 增长) void *ptr1 = malloc (12 * 1024); // 12KB printf ("After malloc 12KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr2 = malloc(120* 1024); // 120KB printf ("After malloc 120KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr3 = malloc(4 * 1024); // 4KB printf ("After malloc 4KB: brk = %p\n" , sbrk(0)); void *ptr4 = malloc(4 * 1024); // 4KB printf ("After malloc 4KB: brk = %p\n" , sbrk(0)); void *ptr5 = malloc(120 * 1024); // 4KB printf ("After malloc 120KB: brk = %p\n" , sbrk(0)); sleep(30); return 0; } ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
运行之后发现,多次内存分配,但是堆还是只扩展了一次,而且 brk 的调用次数也没有发生改变,还是3 次,所以可以验证我们上面的猜测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./malloc_free PID = 2335 Before malloc: brk = 0x1a46000 After malloc 12KB: brk = 0x1a46000 After malloc 120KB: brk = 0x1a67000 After malloc 4KB: brk = 0x1a67000 After malloc 4KB: brk = 0x1a67000 After malloc 120KB: brk = 0x1a67000 ^C ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$./brkstack.bt Attaching 1 probe... ^C @[2335, __brk+11 0x7f1a6dc89b68 , malloc_free]: 1 @[2335, brk+11 , malloc_free]: 3
这里我们可以看到对于小内存的分配,如果发生的堆扩展,那么我们可以 brk 相关的工具来进行跟踪,如果是通过空闲列表直接获取,那么没有办法跟踪。
上面的Demo 中,我们在 print 中调用 sbrk(0) ,这里是否会触发 brk 调用,注释掉,然后再次运行,发现 brk 的调用次数还是3次,说明和 print 的 sbrk(0) 没有关系.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat malloc_free.c // demo3_malloc_free.c int main () { printf ("PID = %d\n" , getpid()); //printf ("Before malloc: brk = %p\n" , sbrk(0)); // 分配大块内存(可能触发 brk 增长) void *ptr1 = malloc (12 * 1024); // 12KB //printf ("After malloc 12KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr2 = malloc(120* 1024); // 120KB //printf ("After malloc 120KB: brk = %p\n" , sbrk(0)); // 再分配小块内存 void *ptr3 = malloc(4 * 1024); // 4KB //printf ("After malloc 4KB: brk = %p\n" , sbrk(0)); sleep(30); return 0; } ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$./brkstack.bt Attaching 1 probe... ^C @[3009, __brk+11 0x7fb13dfadb68 , malloc_free]: 1 @[3009, brk+11 , malloc_free]: 3
这里还要说明一下,通过 brkstack
进行跟踪之后,原本的进程需要一直运行,否则跟踪到 brk
的调用,没办法显示正常的调用栈,所以这里我们使用 sleep
来让进程一直运行,然后使用 Ctrl + C
来结束跟踪。
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$./brkstack.bt Attaching 1 probe... ^C @[3011, 0x7f8f49a0511b 0x7f8f499ffb68 , malloc_free]: 1 @[3011, 0x7f8f4970348b , malloc_free]: 3
博文部分内容参考 © 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
《BPF Performance Tools》
© 2018-至今 liruilonger@gmail.com , 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)