mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
513 lines
33 KiB
HTML
513 lines
33 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="light" dir="ltr">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>lesson 12-profile - bpf-developer-tutorial</title>
|
||
|
||
|
||
<!-- Custom HTML head -->
|
||
|
||
<meta name="description" content="">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff">
|
||
|
||
<link rel="icon" href="../favicon.svg">
|
||
<link rel="shortcut icon" href="../favicon.png">
|
||
<link rel="stylesheet" href="../css/variables.css">
|
||
<link rel="stylesheet" href="../css/general.css">
|
||
<link rel="stylesheet" href="../css/chrome.css">
|
||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" href="../highlight.css">
|
||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
</head>
|
||
<body class="sidebar-visible no-js">
|
||
<div id="body-container">
|
||
<!-- Provide site root to javascript -->
|
||
<script>
|
||
var path_to_root = "../";
|
||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
</script>
|
||
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script>
|
||
try {
|
||
var theme = localStorage.getItem('mdbook-theme');
|
||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script>
|
||
var theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
var html = document.querySelector('html');
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
var body = document.querySelector('body');
|
||
body.classList.remove('no-js')
|
||
body.classList.add('js');
|
||
</script>
|
||
|
||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var body = document.querySelector('body');
|
||
var sidebar = null;
|
||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
sidebar_toggle.checked = sidebar === 'visible';
|
||
body.classList.remove('sidebar-visible');
|
||
body.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<div class="sidebar-scrollbox">
|
||
<ol class="chapter"><li class="chapter-item expanded affix "><a href="../https://github.com/eunomia-bpf/bpf-developer-tutorial.html">https://github.com/eunomia-bpf/bpf-developer-tutorial</a></li><li class="chapter-item expanded affix "><li class="part-title">入门文档</li><li class="chapter-item expanded "><a href="../0-introduce/index.html"><strong aria-hidden="true">1.</strong> lesson 0-introduce</a></li><li class="chapter-item expanded "><a href="../1-helloworld/index.html"><strong aria-hidden="true">2.</strong> lesson 1-helloworld</a></li><li class="chapter-item expanded "><a href="../2-kprobe-unlink/index.html"><strong aria-hidden="true">3.</strong> lesson 2-kprobe-unlink</a></li><li class="chapter-item expanded "><a href="../3-fentry-unlink/index.html"><strong aria-hidden="true">4.</strong> lesson 3-fentry-unlink</a></li><li class="chapter-item expanded "><a href="../4-opensnoop/index.html"><strong aria-hidden="true">5.</strong> lesson 4-opensnoop</a></li><li class="chapter-item expanded "><a href="../5-uprobe-bashreadline/index.html"><strong aria-hidden="true">6.</strong> lesson 5-uprobe-bashreadline</a></li><li class="chapter-item expanded "><a href="../6-sigsnoop/index.html"><strong aria-hidden="true">7.</strong> lesson 6-sigsnoop</a></li><li class="chapter-item expanded "><a href="../7-execsnoop/index.html"><strong aria-hidden="true">8.</strong> lesson 7-execsnoop</a></li><li class="chapter-item expanded "><a href="../8-exitsnoop/index.html"><strong aria-hidden="true">9.</strong> lesson 8-execsnoop</a></li><li class="chapter-item expanded "><a href="../9-runqlat/index.html"><strong aria-hidden="true">10.</strong> lesson 9-runqlat</a></li><li class="chapter-item expanded "><a href="../10-hardirqs/index.html"><strong aria-hidden="true">11.</strong> lesson 10-hardirqs</a></li><li class="chapter-item expanded affix "><li class="part-title">进阶文档和示例</li><li class="chapter-item expanded "><a href="../11-bootstrap/index.html"><strong aria-hidden="true">12.</strong> lesson 11-bootstrap</a></li><li class="chapter-item expanded "><a href="../12-profile/index.html" class="active"><strong aria-hidden="true">13.</strong> lesson 12-profile</a></li><li class="chapter-item expanded "><a href="../13-tcpconnlat/index.html"><strong aria-hidden="true">14.</strong> lesson 13-tcpconnlat</a></li><li class="chapter-item expanded "><a href="../14-tcpstates/index.html"><strong aria-hidden="true">15.</strong> lesson 14-tcpstates</a></li><li class="chapter-item expanded "><a href="../15-javagc/index.html"><strong aria-hidden="true">16.</strong> lesson 15-javagc</a></li><li class="chapter-item expanded "><a href="../16-memleak/index.html"><strong aria-hidden="true">17.</strong> lesson 16-memleak</a></li><li class="chapter-item expanded "><a href="../17-biopattern/index.html"><strong aria-hidden="true">18.</strong> lesson 17-biopattern</a></li><li class="chapter-item expanded "><a href="../18-further-reading/index.html"><strong aria-hidden="true">19.</strong> lesson 18-further-reading</a></li><li class="chapter-item expanded "><a href="../19-lsm-connect/index.html"><strong aria-hidden="true">20.</strong> lesson 19-lsm-connect</a></li><li class="chapter-item expanded "><a href="../20-tc/index.html"><strong aria-hidden="true">21.</strong> lesson 20-tc</a></li><li class="chapter-item expanded "><a href="../21-xdp/index.html"><strong aria-hidden="true">22.</strong> lesson 21-xdp</a></li><li class="chapter-item expanded affix "><li class="part-title">高级主题</li><li class="chapter-item expanded "><a href="../22-android/index.html"><strong aria-hidden="true">23.</strong> 在 Android 上使用 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../30-sslsniff/index.html"><strong aria-hidden="true">24.</strong> 使用 uprobe 捕获多种库的 SSL/TLS 明文数据</a></li><li class="chapter-item expanded "><a href="../23-http/index.html"><strong aria-hidden="true">25.</strong> 使用 eBPF socket filter 或 syscall trace 追踪 HTTP 请求和其他七层协议</a></li><li class="chapter-item expanded "><a href="../29-sockops/index.html"><strong aria-hidden="true">26.</strong> 使用 sockops 加速网络请求转发</a></li><li class="chapter-item expanded "><a href="../34-syscall/index.html"><strong aria-hidden="true">27.</strong> 使用 eBPF 修改系统调用参数</a></li><li class="chapter-item expanded "><a href="../24-hide/index.html"><strong aria-hidden="true">28.</strong> 使用 eBPF 隐藏进程或文件信息</a></li><li class="chapter-item expanded "><a href="../25-signal/index.html"><strong aria-hidden="true">29.</strong> 使用 bpf_send_signal 发送信号终止进程</a></li><li class="chapter-item expanded "><a href="../26-sudo/index.html"><strong aria-hidden="true">30.</strong> 使用 eBPF 添加 sudo 用户</a></li><li class="chapter-item expanded "><a href="../27-replace/index.html"><strong aria-hidden="true">31.</strong> 使用 eBPF 替换任意程序读取或写入的文本</a></li><li class="chapter-item expanded "><a href="../28-detach/index.html"><strong aria-hidden="true">32.</strong> BPF 的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../18-further-reading/ebpf-security.zh.html"><strong aria-hidden="true">33.</strong> eBPF 运行时的安全性与面临的挑战</a></li><li class="chapter-item expanded "><a href="../src/36-userspace-ebpf/README.html"><strong aria-hidden="true">34.</strong> 用户空间 eBPF 运行时:深度解析与应用实践</a></li><li class="chapter-item expanded affix "><li class="part-title">bcc 和 bpftrace 教程与文档</li><li class="chapter-item expanded "><a href="../bcc-documents/kernel-versions.html"><strong aria-hidden="true">35.</strong> BPF Features by Linux Kernel Version</a></li><li class="chapter-item expanded "><a href="../bcc-documents/kernel_config.html"><strong aria-hidden="true">36.</strong> Kernel Configuration for BPF Features</a></li><li class="chapter-item expanded "><a href="../bcc-documents/reference_guide.html"><strong aria-hidden="true">37.</strong> bcc Reference Guide</a></li><li class="chapter-item expanded "><a href="../bcc-documents/special_filtering.html"><strong aria-hidden="true">38.</strong> Special Filtering</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial.html"><strong aria-hidden="true">39.</strong> bcc Tutorial</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial_bcc_python_developer.html"><strong aria-hidden="true">40.</strong> bcc Python Developer Tutorial</a></li><li class="chapter-item expanded "><a href="../bpftrace-tutorial/index.html"><strong aria-hidden="true">41.</strong> bpftrace Tutorial</a></li></ol>
|
||
</div>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||
<div class="sidebar-resize-indicator"></div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Track and set sidebar scroll position -->
|
||
<script>
|
||
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
||
sidebarScrollbox.addEventListener('click', function(e) {
|
||
if (e.target.tagName === 'A') {
|
||
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
||
}
|
||
}, { passive: true });
|
||
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
||
sessionStorage.removeItem('sidebar-scroll');
|
||
if (sidebarScrollTop) {
|
||
// preserve sidebar scroll position when navigating via links within sidebar
|
||
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
||
} else {
|
||
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
||
var activeSection = document.querySelector('#sidebar .active');
|
||
if (activeSection) {
|
||
activeSection.scrollIntoView({ block: 'center' });
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar-hover-placeholder"></div>
|
||
<div id="menu-bar" class="menu-bar sticky">
|
||
<div class="left-buttons">
|
||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</label>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<h1 class="menu-title">bpf-developer-tutorial</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script>
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<h1 id="ebpf-入门实践教程十二使用-ebpf-程序-profile-进行性能分析"><a class="header" href="#ebpf-入门实践教程十二使用-ebpf-程序-profile-进行性能分析">eBPF 入门实践教程十二:使用 eBPF 程序 profile 进行性能分析</a></h1>
|
||
<p>本教程将指导您使用 libbpf 和 eBPF 程序进行性能分析。我们将利用内核中的 perf 机制,学习如何捕获函数的执行时间以及如何查看性能数据。</p>
|
||
<p>libbpf 是一个用于与 eBPF 交互的 C 库。它提供了创建、加载和使用 eBPF 程序所需的基本功能。本教程中,我们将主要使用 libbpf 完成开发工作。perf 是 Linux 内核中的性能分析工具,允许用户测量和分析内核及用户空间程序的性能,以及获取对应的调用堆栈。它利用内核中的硬件计数器和软件事件来收集性能数据。</p>
|
||
<h2 id="ebpf-工具profile-性能分析示例"><a class="header" href="#ebpf-工具profile-性能分析示例">eBPF 工具:profile 性能分析示例</a></h2>
|
||
<p><code>profile</code> 工具基于 eBPF 实现,利用 Linux 内核中的 perf 事件进行性能分析。<code>profile</code> 工具会定期对每个处理器进行采样,以便捕获内核函数和用户空间函数的执行。它可以显示栈回溯的以下信息:</p>
|
||
<ul>
|
||
<li>地址:函数调用的内存地址</li>
|
||
<li>符号:函数名称</li>
|
||
<li>文件名:源代码文件名称</li>
|
||
<li>行号:源代码中的行号</li>
|
||
</ul>
|
||
<p>这些信息有助于开发人员定位性能瓶颈和优化代码。更进一步,可以通过这些对应的信息生成火焰图,以便更直观的查看性能数据。</p>
|
||
<p>在本示例中,可以通过 libbpf 库编译运行它(以 Ubuntu/Debian 为例):</p>
|
||
<p><strong>NOTE:</strong> 首先需要安装 <code>cargo</code> 才能编译得到 <code>profile</code>, 安装方法可以参考<a href="https://rustwiki.org/en/cargo/getting-started/installation.html">Cargo 手册</a></p>
|
||
<pre><code class="language-console">$ git submodule update --init --recursive
|
||
$ sudo apt install clang libelf1 libelf-dev zlib1g-dev
|
||
$ make
|
||
$ sudo ./profile
|
||
COMM: chronyd (pid=156) @ CPU 1
|
||
Kernel:
|
||
0 [<ffffffff81ee9f56>] _raw_spin_lock_irqsave+0x16
|
||
1 [<ffffffff811527b4>] remove_wait_queue+0x14
|
||
2 [<ffffffff8132611d>] poll_freewait+0x3d
|
||
3 [<ffffffff81326d3f>] do_select+0x7bf
|
||
4 [<ffffffff81327af2>] core_sys_select+0x182
|
||
5 [<ffffffff81327f3a>] __x64_sys_pselect6+0xea
|
||
6 [<ffffffff81ed9e38>] do_syscall_64+0x38
|
||
7 [<ffffffff82000099>] entry_SYSCALL_64_after_hwframe+0x61
|
||
Userspace:
|
||
0 [<00007fab187bfe09>]
|
||
1 [<000000000ee6ae98>]
|
||
|
||
COMM: profile (pid=9843) @ CPU 6
|
||
No Kernel Stack
|
||
Userspace:
|
||
0 [<0000556deb068ac8>]
|
||
1 [<0000556dec34cad0>]
|
||
</code></pre>
|
||
<h2 id="实现原理"><a class="header" href="#实现原理">实现原理</a></h2>
|
||
<p>profile 工具由两个部分组成,内核态中的 eBPF 程序和用户态中的 <code>profile</code> 符号处理程序。<code>profile</code> 符号处理程序负责加载 eBPF 程序,以及处理 eBPF 程序输出的数据。</p>
|
||
<h3 id="内核态部分"><a class="header" href="#内核态部分">内核态部分</a></h3>
|
||
<p>内核态 eBPF 程序的实现逻辑主要是借助 perf event,对程序的堆栈进行定时采样,从而捕获程序的执行流程。</p>
|
||
<pre><code class="language-c">// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||
/* Copyright (c) 2022 Meta Platforms, Inc. */
|
||
#include "vmlinux.h"
|
||
#include <bpf/bpf_helpers.h>
|
||
#include <bpf/bpf_tracing.h>
|
||
#include <bpf/bpf_core_read.h>
|
||
|
||
#include "profile.h"
|
||
|
||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||
__uint(max_entries, 256 * 1024);
|
||
} events SEC(".maps");
|
||
|
||
SEC("perf_event")
|
||
int profile(void *ctx)
|
||
{
|
||
int pid = bpf_get_current_pid_tgid() >> 32;
|
||
int cpu_id = bpf_get_smp_processor_id();
|
||
struct stacktrace_event *event;
|
||
int cp;
|
||
|
||
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
|
||
if (!event)
|
||
return 1;
|
||
|
||
event->pid = pid;
|
||
event->cpu_id = cpu_id;
|
||
|
||
if (bpf_get_current_comm(event->comm, sizeof(event->comm)))
|
||
event->comm[0] = 0;
|
||
|
||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||
|
||
event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||
|
||
bpf_ringbuf_submit(event, 0);
|
||
|
||
return 0;
|
||
}
|
||
</code></pre>
|
||
<p>接下来,我们将重点讲解内核态代码的关键部分。</p>
|
||
<ol>
|
||
<li>
|
||
<p>定义 eBPF maps <code>events</code>:</p>
|
||
<pre><code class="language-c">
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||
__uint(max_entries, 256 * 1024);
|
||
} events SEC(".maps");
|
||
</code></pre>
|
||
<p>这里定义了一个类型为 <code>BPF_MAP_TYPE_RINGBUF</code> 的 eBPF maps 。Ring Buffer 是一种高性能的循环缓冲区,用于在内核和用户空间之间传输数据。<code>max_entries</code> 设置了 Ring Buffer 的最大大小。</p>
|
||
</li>
|
||
<li>
|
||
<p>定义 <code>perf_event</code> eBPF 程序:</p>
|
||
<pre><code class="language-c">SEC("perf_event")
|
||
int profile(void *ctx)
|
||
</code></pre>
|
||
<p>这里定义了一个名为 <code>profile</code> 的 eBPF 程序,它将在 perf 事件触发时执行。</p>
|
||
</li>
|
||
<li>
|
||
<p>获取进程 ID 和 CPU ID:</p>
|
||
<pre><code class="language-c">int pid = bpf_get_current_pid_tgid() >> 32;
|
||
int cpu_id = bpf_get_smp_processor_id();
|
||
</code></pre>
|
||
<p><code>bpf_get_current_pid_tgid()</code> 函数返回当前进程的 PID 和 TID,通过右移 32 位,我们得到 PID。<code>bpf_get_smp_processor_id()</code> 函数返回当前 CPU 的 ID。</p>
|
||
</li>
|
||
<li>
|
||
<p>预留 Ring Buffer 空间:</p>
|
||
<pre><code class="language-c">event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
|
||
if (!event)
|
||
return 1;
|
||
</code></pre>
|
||
<p>通过 <code>bpf_ringbuf_reserve()</code> 函数预留 Ring Buffer 空间,用于存储采集的栈信息。若预留失败,返回错误.</p>
|
||
</li>
|
||
<li>
|
||
<p>获取当前进程名:</p>
|
||
<pre><code class="language-c">
|
||
if (bpf_get_current_comm(event->comm, sizeof(event->comm)))
|
||
event->comm[0] = 0;
|
||
</code></pre>
|
||
<p>使用 <code>bpf_get_current_comm()</code> 函数获取当前进程名并将其存储到 <code>event->comm</code>。</p>
|
||
</li>
|
||
<li>
|
||
<p>获取内核栈信息:</p>
|
||
<pre><code class="language-c">
|
||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||
</code></pre>
|
||
<p>使用 <code>bpf_get_stack()</code> 函数获取内核栈信息。将结果存储在 <code>event->kstack</code>,并将其大小存储在 <code>event->kstack_sz</code>。</p>
|
||
</li>
|
||
<li>
|
||
<p>获取用户空间栈信息:</p>
|
||
<pre><code class="language-c">event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||
</code></pre>
|
||
<p>同样使用 <code>bpf_get_stack()</code> 函数,但传递 <code>BPF_F_USER_STACK</code> 标志以获取用户空间栈信息。将结果存储在 <code>event->ustack</code>,并将其大小存储在 <code>event->ustack_sz</code>。</p>
|
||
</li>
|
||
<li>
|
||
<p>将事件提交到 Ring Buffer:</p>
|
||
<pre><code class="language-c">bpf_ringbuf_submit(event, 0);
|
||
</code></pre>
|
||
<p>最后,使用 <code>bpf_ringbuf_submit()</code> 函数将事件提交到 Ring Buffer,以便用户空间程序可以读取和处理。</p>
|
||
<p>这个内核态 eBPF 程序通过定期采样程序的内核栈和用户空间栈来捕获程序的执行流程。这些数据将存储在 Ring Buffer 中,以便用户态的 <code>profile</code> 程序能读取。</p>
|
||
</li>
|
||
</ol>
|
||
<h3 id="用户态部分"><a class="header" href="#用户态部分">用户态部分</a></h3>
|
||
<p>这段代码主要负责为每个在线 CPU 设置 perf event 并附加 eBPF 程序:</p>
|
||
<pre><code class="language-c">static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||
int cpu, int group_fd, unsigned long flags)
|
||
{
|
||
int ret;
|
||
|
||
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||
return ret;
|
||
}
|
||
|
||
int main(){
|
||
...
|
||
for (cpu = 0; cpu < num_cpus; cpu++) {
|
||
/* skip offline/not present CPUs */
|
||
if (cpu >= num_online_cpus || !online_mask[cpu])
|
||
continue;
|
||
|
||
/* Set up performance monitoring on a CPU/Core */
|
||
pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||
if (pefd < 0) {
|
||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||
err = -1;
|
||
goto cleanup;
|
||
}
|
||
pefds[cpu] = pefd;
|
||
|
||
/* Attach a BPF program on a CPU */
|
||
links[cpu] = bpf_program__attach_perf_event(skel->progs.profile, pefd);
|
||
if (!links[cpu]) {
|
||
err = -1;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
...
|
||
}
|
||
</code></pre>
|
||
<p><code>perf_event_open</code> 这个函数是一个对 perf_event_open 系统调用的封装。它接收一个 perf_event_attr 结构体指针,用于指定 perf event 的类型和属性。pid 参数用于指定要监控的进程 ID(-1 表示监控所有进程),cpu 参数用于指定要监控的 CPU。group_fd 参数用于将 perf event 分组,这里我们使用 -1,表示不需要分组。flags 参数用于设置一些标志,这里我们使用 PERF_FLAG_FD_CLOEXEC 以确保在执行 exec 系列系统调用时关闭文件描述符。</p>
|
||
<p>在 main 函数中:</p>
|
||
<pre><code class="language-c">for (cpu = 0; cpu < num_cpus; cpu++) {
|
||
// ...
|
||
}
|
||
</code></pre>
|
||
<p>这个循环针对每个在线 CPU 设置 perf event 并附加 eBPF 程序。首先,它会检查当前 CPU 是否在线,如果不在线则跳过。然后,使用 perf_event_open() 函数为当前 CPU 设置 perf event,并将返回的文件描述符存储在 pefds 数组中。最后,使用 bpf_program__attach_perf_event() 函数将 eBPF 程序附加到 perf event。links 数组用于存储每个 CPU 上的 BPF 链接,以便在程序结束时销毁它们。</p>
|
||
<p>通过这种方式,用户态程序为每个在线 CPU 设置 perf event,并将 eBPF 程序附加到这些 perf event 上,从而实现对系统中所有在线 CPU 的监控。</p>
|
||
<p>以下这两个函数分别用于显示栈回溯和处理从 ring buffer 接收到的事件:</p>
|
||
<pre><code class="language-c">static void show_stack_trace(__u64 *stack, int stack_sz, pid_t pid)
|
||
{
|
||
const struct blazesym_result *result;
|
||
const struct blazesym_csym *sym;
|
||
sym_src_cfg src;
|
||
int i, j;
|
||
|
||
if (pid) {
|
||
src.src_type = SRC_T_PROCESS;
|
||
src.params.process.pid = pid;
|
||
} else {
|
||
src.src_type = SRC_T_KERNEL;
|
||
src.params.kernel.kallsyms = NULL;
|
||
src.params.kernel.kernel_image = NULL;
|
||
}
|
||
|
||
result = blazesym_symbolize(symbolizer, &src, 1, (const uint64_t *)stack, stack_sz);
|
||
|
||
for (i = 0; i < stack_sz; i++) {
|
||
if (!result || result->size <= i || !result->entries[i].size) {
|
||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||
continue;
|
||
}
|
||
|
||
if (result->entries[i].size == 1) {
|
||
sym = &result->entries[i].syms[0];
|
||
if (sym->path && sym->path[0]) {
|
||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||
i, stack[i], sym->symbol,
|
||
stack[i] - sym->start_address,
|
||
sym->path, sym->line_no);
|
||
} else {
|
||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||
i, stack[i], sym->symbol,
|
||
stack[i] - sym->start_address);
|
||
}
|
||
continue;
|
||
}
|
||
|
||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||
for (j = 0; j < result->entries[i].size; j++) {
|
||
sym = &result->entries[i].syms[j];
|
||
if (sym->path && sym->path[0]) {
|
||
printf(" %s+0x%llx %s:%ld\n",
|
||
sym->symbol, stack[i] - sym->start_address,
|
||
sym->path, sym->line_no);
|
||
} else {
|
||
printf(" %s+0x%llx\n", sym->symbol,
|
||
stack[i] - sym->start_address);
|
||
}
|
||
}
|
||
}
|
||
|
||
blazesym_result_free(result);
|
||
}
|
||
|
||
/* Receive events from the ring buffer. */
|
||
static int event_handler(void *_ctx, void *data, size_t size)
|
||
{
|
||
struct stacktrace_event *event = data;
|
||
|
||
if (event->kstack_sz <= 0 && event->ustack_sz <= 0)
|
||
return 1;
|
||
|
||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||
|
||
if (event->kstack_sz > 0) {
|
||
printf("Kernel:\n");
|
||
show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0);
|
||
} else {
|
||
printf("No Kernel Stack\n");
|
||
}
|
||
|
||
if (event->ustack_sz > 0) {
|
||
printf("Userspace:\n");
|
||
show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid);
|
||
} else {
|
||
printf("No Userspace Stack\n");
|
||
}
|
||
|
||
printf("\n");
|
||
return 0;
|
||
}
|
||
</code></pre>
|
||
<p><code>show_stack_trace()</code> 函数用于显示内核或用户空间的栈回溯。它接收一个 stack 参数,是一个指向内核或用户空间栈的指针,stack_sz 参数表示栈的大小,pid 参数表示要显示的进程的 ID(当显示内核栈时,设置为 0)。函数中首先根据 pid 参数确定栈的来源(内核或用户空间),然后调用 blazesym_symbolize() 函数将栈中的地址解析为符号名和源代码位置。最后,遍历解析结果,输出符号名和源代码位置信息。</p>
|
||
<p><code>event_handler()</code> 函数用于处理从 ring buffer 接收到的事件。它接收一个 data 参数,指向 ring buffer 中的数据,size 参数表示数据的大小。函数首先将 data 指针转换为 stacktrace_event 结构体指针,然后检查内核和用户空间栈的大小。如果栈为空,则直接返回。接下来,函数输出进程名称、进程 ID 和 CPU ID 信息。然后分别显示内核栈和用户空间栈的回溯。调用 show_stack_trace() 函数时,分别传入内核栈和用户空间栈的地址、大小和进程 ID。</p>
|
||
<p>这两个函数作为 eBPF profile 工具的一部分,用于显示和处理 eBPF 程序收集到的栈回溯信息,帮助用户了解程序的运行情况和性能瓶颈。</p>
|
||
<h3 id="总结"><a class="header" href="#总结">总结</a></h3>
|
||
<p>通过本篇 eBPF 入门实践教程,我们学习了如何使用 eBPF 程序进行性能分析。在这个过程中,我们详细讲解了如何创建 eBPF 程序,监控进程的性能,并从 ring buffer 中获取数据以分析栈回溯。我们还学习了如何使用 perf_event_open() 函数设置性能监控,并将 BPF 程序附加到性能事件上。在本教程中,我们还展示了如何编写 eBPF 程序来捕获进程的内核和用户空间栈信息,进而分析程序性能瓶颈。通过这个例子,您可以了解到 eBPF 在性能分析方面的强大功能。</p>
|
||
<p>如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档:<a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a> 。您还可以访问我们的教程代码仓库 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 或网站 <a href="https://eunomia.dev/zh/tutorials/">https://eunomia.dev/zh/tutorials/</a> 以获取更多示例和完整的教程。</p>
|
||
<p>接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容,帮助您更好地理解和掌握 eBPF 技术,希望这些内容对您在 eBPF 开发道路上的学习和实践有所帮助。</p>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
<a rel="prev" href="../11-bootstrap/index.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../13-tcpconnlat/index.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
<a rel="prev" href="../11-bootstrap/index.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../13-tcpconnlat/index.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<script>
|
||
window.playground_copyable = true;
|
||
</script>
|
||
|
||
|
||
<script src="../elasticlunr.min.js"></script>
|
||
<script src="../mark.min.js"></script>
|
||
<script src="../searcher.js"></script>
|
||
|
||
<script src="../clipboard.min.js"></script>
|
||
<script src="../highlight.js"></script>
|
||
<script src="../book.js"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|