Files
bpf-developer-tutorial/17-biopattern/index.html

508 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O - 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> 介绍 eBPF 的基本概念、常见的开发工具</a></li><li class="chapter-item expanded "><a href="../1-helloworld/index.html"><strong aria-hidden="true">2.</strong> eBPF Hello World基本框架和开发流程</a></li><li class="chapter-item expanded "><a href="../2-kprobe-unlink/index.html"><strong aria-hidden="true">3.</strong> 使用 kprobe 监测捕获 unlink 系统调用</a></li><li class="chapter-item expanded "><a href="../3-fentry-unlink/index.html"><strong aria-hidden="true">4.</strong> 使用 fentry 监测捕获 unlink 系统调用</a></li><li class="chapter-item expanded "><a href="../4-opensnoop/index.html"><strong aria-hidden="true">5.</strong> 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid</a></li><li class="chapter-item expanded "><a href="../5-uprobe-bashreadline/index.html"><strong aria-hidden="true">6.</strong> 使用 uprobe 捕获 bash 的 readline 函数调用</a></li><li class="chapter-item expanded "><a href="../6-sigsnoop/index.html"><strong aria-hidden="true">7.</strong> 捕获进程发送信号的系统调用集合,使用 hash map 保存状态</a></li><li class="chapter-item expanded "><a href="../7-execsnoop/index.html"><strong aria-hidden="true">8.</strong> 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出</a></li><li class="chapter-item expanded "><a href="../8-exitsnoop/index.html"><strong aria-hidden="true">9.</strong> 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出</a></li><li class="chapter-item expanded "><a href="../9-runqlat/index.html"><strong aria-hidden="true">10.</strong> 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度</a></li><li class="chapter-item expanded "><a href="../10-hardirqs/index.html"><strong aria-hidden="true">11.</strong> 使用 hardirqs 或 softirqs 捕获中断事件</a></li><li class="chapter-item expanded "><a href="../11-bootstrap/index.html"><strong aria-hidden="true">12.</strong> 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用</a></li><li class="chapter-item expanded "><a href="../13-tcpconnlat/index.html"><strong aria-hidden="true">13.</strong> 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时</a></li><li class="chapter-item expanded "><a href="../14-tcpstates/index.html"><strong aria-hidden="true">14.</strong> 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT</a></li><li class="chapter-item expanded "><a href="../15-javagc/index.html"><strong aria-hidden="true">15.</strong> 使用 USDT 捕获用户态 Java GC 事件耗时</a></li><li class="chapter-item expanded "><a href="../16-memleak/index.html"><strong aria-hidden="true">16.</strong> 编写 eBPF 程序 Memleak 监控内存泄漏</a></li><li class="chapter-item expanded "><a href="../17-biopattern/index.html" class="active"><strong aria-hidden="true">17.</strong> 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O</a></li><li class="chapter-item expanded "><a href="../18-further-reading/index.html"><strong aria-hidden="true">18.</strong> 更多的参考资料:论文列表、项目、博客等等</a></li><li class="chapter-item expanded "><a href="../19-lsm-connect/index.html"><strong aria-hidden="true">19.</strong> 使用 LSM 进行安全检测防御</a></li><li class="chapter-item expanded "><a href="../20-tc/index.html"><strong aria-hidden="true">20.</strong> 使用 eBPF 进行 tc 流量控制</a></li><li class="chapter-item expanded affix "><li class="part-title">eBPF 高级特性与进阶主题</li><li class="chapter-item expanded "><a href="../22-android/index.html"><strong aria-hidden="true">21.</strong> 在 Android 上使用 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../23-http/index.html"><strong aria-hidden="true">22.</strong> 使用 eBPF socket filter 或 syscall tracepoint 追踪 HTTP 请求等七层协议</a></li><li class="chapter-item expanded "><a href="../30-sslsniff/index.html"><strong aria-hidden="true">23.</strong> 使用 uprobe 捕获多种库的 SSL/TLS 明文数据</a></li><li class="chapter-item expanded "><a href="../29-sockops/index.html"><strong aria-hidden="true">24.</strong> 使用 sockops 加速网络请求转发</a></li><li class="chapter-item expanded "><a href="../18-further-reading/ebpf-security.zh.html"><strong aria-hidden="true">25.</strong> eBPF 运行时的安全性与面临的挑战</a></li><li class="chapter-item expanded "><a href="../24-hide/index.html"><strong aria-hidden="true">26.</strong> 使用 eBPF 隐藏进程或文件信息</a></li><li class="chapter-item expanded "><a href="../25-signal/index.html"><strong aria-hidden="true">27.</strong> 使用 bpf_send_signal 发送信号终止进程</a></li><li class="chapter-item expanded "><a href="../26-sudo/index.html"><strong aria-hidden="true">28.</strong> 使用 eBPF 添加 sudo 用户</a></li><li class="chapter-item expanded "><a href="../27-replace/index.html"><strong aria-hidden="true">29.</strong> 使用 eBPF 替换任意程序读取或写入的文本</a></li><li class="chapter-item expanded "><a href="../28-detach/index.html"><strong aria-hidden="true">30.</strong> BPF的生命周期使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../30-sslsniff/index.html"><strong aria-hidden="true">31.</strong> 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据</a></li><li class="chapter-item expanded affix "><li class="part-title">bcc 教程与文档</li><li class="chapter-item expanded "><a href="../bcc-documents/kernel-versions.html"><strong aria-hidden="true">32.</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">33.</strong> Kernel Configuration for BPF Features</a></li><li class="chapter-item expanded "><a href="../bcc-documents/reference_guide.html"><strong aria-hidden="true">34.</strong> bcc Reference Guide</a></li><li class="chapter-item expanded "><a href="../bcc-documents/special_filtering.html"><strong aria-hidden="true">35.</strong> Special Filtering</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial.html"><strong aria-hidden="true">36.</strong> bcc Tutorial</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial_bcc_python_developer.html"><strong aria-hidden="true">37.</strong> bcc Python Developer Tutorial</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></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-程序统计随机顺序磁盘-io"><a class="header" href="#ebpf-入门实践教程十七编写-ebpf-程序统计随机顺序磁盘-io">eBPF 入门实践教程十七:编写 eBPF 程序统计随机/顺序磁盘 I/O</a></h1>
<p>eBPF扩展的伯克利数据包过滤器是 Linux 内核中的一种新技术,允许用户在内核空间中执行自定义程序,而无需更改内核代码。这为系统管理员和开发者提供了强大的工具,可以深入了解和监控系统的行为,从而进行优化。</p>
<p>在本篇教程中,我们将探索如何使用 eBPF 编写程序来统计随机和顺序的磁盘 I/O。磁盘 I/O 是计算机性能的关键指标之一,特别是在数据密集型应用中。</p>
<h2 id="随机顺序磁盘-io"><a class="header" href="#随机顺序磁盘-io">随机/顺序磁盘 I/O</a></h2>
<p>随着技术的进步和数据量的爆炸性增长,磁盘 I/O 成为了系统性能的关键瓶颈。应用程序的性能很大程度上取决于其如何与存储层进行交互。因此,深入了解和优化磁盘 I/O特别是随机和顺序的 I/O变得尤为重要。</p>
<ol>
<li>
<p><strong>随机 I/O</strong>:随机 I/O 发生在应用程序从磁盘的非连续位置读取或写入数据时。这种 I/O 模式的主要特点是磁盘头需要频繁地在不同的位置之间移动,导致其通常比顺序 I/O 的速度慢。典型的产生随机 I/O 的场景包括数据库查询、文件系统的元数据操作以及虚拟化环境中的并发任务。</p>
</li>
<li>
<p><strong>顺序 I/O</strong>:与随机 I/O 相反,顺序 I/O 是当应用程序连续地读取或写入磁盘上的数据块。这种 I/O 模式的优势在于磁盘头可以在一个方向上连续移动,从而大大提高了数据的读写速度。视频播放、大型文件的下载或上传以及连续的日志记录都是产生顺序 I/O 的典型应用。</p>
</li>
</ol>
<p>为了实现存储性能的最优化,了解随机和顺序的磁盘 I/O 是至关重要的。例如,随机 I/O 敏感的应用程序在 SSD 上的性能通常远超于传统硬盘,因为 SSD 在处理随机 I/O 时几乎没有寻址延迟。相反,对于大量顺序 I/O 的应用,如何最大化磁盘的连续读写速度则更为关键。</p>
<p>在本教程的后续部分,我们将详细探讨如何使用 eBPF 工具来实时监控和统计这两种类型的磁盘 I/O。这不仅可以帮助我们更好地理解系统的 I/O 行为,还可以为进一步的性能优化提供有力的数据支持。</p>
<h2 id="biopattern"><a class="header" href="#biopattern">Biopattern</a></h2>
<p>Biopattern 可以统计随机/顺序磁盘I/O次数的比例。</p>
<p>首先,确保你已经正确安装了 libbpf 和相关的工具集,可以在这里找到对应的源代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">bpf-developer-tutorial</a></p>
<p>导航到 <code>biopattern</code> 的源代码目录,并使用 <code>make</code> 命令进行编译:</p>
<pre><code class="language-bash">cd ~/bpf-developer-tutorial/src/17-biopattern
make
</code></pre>
<p>编译成功后,你应该可以在当前目录下看到 <code>biopattern</code> 的可执行文件。基本的运行命令如下:</p>
<pre><code class="language-bash">sudo ./biopattern [interval] [count]
</code></pre>
<p>例如要每秒打印一次输出并持续10秒你可以运行</p>
<pre><code class="language-console">$ sudo ./biopattern 1 10
Tracing block device I/O requested seeks... Hit Ctrl-C to end.
DISK %RND %SEQ COUNT KBYTES
sr0 0 100 3 0
sr1 0 100 8 0
sda 0 100 1 4
sda 100 0 26 136
sda 0 100 1 4
</code></pre>
<p>输出列的含义如下:</p>
<ul>
<li><code>DISK</code>:被追踪的磁盘名称。</li>
<li><code>%RND</code>:随机 I/O 的百分比。</li>
<li><code>%SEQ</code>:顺序 I/O 的百分比。</li>
<li><code>COUNT</code>:在指定的时间间隔内的 I/O 请求次数。</li>
<li><code>KBYTES</code>:在指定的时间间隔内读写的数据量(以 KB 为单位)。</li>
</ul>
<p>从上述输出中,我们可以得出以下结论:</p>
<ul>
<li><code>sr0</code><code>sr1</code> 设备在观测期间主要进行了顺序 I/O但数据量很小。</li>
<li><code>sda</code> 设备在某些时间段内只进行了随机 I/O而在其他时间段内只进行了顺序 I/O。</li>
</ul>
<p>这些信息可以帮助我们了解系统的 I/O 模式,从而进行针对性的优化。</p>
<h2 id="ebpf-biopattern-实现原理"><a class="header" href="#ebpf-biopattern-实现原理">eBPF Biopattern 实现原理</a></h2>
<p>首先,让我们看一下 biopattern 的核心 eBPF 内核态代码:</p>
<pre><code class="language-c">#include &lt;vmlinux.h&gt;
#include &lt;bpf/bpf_helpers.h&gt;
#include &lt;bpf/bpf_tracing.h&gt;
#include &quot;biopattern.h&quot;
#include &quot;maps.bpf.h&quot;
#include &quot;core_fixes.bpf.h&quot;
const volatile bool filter_dev = false;
const volatile __u32 targ_dev = 0;
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, u32);
__type(value, struct counter);
} counters SEC(&quot;.maps&quot;);
SEC(&quot;tracepoint/block/block_rq_complete&quot;)
int handle__block_rq_complete(void *args)
{
struct counter *counterp, zero = {};
sector_t sector;
u32 nr_sector;
u32 dev;
if (has_block_rq_completion()) {
struct trace_event_raw_block_rq_completion___x *ctx = args;
sector = BPF_CORE_READ(ctx, sector);
nr_sector = BPF_CORE_READ(ctx, nr_sector);
dev = BPF_CORE_READ(ctx, dev);
} else {
struct trace_event_raw_block_rq_complete___x *ctx = args;
sector = BPF_CORE_READ(ctx, sector);
nr_sector = BPF_CORE_READ(ctx, nr_sector);
dev = BPF_CORE_READ(ctx, dev);
}
if (filter_dev &amp;&amp; targ_dev != dev)
return 0;
counterp = bpf_map_lookup_or_try_init(&amp;counters, &amp;dev, &amp;zero);
if (!counterp)
return 0;
if (counterp-&gt;last_sector) {
if (counterp-&gt;last_sector == sector)
__sync_fetch_and_add(&amp;counterp-&gt;sequential, 1);
else
__sync_fetch_and_add(&amp;counterp-&gt;random, 1);
__sync_fetch_and_add(&amp;counterp-&gt;bytes, nr_sector * 512);
}
counterp-&gt;last_sector = sector + nr_sector;
return 0;
}
char LICENSE[] SEC(&quot;license&quot;) = &quot;GPL&quot;;
</code></pre>
<ol>
<li>全局变量定义</li>
</ol>
<pre><code class="language-c"> const volatile bool filter_dev = false;
const volatile __u32 targ_dev = 0;
</code></pre>
<p>这两个全局变量用于设备过滤。<code>filter_dev</code> 决定是否启用设备过滤,而 <code>targ_dev</code> 是我们想要追踪的目标设备的标识符。</p>
<p>BPF map 定义:</p>
<pre><code class="language-c"> struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, u32);
__type(value, struct counter);
} counters SEC(&quot;.maps&quot;);
</code></pre>
<p>这部分代码定义了一个 BPF map类型为哈希表。该映射的键是设备的标识符而值是一个 <code>counter</code> 结构体,用于存储设备的 I/O 统计信息。</p>
<p>追踪点函数:</p>
<pre><code class="language-c"> SEC(&quot;tracepoint/block/block_rq_complete&quot;)
int handle__block_rq_complete(void *args)
{
struct counter *counterp, zero = {};
sector_t sector;
u32 nr_sector;
u32 dev;
if (has_block_rq_completion()) {
struct trace_event_raw_block_rq_completion___x *ctx = args;
sector = BPF_CORE_READ(ctx, sector);
nr_sector = BPF_CORE_READ(ctx, nr_sector);
dev = BPF_CORE_READ(ctx, dev);
} else {
struct trace_event_raw_block_rq_complete___x *ctx = args;
sector = BPF_CORE_READ(ctx, sector);
nr_sector = BPF_CORE_READ(ctx, nr_sector);
dev = BPF_CORE_READ(ctx, dev);
}
if (filter_dev &amp;&amp; targ_dev != dev)
return 0;
counterp = bpf_map_lookup_or_try_init(&amp;counters, &amp;dev, &amp;zero);
if (!counterp)
return 0;
if (counterp-&gt;last_sector) {
if (counterp-&gt;last_sector == sector)
__sync_fetch_and_add(&amp;counterp-&gt;sequential, 1);
else
__sync_fetch_and_add(&amp;counterp-&gt;random, 1);
__sync_fetch_and_add(&amp;counterp-&gt;bytes, nr_sector * 512);
}
counterp-&gt;last_sector = sector + nr_sector;
return 0;
}
</code></pre>
<p>在 Linux 中,每次块设备的 I/O 请求完成时,都会触发一个名为 <code>block_rq_complete</code> 的追踪点。这为我们提供了一个机会,通过 eBPF 来捕获这些事件,并进一步分析 I/O 的模式。</p>
<p>主要逻辑分析:</p>
<ul>
<li><strong>提取 I/O 请求信息</strong>:从传入的参数中获取 I/O 请求的相关信息。这里有两种可能的上下文结构,取决于 <code>has_block_rq_completion</code> 的返回值。这是因为不同版本的 Linux 内核可能会有不同的追踪点定义。无论哪种情况,我们都从上下文中提取出扇区号 (<code>sector</code>)、扇区数量 (<code>nr_sector</code>) 和设备标识符 (<code>dev</code>)。</li>
<li><strong>设备过滤</strong>:如果启用了设备过滤 (<code>filter_dev</code><code>true</code>),并且当前设备不是目标设备 (<code>targ_dev</code>),则直接返回。这允许用户只追踪特定的设备,而不是所有设备。</li>
<li><strong>统计信息更新</strong>
- <strong>查找或初始化统计信息</strong>:使用 <code>bpf_map_lookup_or_try_init</code> 函数查找或初始化与当前设备相关的统计信息。如果映射中没有当前设备的统计信息,它会使用 <code>zero</code> 结构体进行初始化。
- <strong>判断 I/O 模式</strong>:根据当前 I/O 请求与上一个 I/O 请求的扇区号,我们可以判断当前请求是随机的还是顺序的。如果两次请求的扇区号相同,那么它是顺序的;否则,它是随机的。然后,我们使用 <code>__sync_fetch_and_add</code> 函数更新相应的统计信息。这是一个原子操作,确保在并发环境中数据的一致性。
- <strong>更新数据量</strong>:我们还更新了该设备的总数据量,这是通过将扇区数量 (<code>nr_sector</code>) 乘以 512每个扇区的字节数来实现的。
- <strong>更新最后一个 I/O 请求的扇区号</strong>:为了下一次的比较,我们更新了 <code>last_sector</code> 的值。</li>
</ul>
<p>在 Linux 内核的某些版本中,由于引入了一个新的追踪点 <code>block_rq_error</code>,追踪点的命名和结构发生了变化。这意味着,原先的 <code>block_rq_complete</code> 追踪点的结构名称从 <code>trace_event_raw_block_rq_complete</code> 更改为 <code>trace_event_raw_block_rq_completion</code>。这种变化可能会导致 eBPF 程序在不同版本的内核上出现兼容性问题。</p>
<p>为了解决这个问题,<code>biopattern</code> 工具引入了一种机制来动态检测当前内核使用的是哪种追踪点结构,即 <code>has_block_rq_completion</code> 函数。</p>
<ol>
<li><strong>定义两种追踪点结构</strong></li>
</ol>
<pre><code class="language-c"> struct trace_event_raw_block_rq_complete___x {
dev_t dev;
sector_t sector;
unsigned int nr_sector;
} __attribute__((preserve_access_index));
struct trace_event_raw_block_rq_completion___x {
dev_t dev;
sector_t sector;
unsigned int nr_sector;
} __attribute__((preserve_access_index));
</code></pre>
<p>这里定义了两种追踪点结构,分别对应于不同版本的内核。每种结构都包含设备标识符 (<code>dev</code>)、扇区号 (<code>sector</code>) 和扇区数量 (<code>nr_sector</code>)。</p>
<p><strong>动态检测追踪点结构</strong></p>
<pre><code class="language-c"> static __always_inline bool has_block_rq_completion()
{
if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x))
return true;
return false;
}
</code></pre>
<p><code>has_block_rq_completion</code> 函数使用 <code>bpf_core_type_exists</code> 函数来检测当前内核是否存在 <code>trace_event_raw_block_rq_completion___x</code> 结构。如果存在,函数返回 <code>true</code>,表示当前内核使用的是新的追踪点结构;否则,返回 <code>false</code>,表示使用的是旧的结构。在对应的 eBPF 代码中,会根据两种不同的定义分别进行处理,这也是适配不同内核版本之间的变更常见的方案。</p>
<h3 id="用户态代码"><a class="header" href="#用户态代码">用户态代码</a></h3>
<p><code>biopattern</code> 工具的用户态代码负责从 BPF 映射中读取统计数据,并将其展示给用户。通过这种方式,系统管理员可以实时监控每个设备的 I/O 模式,从而更好地理解和优化系统的 I/O 性能。</p>
<p>主循环:</p>
<pre><code class="language-c"> /* main: poll */
while (1) {
sleep(env.interval);
err = print_map(obj-&gt;maps.counters, partitions);
if (err)
break;
if (exiting || --env.times == 0)
break;
}
</code></pre>
<p>这是 <code>biopattern</code> 工具的主循环,它的工作流程如下:</p>
<ul>
<li><strong>等待</strong>:使用 <code>sleep</code> 函数等待指定的时间间隔 (<code>env.interval</code>)。</li>
<li><strong>打印映射</strong>:调用 <code>print_map</code> 函数打印 BPF 映射中的统计数据。</li>
<li><strong>退出条件</strong>:如果收到退出信号 (<code>exiting</code><code>true</code>) 或者达到指定的运行次数 (<code>env.times</code> 达到 0),则退出循环。</li>
</ul>
<p>打印映射函数:</p>
<pre><code class="language-c"> static int print_map(struct bpf_map *counters, struct partitions *partitions)
{
__u32 total, lookup_key = -1, next_key;
int err, fd = bpf_map__fd(counters);
const struct partition *partition;
struct counter counter;
struct tm *tm;
char ts[32];
time_t t;
while (!bpf_map_get_next_key(fd, &amp;lookup_key, &amp;next_key)) {
err = bpf_map_lookup_elem(fd, &amp;next_key, &amp;counter);
if (err &lt; 0) {
fprintf(stderr, &quot;failed to lookup counters: %d\n&quot;, err);
return -1;
}
lookup_key = next_key;
total = counter.sequential + counter.random;
if (!total)
continue;
if (env.timestamp) {
time(&amp;t);
tm = localtime(&amp;t);
strftime(ts, sizeof(ts), &quot;%H:%M:%S&quot;, tm);
printf(&quot;%-9s &quot;, ts);
}
partition = partitions__get_by_dev(partitions, next_key);
printf(&quot;%-7s %5ld %5ld %8d %10lld\n&quot;,
partition ? partition-&gt;name : &quot;Unknown&quot;,
counter.random * 100L / total,
counter.sequential * 100L / total, total,
counter.bytes / 1024);
}
lookup_key = -1;
while (!bpf_map_get_next_key(fd, &amp;lookup_key, &amp;next_key)) {
err = bpf_map_delete_elem(fd, &amp;next_key);
if (err &lt; 0) {
fprintf(stderr, &quot;failed to cleanup counters: %d\n&quot;, err);
return -1;
}
lookup_key = next_key;
}
return 0;
}
</code></pre>
<p><code>print_map</code> 函数负责从 BPF 映射中读取统计数据,并将其打印到控制台。其主要逻辑如下:</p>
<ul>
<li><strong>遍历 BPF 映射</strong>:使用 <code>bpf_map_get_next_key</code><code>bpf_map_lookup_elem</code> 函数遍历 BPF 映射,获取每个设备的统计数据。</li>
<li><strong>计算总数</strong>:计算每个设备的随机和顺序 I/O 的总数。</li>
<li><strong>打印统计数据</strong>:如果启用了时间戳 (<code>env.timestamp</code><code>true</code>),则首先打印当前时间。接着,打印设备名称、随机 I/O 的百分比、顺序 I/O 的百分比、总 I/O 数量和总数据量(以 KB 为单位)。</li>
<li><strong>清理 BPF 映射</strong>:为了下一次的统计,使用 <code>bpf_map_get_next_key</code><code>bpf_map_delete_elem</code> 函数清理 BPF 映射中的所有条目。</li>
</ul>
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
<p>在本教程中,我们深入探讨了如何使用 eBPF 工具 biopattern 来实时监控和统计随机和顺序的磁盘 I/O。我们首先了解了随机和顺序磁盘 I/O 的重要性,以及它们对系统性能的影响。接着,我们详细介绍了 biopattern 的工作原理,包括如何定义和使用 BPF maps如何处理不同版本的 Linux 内核中的追踪点变化,以及如何在 eBPF 程序中捕获和分析磁盘 I/O 事件。</p>
<p>您可以访问我们的教程代码仓库 <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>
<ul>
<li>完整代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/17-biopattern">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/17-biopattern</a></li>
<li>bcc 工具:<a href="https://github.com/iovisor/bcc/blob/master/libbpf-tools/biopattern.c">https://github.com/iovisor/bcc/blob/master/libbpf-tools/biopattern.c</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../16-memleak/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="../18-further-reading/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="../16-memleak/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="../18-further-reading/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>