找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3721

积分

0

好友

519

主题
发表于 2026-2-12 09:42:12 | 查看: 32| 回复: 0

在 Android 系统开发或性能调试中,我们常常需要深入内核层面追踪数据,而 eBPF(尤其是其 Map 结构)是关键的观察窗口。这篇文章将通过一个具体的场景——监控进程的 GPU 内存使用量,来演示如何在 Android 设备上查看 BPF Map 中的数据。

理解数据来源:一个 BPF Map 的读取示例

首先,我们来看一段实际用于读取数据的 C++ 代码片段,它清晰地展示了用户空间程序如何与名为 gpu_mem_total_map 的 BPF Map 进行交互。这个 Map 用于存储各进程的 GPU 内存总量。

bool ReadProcessGpuUsageKb([[maybe_unused]] uint32_t pid, [[maybe_unused]] uint32_t gpu_id,
                           uint64_t* size) {
#if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
    static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpuMem_gpu_mem_total_map";

    uint64_t gpu_mem;

    // BPF Key [32-bits GPU ID | 32-bits PID]
    uint64_t kBpfKeyGpuUsage = ((uint64_t)gpu_id << 32) | pid;

    // Use the read-only wrapper BpfMapRO to properly retrieve the read-only map.
    auto map = bpf::BpfMapRO<uint64_t, uint64_t>(kBpfGpuMemTotalMap);
    if (!map.isValid()) {
        LOG(ERROR) << "Can't open file: " << kBpfGpuMemTotalMap;
        return false;
    }

    auto res = map.readValue(kBpfKeyGpuUsage);

    if (res.ok()) {
        gpu_mem = res.value();
    } else if (res.error().code() == ENOENT) {
        gpu_mem = 0;
    } else {
        LOG(ERROR) << "Invalid file format: " << kBpfGpuMemTotalMap;
        return false;
    }

    if (size) {
        *size = gpu_mem / 1024;
    }
    return true;
#else
    if (size) {
        *size = 0;
    }
    return false;
#endif
}

从代码中我们可以看到几个关键点:

  1. BPF Map 在文件系统中的路径是 /sys/fs/bpf/map_gpuMem_gpu_mem_total_map
  2. Map 的键(Key)是一个 64 位整数,其高 32 位是 GPU ID,低 32 位是进程 PID。本例中 GPU ID 为 0。
  3. 值(Value)也是一个 64 位整数,表示以字节为单位的 GPU 内存使用量。

了解数据是如何被存储和读取的之后,我们进入正题:如何绕过应用层,直接使用命令行工具来“窥探”这个 Map 里的原始数据。

使用 bpftool 直接查看 BPF Map

bpftool 是 Linux 内核社区维护的 Swiss Army Knife,用于管理和检查 eBPF 对象。在具备 root 权限的 Android 设备上,我们同样可以使用它。假设我们想查看 PID 为 27309 的进程的 GPU 内存使用情况。

首先,我们可以用系统命令 dumpsys gpu 来验证一下这个进程的 GPU 内存总量,作为后续对比的基准。

# dumpsys gpu |grep 27309
Proc 27309 total: 58912768

系统显示该进程的 GPU 内存总量为 58912768 字节。接下来,我们使用 bpftool 来检查对应的 BPF Map。

第一步:查看 Map 的基本信息
使用 bpftool map show 命令可以列出 Map 的类型、大小等元数据。

# bpftool map show pinned /sys/fs/bpf/map_gpuMem_gpu_mem_total_map
62: hash  name gpu_mem_total_m  flags 0x0
        key 8B  value 8B  max_entries 1024  memlock 16384B

输出显示这是一个哈希表类型的 Map,键和值都是 8 字节(64位),最多可存储 1024 个条目。这验证了代码中对键值类型的定义。

第二步:导出 Map 中的所有数据
使用 bpftool map dump 命令可以将 Map 中的所有键值对以十六进制形式打印出来。

# bpftool map dump pinned /sys/fs/bpf/map_gpuMem_gpu_mem_total_map
key: ad 6a 00 00 00 00 00 00  value: 00 f0 82 03 00 00 00 00
key: d6 08 00 00 00 00 00 00  value: 00 50 44 06 00 00 00 00
key: 00 00 00 00 00 00 00 00  value: 00 d0 d8 0c 00 00 00 00
key: b4 66 00 00 00 00 00 00  value: 00 80 01 00 00 00 00 00
key: b9 66 00 00 00 00 00 00  value: 00 50 a5 02 00 00 00 00
key: 18 69 00 00 00 00 00 00  value: 00 f0 09 00 00 00 00 00
Found 6 elements

输出显示了 6 个条目。我们需要从中找到 PID 为 27309(即 0x6AAD)的那一条。注意,由于系统通常使用小端字节序(Little Endian),我们在读取十六进制时需要从右往左看低有效位。

第三步:解析数据

  1. 第一个键(Key)的字节序列是 ad 6a 00 00 00 00 00 00。以小端格式解读,其低 32 位是 0x00006aad,换算成十进制正是 27309。高 32 位都是0,对应 GPU ID 0。
  2. 第一个值(Value)的字节序列是 00 f0 82 03 00 00 00 00。同样以小端格式解读,其数值为 0x000000000382f000,换算成十进制是 58912768

这个结果与之前 dumpsys gpu 命令的输出完全吻合!至此,我们成功通过直接读取 BPF Map 验证了用户空间获取的数据。

总结与提醒

通过这个具体的例子,我们演示了在 Android 环境下使用 bpftool 工具查看 BPF Map 数据的完整流程:从定位 Map 文件、查看其信息,到导出并解析原始的键值对。这种方法对于调试 eBPF 程序、验证数据准确性,或是进行深度系统排查都非常有用。

记住一个关键点:在解析 bpftool 输出的十六进制数据时,一定要注意字节序问题。大多数现代系统,包括 Android 设备所使用的 ARM 架构,都采用小端字节序,这意味着数值的低位字节存储在内存的低地址处,在打印时也靠前显示。所以我们需要“反着读”才能得到正确的数值。

希望这篇实战指南能帮助你更好地理解和驾驭 Android 系统中的 eBPF 技术。如果你想了解更多关于系统底层或移动开发的技术细节,欢迎来 云栈社区 交流探讨。




上一篇:一致性哈希详解:对比取模法,解决分布式缓存与数据库扩缩容难题
下一篇:基于LMAX Disruptor与Aeron:加密货币高频交易系统CryptoHFT架构设计与性能分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 13:03 , Processed in 0.376383 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表