我看远山,远山悲悯
写在前面
博文内容涉及利用 BPF
分析用户态 mmap
大内存分配的一些工具分享以及 Demo
主要为 bcc
工具 mmapsnoop
以及其变体和 bpftrace
工具 mmapfiles
和 fmapfaults
对 mmap
私有匿名映射,共享映射,文件映射,大页映射等类型内存映射分配的跟踪Demo
理解不足小伙伴帮忙指正 :),生活加油
我看远山,远山悲悯
持续分享技术干货,感兴趣小伙伴可以关注下 ^_^
为什么监控 Linux 用户态大内存分配? Linux 的内存分配机制整体可分为两大核心部分,brk 和 mmap
。
brk 部分是用于存储用户态程序数据的小内存分配,主要依赖brk调用实现
。brk 系统调用通过移动堆顶指针来扩展或收缩堆空间,从而实现小块内存的分配与释放 。它适用于分配内存量较小、频繁申请和释放的场景
,
例如程序运行过程中临时存储少量数据的变量。在这种机制下,内存管理相对简单直接,但随着程序的复杂和内存需求的增加,可能会出现内存碎片等问题。
mmap 部分则是基于mmap的大内存分配
。当程序需要分配较大块的内存时,mmap 便发挥作用。它通过将文件、设备或匿名内存区域映射到进程的虚拟地址空间
,为程序提供连续的、较大规模的内存区域。
与brk
不同,mmap
在内存管理上更为灵活,能有效避免内存碎片问题,且在内存共享、文件映射等场景下具有独特优势
。
通过监控mmap
操作,开发者可以追踪大内存的分配与释放情况,及时发现异常的内存占用,从而定位内存泄露的源头,为优化程序性能和稳定性提供有力支持。所以对基于mmap的大内存分配进行监控,在内存泄露分析中有着至关重要的意义。
下面实验使用的Linux环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$hostnamectl Static hostname: liruilongs.github.io Icon name: computer-vm Chassis: vm 🖴 Machine ID: 7deac2815b304f9795f9e0a8b0ae7765 Boot ID: 67932b8e733942d19069be54b76278bd 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]-[~/bpfdemo] └─$
如何监控? 这里我们主要使用 mmapsnoop
和 mmapfiles
等工具基于 eBPF
技术,对大内存分配进行监控,先来了解一下 mmapsnoop
mmapsnoop mmapsnoop(8)
是一个 BCC 工具,在 《BPF Performance Tools》
一书中首次出现,主要用于跟踪全系统的mmap(2)
系统调用并打印出mmap
函数调用的详细信息, mmap
是一个非常重要的系统调用,主要用于在进程的虚拟地址空间中创建一个新的内存映射。
在C语言中,mmap
的函数原型如下:
1 2 3 #include <sys/mman.h> void *mmap (void *addr, size_t length, int prot, int flags, int fd, off_t offset) ;
参数解释:
addr :指定映射的起始地址,通常设为NULL
,让内核自动选择合适的地址。
length :映射区域的大小,以字节为单位。
prot :内存区域的访问权限,常见的标志有:
PROT_READ
:可读
PROT_WRITE
:可写
PROT_EXEC
:可执行
PROT_NONE
:不可访问
flags :控制映射的类型和行为,常见的标志有:
MAP_SHARED
:映射区域可被多个进程共享,对映射区域的修改会反映到文件中。
MAP_PRIVATE
:映射区域是私有的,对映射区域的修改不会反映到文件中。
MAP_FIXED
:如果addr
参数不为NULL
,则强制使用指定的地址进行映射。
MAP_ANON
:创建一个匿名映射,不与任何文件关联。
fd :文件描述符,用于指定要映射的文件。如果使用MAP_ANON
标志,则该参数可以忽略,通常设为-1
。
offset :文件的偏移量,指定从文件的哪个位置开始映射。
返回值:
成功时,返回映射区域的起始地址。
失败时,返回MAP_FAILED
(通常为(void *) -1
),并设置errno
来指示错误原因。
mmapsnoop
主要功能是跟踪全系统的mmap
系统调用,通过eBPF程序跟踪全系统的mmap
系统调用,包括调用的进程ID、进程名称、映射的大小、访问权限、映射标志、文件偏移量和文件名等信息。将每个mmap
请求的详细信息打印到控制台。
对应的代码地址:
https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch07_Memory/mmapsnoop.py
下面为源码的主要代码,主要实现为:
内核态 kprobe
动态跟踪:
fd_install
探针 缓存文件描述符(FD)与文件对象的映射关系(fd2file 哈希表)
__close_fd
探针 在文件描述符关闭时清理缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ................... int kprobe__fd_install (struct pt_regs *ctx, int fd, struct file *file) { u32 pid = bpf_get_current_pid_tgid() >> 32 ; struct fdkey_t key = {.fd = fd, .pid = pid}; fd2file.update(&key, &file); return 0 ; } int kprobe____close_fd (struct pt_regs *ctx, struct files_struct *files, int fd) { u32 pid = bpf_get_current_pid_tgid() >> 32 ; struct fdkey_t key = {.fd = fd, .pid = pid}; fd2file.delete (&key); return 0 ; }
内核跟踪点 tracepoint
的静态跟踪: sys_enter_mmap
跟踪点
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 // sys_enter_mmap 跟踪点 TRACEPOINT_PROBE(syscalls, sys_enter_mmap) { struct task_struct *task; struct file **fpp, *file; u32 pid = bpf_get_current_pid_tgid() >> 32; struct fdkey_t key = {.fd = args->fd, .pid = pid}; fpp = fd2file.lookup(&key); if (fpp == 0) return 0; file = *fpp; struct mmap_data_t data = { .len = args->len, .prot = args->prot, .flags = args->flags, .off = args->off, .pid = pid }; bpf_get_current_comm(&data.comm, sizeof(data.comm)); struct dentry *de = file->f_path.dentry; struct qstr d_name = {}; bpf_probe_read(&d_name, sizeof(d_name), (void *)&de->d_name); bpf_probe_read(&data.path, sizeof(data.path), d_name.name); mmap_events.perf_submit(args, &data, sizeof(data)); return 0; }
同时当前实验环境这里的内核态跟踪函数也需要换一下 __close_fd
需要替换为 __close_fd_get_file
1 2 3 4 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$grep __close_fd /proc/kallsyms ffffffff9a25af20 T __pfx___close_fd_get_file ffffffff9a25af30 T __close_fd_get_file
1 2 3 int kprobe____close_fd_get_file (struct pt_regs *ctx, struct files_struct *files, int fd)
使用方式相对简单,直接通过命令行即可,示例输出
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@liruilongs.github.io tools] PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 70696 b'mmapsnoop' RW- S--- 0 260 b'[perf_event]' 70696 b'mmapsnoop' RW- S--- 0 260 b'[perf_event]' 70697 b'crond' R-- -P-- 0 61 b'ld.so.cache' 70697 b'crond' R-E -PF- 0 386 b'libnss_systemd.so.2' 70697 b'crond' RW- -PF- 304 20 b'libnss_systemd.so.2' 70697 b'crond' R-E -PF- 0 640 b'libm.so.6' 70697 b'crond' RW- -PF- 572 8 b'libm.so.6' ................... 70703 b'date' RW- -PF- 1588 24 b'libc.so.6' 70703 b'date' R-- -P-- 0 18591 b'locale-archive' ^C[root@liruilongs.github.io tools]
部分列说明:
PROT
:内存区域的访问权限,RWE 对应读写可执行
MAP
:映射标志,体现内存映射的特性,SPFA 对应 共享私有固定匿名
FILE
:映射的文件名称
SIZE(KB)
:映射区域的大小,同样以 KB 为单位,明确了此次mmap操作分配的内存空间大小
OFFS(KB)
:文件偏移量
下面我们看一些监控 Demo
mmap私有匿名映射 下面的 Demo 创建一个匿名内存区域,一般这种场景,常用于分配大块的内存(如缓存、数据结构),进程间共享内存(需配合MAP_SHARED,但这里使用MAP_PRIVATE,内存不可共享)
,mmap
仅分配虚拟地址空间,首次访问时才分配物理页(通过缺页中断)
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 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat anon2mmap.c #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #define GB ((long long) 1024 * 1024 * 1024) int main () { long long size = 8 * GB; void *ptr = mmap(NULL , size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); if (ptr == MAP_FAILED) { perror("mmap failed" ); return 1 ; } for (long long i = 0 ; i < size; i += 4096 ) { ((char *)ptr)[i] = 'A' ; if (i % (GB) == 0 ) { printf ("Allocated %lld GB\n" , i / GB); } } printf ("Successfully mapped %lld GB\n" , size / GB); munmap(ptr, size); return 0 ; } ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
如果 mmap
没有映射文件或者设备,不使用大页,那么默认使用标准内存页对齐的方式创建虚拟内存
这里通过循环向标准内存页(4KB)写入数据,选择 4KB(4096 字节)作为步长,是为了确保每个内存页只被访问一次。触发实际的物理内存分配(Linux 采用延迟分配策略,mmap仅分配虚拟地址,首次访问时触发缺页异常才分配物理页)
编译之后运行上面的程序,可以看到在物理内存在分配第 4 GB
内存时 触发了 OOM killer
1 2 3 4 5 6 7 8 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./anon2mmap Allocated 0 GB Allocated 1 GB Allocated 2 GB Allocated 3 GB Allocated 4 GB Killed
通过内核日志我们可以验证这一点, anon2mmap(PID=13365)
尝试分配大量内存,导致系统内存耗尽。同时展示了,内存分配标志,OOM 评分等,以及触发 OOM killer 的函数调用栈
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] └─$dmesg --follow -T [Sat Jun 7 16:54:14 2025] anon2mmap invoked oom-killer: gfp_mask=0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_COMP|__GFP_ZERO), order=0, oom_score_adj=0 [Sat Jun 7 16:54:14 2025] CPU: 1 PID: 13365 Comm: anon2mmap Kdump: loaded Tainted: G OE ------- --- 5.14.0-427.20.1.el9_4.x86_64 [Sat Jun 7 16:54:14 2025] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 11/12/2020 [Sat Jun 7 16:54:14 2025] Call Trace: [Sat Jun 7 16:54:14 2025] <TASK> [Sat Jun 7 16:54:14 2025] dump_stack_lvl+0x34/0x48 [Sat Jun 7 16:54:14 2025] dump_header+0x4a/0x201 [Sat Jun 7 16:54:14 2025] oom_kill_process.cold+0xb/0x10 [Sat Jun 7 16:54:14 2025] out_of_memory+0xed/0x2e0 [Sat Jun 7 16:54:14 2025] __alloc_pages_slowpath.constprop.0+0x6e8/0x960 [Sat Jun 7 16:54:14 2025] __alloc_pages+0x21d/0x250 [Sat Jun 7 16:54:14 2025] __folio_alloc+0x17/0x50 [Sat Jun 7 16:54:14 2025] ? policy_node+0x4f/0x70 [Sat Jun 7 16:54:14 2025] vma_alloc_folio+0xa3/0x390 [Sat Jun 7 16:54:14 2025] do_anonymous_page+0x63/0x520 [Sat Jun 7 16:54:14 2025] __handle_mm_fault+0x32b/0x670 [Sat Jun 7 16:54:14 2025] ? nohz_balancer_kick+0x31/0x250 [Sat Jun 7 16:54:14 2025] handle_mm_fault+0xcd/0x290 [Sat Jun 7 16:54:14 2025] do_user_addr_fault+0x1b4/0x6a0 [Sat Jun 7 16:54:14 2025] ? sched_clock_cpu+0x9/0xc0 [Sat Jun 7 16:54:14 2025] exc_page_fault+0x62/0x150 [Sat Jun 7 16:54:14 2025] asm_exc_page_fault+0x22/0x30 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 [Sat Jun 7 16:54:14 2025] Tasks state (memory values in pages): [Sat Jun 7 16:54:14 2025] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Sat Jun 7 16:54:14 2025] [ 701] 0 701 12852 192 114688 288 -250 systemd-journal [Sat Jun 7 16:54:14 2025] [ 715] 0 715 8274 32 98304 512 -1000 systemd-udevd [Sat Jun 7 16:54:14 2025] [ 887] 0 887 4539 80 57344 672 -1000 auditd 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 [Sat Jun 7 16:54:14 2025] [ 13058] 48 13058 374632 38 483328 16256 0 /usr/sbin/httpd [Sat Jun 7 16:54:14 2025] [ 13364] 0 13364 63300 2813 495616 12512 0 mmapsnoop [Sat Jun 7 16:54:14 2025] [ 13365] 0 13365 2097810 808956 9932800 424160 0 anon2mmap [Sat Jun 7 16:54:14 2025] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user.slice/user-0.slice/session-9.scope,task=anon2mmap,pid=13365,uid=0 [Sat Jun 7 16:54:14 2025] Out of memory: Killed process 13365 (anon2mmap) total-vm:8391240kB, anon-rss:3235696kB, file-rss:128kB, shmem-rss:0kB, UID:0 pgtables:9700kB oom_score_adj:0
虚拟内存总量 total-vm: 8391240kB(约 8GB)
,对应代码中的 8GB
内存申请
1 2 3 long long size = 8 * GB; void *ptr = mmap(NULL , size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );
匿名物理内存 anon-rss: 3235696kB(约 3.2GB)
,对应触发OOM killer 的内存消耗
这里我们用 mmapsnoop 去跟踪 mmap 的匿名内存的分配
1 2 3 4 5 6 7 8 9 10 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop -T TIME PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 00:42:09 13365 b'anon2mmap' R-- -P-- 0 28 b'ld.so.cache' 00:42:09 13365 b'anon2mmap' R-- -P-- 0 2083 b'libc.so.6' 00:42:09 13365 b'anon2mmap' R-E -PF- 160 1492 b'libc.so.6' 00:42:09 13365 b'anon2mmap' R-- -PF- 1652 352 b'libc.so.6' 00:42:09 13365 b'anon2mmap' RW- -PF- 2004 24 b'libc.so.6' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
这里脚本有一些问题,可以看到只能跟踪到一些共享库的分配,却看不到实际的匿名内存的分配,这是什么原因?
mmap 映射的匿名内存直接使用的虚拟内存,没有映射文件,所以之前的脚本没有记录这部分, 可以看到代码在 fd2file
中没有关联的数据时,直接返回 0,即匿名映射的内存会直接忽略
1 2 3 4 5 6 7 8 9 TRACEPOINT_PROBE(syscalls, sys_enter_mmap) { ...................... fpp = fd2file.lookup(&key); if (fpp == 0 ) return 0 ; file = *fpp; ..................... return 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 TRACEPOINT_PROBE(syscalls, sys_enter_mmap) { u32 pid = bpf_get_current_pid_tgid() >> 32 ; struct fdkey_t key = {.fd = args->fd, .pid = pid}; const char *anon_name = "[anon]" ; bpf_trace_printk("DEBUG: mmap fd=%d, pid=%d ---" , args->fd, pid); long fd = (long )args->fd; if ( args->fd == 0xFFFFFFFF ) { struct mmap_data_t data = { .len = args->len, .prot = args->prot, .flags = args->flags, .off = args->off, .pid = pid }; bpf_trace_printk("DEBUG: mmap len=%d, prot=%d ---" , args->len, args->prot); bpf_get_current_comm(&data.comm, sizeof (data.comm)); bpf_probe_read(&data.path, sizeof (data.path), (void *)anon_name); mmap_events.perf_submit(args, &data, sizeof (data)); return 0 ; } struct file **fpp = fd2file.lookup(&key); if (fpp == 0 ) return 0 ; struct file *file = *fpp; struct mmap_data_t data = { .len = args->len, .prot = args->prot, .flags = args->flags, .off = args->off, .pid = pid }; bpf_get_current_comm(&data.comm, sizeof (data.comm)); struct dentry *de = file->f_path.dentry; struct qstr d_name = {}; bpf_probe_read(&d_name, sizeof (d_name), (void *)&de->d_name); bpf_probe_read(&data.path, sizeof (data.path), d_name.name); mmap_events.perf_submit(args, &data, sizeof (data)); return 0 ; }
分配匿名内存的时候 args->fd
的值为 -1
, 需要注意 这里的 if ( args->fd == 0xFFFFFFFF )
,需要用 32 位的 -1
表示
bpf_trace_printk
用于调试,输出会写入 /sys/kernel/debug/tracing/trace_pipe
下面是修改之后的输出
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./ano_mmapsoonp PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 49103 ano_mmapsoonp RW- S--- 0 260 [perf_event] 49104 anon2mmap RW- -P-A 0 8 49104 anon2mmap R-- -P-- 0 28 ld.so.cache 49104 anon2mmap R-- -P-- 0 2083 libc.so.6 49104 anon2mmap R-E -PF- 160 1492 libc.so.6 49104 anon2mmap R-- -PF- 1652 352 libc.so.6 49104 anon2mmap RW- -PF- 2004 24 libc.so.6 49104 anon2mmap RW- -PFA 0 51 49104 anon2mmap RW- -P-A 0 12 49104 anon2mmap RW- -P-A 0 8388608
可以看到最后一条日志,对应分配虚拟内存 8388608KB/1024/1024 = 8G
1 `49104 anon2mmap RW- -P-A 0 8388608`
下面为调试日志的输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$ cat /sys/kernel/debug/tracing/trace_pipe ano_mmapsoonp-49103 [000] ....2.1 591493.415773: bpf_trace_printk: DEBUG: mmap fd=-1, pid=49103 --- ano_mmapsoonp-49103 [000] ....2.1 591493.415802: bpf_trace_printk: DEBUG: mmap len=4096, prot=7 --- ano_mmapsoonp-49103 [000] ....2.1 591493.415963: bpf_trace_printk: DEBUG: mmap fd=12, pid=49103 --- ano_mmapsoonp-49103 [000] ....2.1 591493.416216: bpf_trace_printk: DEBUG: mmap fd=13, pid=49103 --- anon2mmap-49104 [001] ....2.1 591496.676898: bpf_trace_printk: DEBUG: mmap fd=-1, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.676906: bpf_trace_printk: DEBUG: mmap len=8192, prot=3 --- anon2mmap-49104 [001] ....2.1 591496.677042: bpf_trace_printk: DEBUG: mmap fd=3, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.677532: bpf_trace_printk: DEBUG: mmap fd=3, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.677677: bpf_trace_printk: DEBUG: mmap fd=3, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.677715: bpf_trace_printk: DEBUG: mmap fd=3, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.678286: bpf_trace_printk: DEBUG: mmap fd=3, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.678361: bpf_trace_printk: DEBUG: mmap fd=-1, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.678368: bpf_trace_printk: DEBUG: mmap len=53168, prot=3 --- anon2mmap-49104 [001] ....2.1 591496.679738: bpf_trace_printk: DEBUG: mmap fd=-1, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.679747: bpf_trace_printk: DEBUG: mmap len=12288, prot=3 --- anon2mmap-49104 [001] ....2.1 591496.680823: bpf_trace_printk: DEBUG: mmap fd=-1, pid=49104 --- anon2mmap-49104 [001] ....2.1 591496.680849: bpf_trace_printk: DEBUG: mmap len=0, prot=3 --- systemd-journal-701 [000] ....2.1 591502.486094: bpf_trace_printk: DEBUG: mmap fd=22, pid=701 --- in :imjournal-1173 [001] ....2.1 591502.744724: bpf_trace_printk: DEBUG: mmap fd=8, pid=1144 --- ^C ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
mmap共享内存 多个进程可以通过 MAP_SHARED
标志共享同一个映射区域,实现进程间的通信。下面是一个对应的Demo
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> #define SHM_NAME "/my_shared_memory" #define SHM_SIZE 4096 int main () { int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666 ); if (fd == -1 ) { perror("shm_open" ); exit (EXIT_FAILURE); } if (ftruncate(fd, SHM_SIZE) == -1 ) { perror("ftruncate" ); close(fd); shm_unlink(SHM_NAME); exit (EXIT_FAILURE); } char *shared_memory = mmap(NULL , SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (shared_memory == MAP_FAILED) { perror("mmap" ); close(fd); shm_unlink(SHM_NAME); exit (EXIT_FAILURE); } close(fd); pid_t pid = fork(); if (pid < 0 ) { perror("fork" ); munmap(shared_memory, SHM_SIZE); shm_unlink(SHM_NAME); exit (EXIT_FAILURE); } if (pid == 0 ) { sprintf (shared_memory, "Hello from child process! PID=%d" , getpid()); munmap(shared_memory, SHM_SIZE); exit (EXIT_SUCCESS); } else { wait(NULL ); printf ("Parent read: %s\n" , shared_memory); munmap(shared_memory, SHM_SIZE); shm_unlink(SHM_NAME); } return 0 ; }
执行程序
1 2 3 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./shar2mmap Parent read : Hello from child process! PID=13375
下面为通过 mmapsnoop 来监控的日志,这里不涉及匿名内存跟踪,fd 的值不为 -1,所以可以直接只用原脚本跟踪
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop -T TIME PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 00:46:36 13374 b'shar2mmap' R-- -P-- 0 28 b'ld.so.cache' 00:46:36 13374 b'shar2mmap' R-- -P-- 0 2083 b'libc.so.6' 00:46:36 13374 b'shar2mmap' R-E -PF- 160 1492 b'libc.so.6' 00:46:36 13374 b'shar2mmap' R-- -PF- 1652 352 b'libc.so.6' 00:46:36 13374 b'shar2mmap' RW- -PF- 2004 24 b'libc.so.6' 00:46:36 13374 b'shar2mmap' RW- S--- 0 4 b'my_shared_memory' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
可以看到最后一行,即为跟踪的共享内存分配,S 标志为共享内存,my_shared_memory对应共享内存名字, 4 为分配的 4KB
1 00:46:36 13374 b'shar2mmap' RW- S--- 0 4 b'my_shared_memory'
文件映射 将文件的内容映射到进程的内存中,可以直接通过内存操作来访问文件,避免了频繁的read
和write
系统调用,提高了文件访问的效率。同时有些进程间交互也使用这种方式
下面是一个 mmap 文件映射的内存的 demo,创建文件、mmap 映射文件到内存、在内存中读写和修改文件内容、同步修改到文件、解除映射以及关闭文件的完整流程
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #define FILE_NAME "testfile.txt" #define FILE_SIZE 1024 int main () { int fd; char *map ; struct stat sb ; fd = open(FILE_NAME, O_RDWR | O_CREAT, 0644 ); if (fd == -1 ) { perror("open" ); return EXIT_FAILURE; } if (ftruncate(fd, FILE_SIZE) == -1 ) { perror("ftruncate" ); close(fd); return EXIT_FAILURE; } if (fstat(fd, &sb) == -1 ) { perror("fstat" ); close(fd); return EXIT_FAILURE; } map = mmap(NULL , sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (map == MAP_FAILED) { perror("mmap" ); close(fd); return EXIT_FAILURE; } for (int i = 0 ; i < FILE_SIZE; i++) { map [i] = 'A' + (i % 26 ); } printf ("Original file content:\n" ); for (int i = 0 ; i < FILE_SIZE; i++) { putchar (map [i]); } printf ("\n" ); for (int i = 0 ; i < FILE_SIZE; i++) { map [i] = 'a' + (i % 26 ); } if (msync(map , sb.st_size, MS_SYNC) == -1 ) { perror("msync" ); } if (munmap(map , sb.st_size) == -1 ) { perror("munmap" ); } close(fd); return EXIT_SUCCESS; }
编译之后运行上面的Demo
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$vim file2mmap.c ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$gcc -g file2mmap.c -o file2mmap ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./file2mmap 当前进程PID: 10581 Original file content: ABCDE。。。。。。。。。。。。。。。。。。。。。。FGHIJ ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
1 2 3 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat testfile.txt abcdefghi。。。。。。。。。。。。。。。。。。。。。efghij
运行 file2mmap
,通过 mmapsnoop 命令监控 mmap 的调用
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop -T TIME PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 00:18:18 10580 b'mmapsnoop' RW- S--- 0 260 b'[perf_event]' 00:18:20 10581 b'file2mmap' R-- -P-- 0 28 b'ld.so.cache' 00:18:20 10581 b'file2mmap' R-- -P-- 0 2083 b'libc.so.6' 00:18:20 10581 b'file2mmap' R-E -PF- 160 1492 b'libc.so.6' 00:18:20 10581 b'file2mmap' R-- -PF- 1652 352 b'libc.so.6' 00:18:20 10581 b'file2mmap' RW- -PF- 2004 24 b'libc.so.6' 00:18:20 10581 b'file2mmap' RW- S--- 0 1 b'testfile.txt' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
最后一行为跟踪的文件映射内存分配,可以看到大下为我们上面定义的 1kb
1 00:18:20 10581 b'file2mmap' RW- S--- 0 1 b'testfile.txt'
私有写时复制映射 使用 MAP_PRIVATE
标志创建的映射属于私有写时复制映射。当进程对映射区域进行写操作时,内核会为该进程复制一份物理内存页,这样进程的修改不会影响到其他映射了同一文件或区域的进程。这种映射方式适合于多个进程需要读取相同数据,但可能会各自进行修改的场景。下面是一个Demo
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 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #define MAP_SIZE 4096 int main () { int fd; char *map ; fd = open("tempfile" , O_RDWR | O_CREAT | O_TRUNC, 0644 ); if (fd == -1 ) { perror("open" ); return EXIT_FAILURE; } if (ftruncate(fd, MAP_SIZE) == -1 ) { perror("ftruncate" ); close(fd); return EXIT_FAILURE; } map = mmap(NULL , MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0 ); if (map == MAP_FAILED) { perror("mmap" ); close(fd); return EXIT_FAILURE; } for (int i = 0 ; i < MAP_SIZE; i++) { map [i] = 'A' ; } printf ("Original value at map[0]: %c\n" , map [0 ]); map [0 ] = 'B' ; printf ("Modified value at map[0]: %c\n" , map [0 ]); if (munmap(map , MAP_SIZE) == -1 ) { perror("munmap" ); } close(fd); return EXIT_SUCCESS; }
1 2 3 4 5 6 7 8 9 10 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$vim cow2mmap.c ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$gcc -g cow2mmap.c -o cow2mmap ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./cow2mmap Original value at map[0]: A Modified value at map[0]: B ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop -T TIME PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 01:45:48 13519 b'cow2mmap' R-- -P-- 0 28 b'ld.so.cache' 01:45:48 13519 b'cow2mmap' R-- -P-- 0 2083 b'libc.so.6' 01:45:48 13519 b'cow2mmap' R-E -PF- 160 1492 b'libc.so.6' 01:45:48 13519 b'cow2mmap' R-- -PF- 1652 352 b'libc.so.6' 01:45:48 13519 b'cow2mmap' RW- -PF- 2004 24 b'libc.so.6' 01:45:48 13519 b'cow2mmap' RW- -P-- 0 4 b'tempfile' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
设备映射 设备映射允许进程直接访问硬件设备的内存空间。通过将设备的物理地址映射到进程的虚拟地址空间,进程可以像访问普通内存一样访问设备的寄存器和数据缓冲区,从而实现对设备的直接控制和数据交互。例如,显卡、网络接口卡等设备可以通过设备映射来提高数据传输效率。
其他语言 前面的 Demo都是 C 语言的跟踪,我们来看看其他语言的
Java 下面是一个 Java 的内存文件映射的Demo
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 37 38 39 40 41 42 43 44 ┌──[root@liruilongs .github.io]-[~/bpfdemo] └─$vim javaMap.java ┌──[root@liruilongs .github.io]-[~/bpfdemo] └─$java javaMap.java Hello from Java mmap! ┌──[root@liruilongs .github.io]-[~/bpfdemo] └─$java javaMap.java Hello from Java mmap! ┌──[root@liruilongs .github.io]-[~/bpfdemo] └─$cat javaMap.java import java.io.*;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class JavaMmap { public static void main (String[] args) { try { File file = new File("tempfile" ); RandomAccessFile raf = new RandomAccessFile(file, "rw" ); FileChannel channel = raf.getChannel(); MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0 , 1024 ); String message = "Hello from Java mmap!" ; buffer.put(message.getBytes()); buffer.position(0 ); byte [] data = new byte [message.length()]; buffer.get(data); System.out.println(new String(data)); channel.close(); raf.close(); } catch (IOException e) { e.printStackTrace(); } } } ┌──[root@liruilongs .github.io]-[~/bpfdemo] └─$
通过 mmapsnoop
,我们可以在下面的日志的最后一行看的跟踪数据,我们申请了 1KB
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]-[/usr/share/bcc/tools] └─$./mmapsnoop PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 41156 b'mmapsnoop' RW- S--- 0 260 b'[perf_event]' 41157 b'java' R-- -P-- 0 108 b'libjli.so' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 41157 b'java' RW- -PF- 100 8 b'libjli.so' 41157 b'java' R-- -P-- 0 28 b'ld.so.cache' 41157 b'java' R-- -P-- 0 2083 b'libc.so.6' 41157 b'java' R-E -PF- 160 1492 b'libc.so.6' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 41157 b'java' R-- -PF- 16440 2584 b'libjvm.so' 41157 b'java' RW- -PF- 19024 952 b'libjvm.so' 41157 b'java' R-- -P-- 0 28 b'ld.so.cache' 41157 b'java' R-- -P-- 0 872 b'libm.so.6' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 41157 b'java' R-E -PF- 16 80 b'libjimage.so' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。.。。。。。。。。。。。。 41157 b'java' RW- -PF- 152 8 b'libjava.so' 41157 b'java' RW- -PF- 4 4432 b'classes.jsa' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 41157 b'java' R-- -PF- 280 548 b'libjsvml.so' 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 41157 b'java' R-- -P-- 0 264 b'libnspr4.so' 41157 b'java' R-E -PF- 48 148 b'libnspr4.so' 41157 b'java' R-- -PF- 196 44 b'libnspr4.so' 41157 b'java' RW- -PF- 240 12 b'libnspr4.so' 41157 b'java' RW- S--- 0 1 b'tempfile' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
python 下面是一个 python 的 Demo,有一个问题,跟踪的内存映射长度为 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 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$python py_mmap.py b'Hi,mmapmmap!' ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat py_mmap.py import mmap with open("tempfile" , "w+b" ) as f: f.write(b"Hello, mmap!" ) f.flush() mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE) mm[0:7] = b"Hi,mmap" print (mm.readline()) mm.close() ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
我觉得可能是文件写入的字符串小于 1kb,所以展示为 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 41205 b'python' R-- -P-- 0 28 b'ld.so.cache' 41205 b'python' R-- -P-- 0 3467 b'libpython3.9.so.1.0' 41205 b'python' R-E -PF- 360 1748 b'libpython3.9.so.1.0' ............................................................... 41205 b'python' RW- -PF- 2004 24 b'libc.so.6' 41205 b'python' R-- -P-- 0 872 b'libm.so.6' 41205 b'python' R-E -PF- 52 448 b'libm.so.6' .................................... 41205 b'python' RW- -PF- 24 8 b'mmap.cpython-39-x86_64-linux-gnu' 41205 b'python' RW- S--- 0 0 b'tempfile' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
在前面的博文和小伙伴分享过 mmap
可以使用 大页映射,提高内存访问性能,默认情况下 ,mmap 是使用标准内存页 4kb 来映射内存的,这里我们也看几个 Demo
大页相关 大页映射既可以用于文件映射,也可以用于匿名映射,具体取决于使用的场景和参数:
在这之前我们需要做一些准备工作
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 ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$grep -i huge /proc/meminfo | grep -i size Hugepagesize: 2048 kB ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$sysctl -a | grep huge vm.hugetlb_optimize_vmemmap = 0 vm.hugetlb_shm_group = 0 vm.nr_hugepages = 0 vm.nr_hugepages_mempolicy = 0 vm.nr_overcommit_hugepages = 0 ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$free -h total used free shared buff/cache available Mem: 15Gi 12Gi 2.8Gi 3.0Mi 419Mi 3.0Gi Swap: 2.0Gi 337Mi 1.7Gi ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$sysctl -w vm.nr_hugepages=50 vm.nr_hugepages = 50 ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$free -h total used free shared buff/cache available Mem: 15Gi 12Gi 2.7Gi 3.0Mi 419Mi 2.9Gi Swap: 2.0Gi 337Mi 1.7Gi ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$sysctl -a | grep huge vm.hugetlb_optimize_vmemmap = 0 vm.hugetlb_shm_group = 0 vm.nr_hugepages = 50 vm.nr_hugepages_mempolicy = 50 vm.nr_overcommit_hugepages = 0 ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$mkdir -p /dev/hugepages ┌──[root@liruilongs.github.io]-[~/bpfdemo/mmapdev] └─$mount -t hugetlbfs -o pagesize=2M none /dev/hugepages
上面的命令主要完成了大页内存(Huge Pages)
的配置与挂载,具体操作如下:
首先通过grep
命令确认系统默认大页大小为2048KB(2MB);接着用sysctl
查看大页相关参数,此时vm.nr_hugepages
为0,意味着未分配大页内存;随后执行sysctl -w vm.nr_hugepages=50
,为系统分配50个大页,每个大小2MB,共100MB内存,此时free -h
显示可用内存减少约100MB;
接着通过sysctl -a
验证大页分配成功,nr_hugepages
和nr_hugepages_mempolicy
均变为50;最后创建/dev/hugepages
目录,并使用mount
命令将大页文件系统(hugetlbfs)挂载到该目录,指定页大小为2MB,以便后续通过该目录使用大页内存。
文件大页映射 下面是一个 文件内存映射使用大页的 Demo,可以看到分配2个大页,一个大页 2MB,两个 4MB
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$cat hug2mmap.c #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define MAP_SIZE (4 * 1024 * 1024) int main () { int fd; char *map ; char path[] = "/dev/hugepages/test_huge" ; fd = open(path, O_CREAT | O_RDWR, 0666 ); if (fd == -1 ) { perror("open" ); return EXIT_FAILURE; } if (ftruncate(fd, MAP_SIZE) == -1 ) { perror("ftruncate" ); close(fd); return EXIT_FAILURE; } map = mmap(NULL , MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (map == MAP_FAILED) { perror("mmap" ); close(fd); unlink(path); return EXIT_FAILURE; } memset (map , 'A' , MAP_SIZE); munmap(map , MAP_SIZE); close(fd); unlink(path); return 0 ; }
执行 Demo后观察 HugePages
相关的指标数据,确认是否使用成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$vim hug2mmap.c ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$gcc -g hug2mmap.c -o hug2mmap ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./hug2mmap ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./hug2mmap hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime,pagesize=2M) Huge pages usage: AnonHugePages: 20480 kB ShmemHugePages: 0 kB FileHugePages: 0 kB HugePages_Total: 50 HugePages_Free: 48 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 102400 kB 50
分配了 50 个,使用 2个,空闲大页数为 48 个
1 2 HugePages_Total: 50 HugePages_Free: 48
在 mmapsnoop 的最后一行输出中显示大小为 4096 KB
,这与代码中设置的映射 两个大页一致。
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$./mmapsnoop -T TIME PID COMM PROT MAP OFFS(KB) SIZE(KB) FILE 09:33:46 15674 b'mmapsnoop' RW- S--- 0 260 b'[perf_event]' 09:33:46 15675 b'hug2mmap' R-- -P-- 0 28 b'ld.so.cache' 09:33:46 15675 b'hug2mmap' R-- -P-- 0 2083 b'libc.so.6' 09:33:46 15675 b'hug2mmap' R-E -PF- 160 1492 b'libc.so.6' 09:33:46 15675 b'hug2mmap' R-- -PF- 1652 352 b'libc.so.6' 09:33:46 15675 b'hug2mmap' RW- -PF- 2004 24 b'libc.so.6' 09:33:46 15675 b'hug2mmap' RW- S--- 0 4096 b'test_huge' ^C┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] └─$
mmapfiles(8) mmapfles(8)
是一个 bpftrace
工具,同样用于跟踪 mmap(2)
调用,主要统计映射入内存地址范围的文件频率信息
, 可以通过这个命令直观的看到频繁分配的进程数据
对应的代码地址:
https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch08_FileSystems/mmapfiles.bt
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]-[/usr/share/bpftrace/tools] └─$cat mmapfiles.bt #!/usr/bin/bpftrace #include <linux/mm.h> kprobe:do_mmap { $file = (struct file *)arg0; $name = $file->f_path.dentry; $dir1 = $name->d_parent; $dir2 = $dir1->d_parent; @[str($dir2->d_name.name), str($dir1->d_name.name), str($name->d_name.name)] = count(); } ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$
这里我们执行一些前面写的 Demo,看下输出
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]-[/usr/share/bpftrace/tools] └─$./mmapfiles.bt Attaching 1 probe... ^C @[locale, C.utf8, LC_PAPER]: 1 @[/, /, my_shared_memory]: 1 。。。。。。。。。。。 @[root, bpfdemo, testfile.txt]: 1 。。。。。。。。。。。。。。。。。。。。 @[locale, C.utf8, LC_ADDRESS]: 1 @[anon_hugepage, anon_hugepage, anon_hugepage]: 2 @[root, bpfdemo, file2mmap]: 4 @[root, bpfdemo, anonhag2mmap]: 4 @[usr, lib64, libpcre2-8.so.0.11.0]: 4 @[root, bpfdemo, shar2mmap]: 4 @[usr, lib64, libselinux.so.1]: 4 @[usr, lib64, libcap.so.2.48]: 4 @[usr, bin, ls]: 4 @[root, bpfdemo, hug2mmap]: 4 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$
前两个列为 当前程序的两个父目录信息,root, bpfdemo
,我们的实验在 root
目录的 bpfdemo
目录下完成,第三列为映射的进程名字,最后一列为映射次数
fmapfaults(8) fmapfault(8)
跟踪内存映射文件的缺页错误
,按进程名和文件名来统计,内存映射了之后只有写入数据才会发生缺页错误,所以如果我们想知道那些进程在分配虚拟内存之后进行了读写操作,那么可以通过 fmapfaults
跟踪。
对应的代码地址:
https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch08_FileSystems/fmapfault.bt
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 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$vim fmapfault.bt ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$chmod +x fmapfault.bt /* * fmapfault - Count file map faults. * * See BPF Performance Tools, Chapter 8, 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. */ kprobe:filemap_fault { $vf = (struct vm_fault *)arg0; $file = $vf ->vma->vm_file->f_path.dentry->d_name.name; @[comm, str($file )] = count(); }
这里任然使用之前的 Demo 进行测试
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./shar2mmap Parent read : Hello from child process! PID=15816 ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./hug2mmap ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$./file2mmap 当前进程PID: 15818 Original file content: ABCD.............................IJ ┌──[root@liruilongs.github.io]-[~/bpfdemo] └─$
需要注意,文件实际的读写操作频率可能高于缺页错误发生的频率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$./fmapfault.bt Attaching 1 probe... ^C @[file2mmap, libc.so.6]: 1 @[shar2mmap, shar2mmap]: 1 @[file2mmap, file2mmap]: 1 @[shar2mmap, libc.so.6]: 1 @[file2mmap, testfile.txt]: 1 @[hug2mmap, hug2mmap]: 1 @[hug2mmap, libc.so.6]: 1 @[hug2mmap, ld-linux-x86-64.so.2]: 4 @[file2mmap, ld-linux-x86-64.so.2]: 4 @[shar2mmap, ld-linux-x86-64.so.2]: 4 ┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools] └─$
博文部分内容参考 © 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
《BPF Performance Tools》
© 2018-至今 liruilonger@gmail.com , 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)