我看远山,远山悲悯 
写在前面 
博文内容涉及利用 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  =     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  =     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  =     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  =     if  (fpp == 0 )         return  0 ;     struct  file  *file  =     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  =     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)