openEuler 内存调优之 BPF 分析用户态 mmap 大内存分配【华为根技术】

举报
山河已无恙 发表于 2025/06/28 17:08:09 2025/06/28
【摘要】 写在前面博文内容涉及利用 BPF 分析用户态 mmap 大内存分配的一些工具分享以及 Demo主要为 bcc 工具 mmapsnoop以及其变体和 bpftrace 工具 mmapfiles 和 fmapfaults对 mmap 私有匿名映射,共享映射,文件映射,大页映射等类型内存映射分配的跟踪Demo理解不足小伙伴帮忙指正 :),生活加油 我看远山,远山悲悯持续分享技术干货,感兴趣小伙伴...

写在前面


  • 博文内容涉及利用 BPF 分析用户态 mmap 大内存分配的一些工具分享以及 Demo
  • 主要为 bcc 工具 mmapsnoop以及其变体和 bpftrace 工具 mmapfilesfmapfaults
  • mmap 私有匿名映射,共享映射,文件映射,大页映射等类型内存映射分配的跟踪Demo
  • 理解不足小伙伴帮忙指正 :),生活加油

我看远山,远山悲悯

持续分享技术干货,感兴趣小伙伴可以关注下 ^_^


为什么监控 Linux 用户态大内存分配?

Linux 的内存分配机制整体可分为两大核心部分,brk 和 mmap

brk​ 部分是用于存储用户态程序数据的小内存分配,主要依赖brk调用实现。brk 系统调用通过移动堆顶指针来扩展或收缩堆空间,从而实现小块内存的分配与释放 。它适用于分配内存量较小、频繁申请和释放的场景

例如程序运行过程中临时存储少量数据的变量。在这种机制下,内存管理相对简单直接,但随着程序的复杂和内存需求的增加,可能会出现内存碎片等问题。​

mmap 部分则是基于mmap的大内存分配。当程序需要分配较大块的内存时,mmap 便发挥作用。它通过将文件、设备或匿名内存区域映射到进程的虚拟地址空间,为程序提供连续的、较大规模的内存区域。

brk不同,mmap在内存管理上更为灵活,能有效避免内存碎片问题,且在内存共享、文件映射等场景下具有独特优势。​

通过监控mmap操作,开发者可以追踪大内存的分配与释放情况,及时发现异常的内存占用,从而定位内存泄露的源头,为优化程序性能和稳定性提供有力支持。所以对基于mmap的大内存分配进行监控,在内存泄露分析中有着至关重要的意义。

下面实验使用的Linux环境

[root@developer ~]# hostnamectl 
 Static hostname: developer
       Icon name: computer-vm
         Chassis: vm
      Machine ID: 7ad73f2b5f7046a2a389ca780f472467
         Boot ID: cef15819a5c34efa92443b6eff608cc9
  Virtualization: kvm
Operating System: openEuler 22.03 (LTS-SP4)
          Kernel: Linux 5.10.0-250.0.0.154.oe2203sp4.aarch64
    Architecture: arm64
 Hardware Vendor: OpenStack Foundation
  Hardware Model: OpenStack Nova
[root@developer ~]#

如何监控? 这里我们主要使用 mmapsnoopmmapfiles等工具基于 eBPF 技术,对大内存分配进行监控,先来了解一下 mmapsnoop

mmapsnoop

mmapsnoop(8)是一个 BCC 工具,在 《BPF Performance Tools》 一书中首次出现,主要用于跟踪全系统的mmap(2)系统调用并打印出mmap函数调用的详细信息, mmap 是一个非常重要的系统调用,主要用于在进程的虚拟地址空间中创建一个新的内存映射。

在C语言中,mmap的函数原型如下:

#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 探针 在文件描述符关闭时清理缓存
...................
// cache pid+FD -> file for later lookup
// TODO: use a task->files->fdt->fd[] lookup in the mmap tracepoint instead.
// fd_install 探针 缓存文件描述符(FD)与文件对象的映射关系(fd2file 哈希表)
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;
}

// assume this and other events are in PID context
// ​​__close_fd 探针  在文件描述符关闭时清理缓存
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 跟踪点

// ​​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

┌──[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
// assume this and other events are in PID context
//int kprobe____close_fd(struct pt_regs *ctx, struct files_struct *files, int fd)
int kprobe____close_fd_get_file(struct pt_regs *ctx, struct files_struct *files, int fd)

使用方式相对简单,直接通过命令行即可,示例输出

[root@liruilongs.github.io tools]# ./mmapsnoop 
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仅分配虚拟地址空间,首次访问时才分配物理页(通过缺页中断)

┌──[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;  // 尝试映射8GB
    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

┌──[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 的函数调用栈

┌──[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 #1
[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 内存申请

    long long size = 8 * GB;  // 尝试映射8GB
    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

匿名物理内存 anon-rss: 3235696kB(约 3.2GB),对应触发OOM killer 的内存消耗

这里我们用 mmapsnoop 去跟踪 mmap 的匿名内存的分配

┌──[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,即匿名映射的内存会直接忽略

TRACEPOINT_PROBE(syscalls, sys_enter_mmap) {
......................
    fpp = fd2file.lookup(&key);
    if (fpp == 0)
        return 0;
    file = *fpp;
  .....................
    return 0;
}

这里我们需要修改一下原来的脚本,下面为修改之后的

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]";
    // 调试:打印fd值
    bpf_trace_printk("DEBUG: mmap fd=%d, pid=%d  ---", args->fd, pid);

    // 处理匿名内存映射(fd为-1)
    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

下面是修改之后的输出

┌──[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

`49104  anon2mmap      RW-  -P-A  0        8388608` 

下面为调试日志的输出

^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

#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;
}

执行程序

┌──[root@liruilongs.github.io]-[~/bpfdemo]
└─$./shar2mmap
Parent read: Hello from child process! PID=13375

下面为通过 mmapsnoop 来监控的日志,这里不涉及匿名内存跟踪,fd 的值不为 -1,所以可以直接只用原脚本跟踪

┌──[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

00:46:36 13374  b'shar2mmap'   RW-  S---  0        4        b'my_shared_memory'

文件映射

将文件的内容映射到进程的内存中,可以直接通过内存操作来访问文件,避免了频繁的readwrite系统调用,提高了文件访问的效率。同时有些进程间交互也使用这种方式

下面是一个 mmap 文件映射的内存的 demo,创建文件、mmap 映射文件到内存、在内存中读写和修改文件内容、同步修改到文件、解除映射以及关闭文件的完整流程

#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

┌──[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]
└─$
┌──[root@liruilongs.github.io]-[~/bpfdemo]
└─$cat testfile.txt
abcdefghi。。。。。。。。。。。。。。。。。。。。。efghij

运行 file2mmap,通过 mmapsnoop 命令监控 mmap 的调用

┌──[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

00:18:20 10581  b'file2mmap'   RW-  S---  0        1        b'testfile.txt'

私有写时复制映射

使用 MAP_PRIVATE 标志创建的映射属于私有写时复制映射。当进程对映射区域进行写操作时,内核会为该进程复制一份物理内存页,这样进程的修改不会影响到其他映射了同一文件或区域的进程。这种映射方式适合于多个进程需要读取相同数据,但可能会各自进行修改的场景。下面是一个Demo

#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;
}
┌──[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]
└─$
┌──[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

┌──[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
// JavaMmap.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

┌──[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,这里有没有小伙伴知道是什么原因,欢迎留言讨论 ^_^

┌──[root@liruilongs.github.io]-[~/bpfdemo]
└─$python py_mmap.py
b'Hi,mmapmmap!'
┌──[root@liruilongs.github.io]-[~/bpfdemo]
└─$cat py_mmap.py
# python_mmap.py
import mmap

# 创建一个临时文件
with open("tempfile", "w+b") as f:
    f.write(b"Hello, mmap!")
    f.flush()

    # 创建 mmap 对象(映射整个文件)
    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

┌──[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

大页相关

大页映射既可以用于文件映射,也可以用于匿名映射,具体取决于使用的场景和参数:

在这之前我们需要做一些准备工作

┌──[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_hugepagesnr_hugepages_mempolicy均变为50;最后创建/dev/hugepages目录,并使用mount命令将大页文件系统(hugetlbfs)挂载到该目录,指定页大小为2MB,以便后续通过该目录使用大页内存。

文件大页映射

下面是一个 文件内存映射使用大页的 Demo,可以看到分配2个大页,一个大页 2MB,两个 4MB

┌──[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)  // 4MB,2个2MB大页

int main() {
    int fd;
    char *map;
    char path[] = "/dev/hugepages/test_huge";

    // 确保挂载了 hugetlbfs
    //system("mount | grep hugetlbfs || mount -t hugetlbfs -o pagesize=2M none /dev/hugepages");

    // 打开 hugetlbfs 文件
    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_HUGETLB 标志)
    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);

    // 验证大页使用
    //printf("Huge pages usage:\n");
    //system("grep -i huge /proc/meminfo");
    //system("cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages");

    // 清理
    munmap(map, MAP_SIZE);
    close(fd);
    unlink(path);

    return 0;
}

执行 Demo后观察 HugePages 相关的指标数据,确认是否使用成功

┌──[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 个

HugePages_Total:      50
HugePages_Free:       48

在 mmapsnoop 的最后一行输出中显示大小为 4096 KB,这与代码中设置的映射 两个大页一致。

┌──[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

┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools]
└─$cat mmapfiles.bt
#!/usr/bin/bpftrace
/*
 * mmapfiles - Count mmap(2) files.
 *
 * 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.
 */

#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,看下输出

┌──[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

┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools]
└─$vim fmapfault.bt
┌──[root@liruilongs.github.io]-[/usr/share/bpftrace/tools]
└─$chmod +x fmapfault.bt
#!/usr/local/bin/bpftrace
/*
 * 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.
 */

#include <linux/mm.h>

kprobe:filemap_fault
{
 $vf = (struct vm_fault *)arg0;
 $file = $vf->vma->vm_file->f_path.dentry->d_name.name;
 @[comm, str($file)] = count();
}

这里任然使用之前的 Demo 进行测试

┌──[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]
└─$

需要注意,文件实际的读写操作频率可能高于缺页错误发生的频率。

┌──[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-至今 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。