mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
503 lines
34 KiB
HTML
503 lines
34 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="sidebar-visible no-js light">
|
||
<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>
|
||
<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('no-js')
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
html.classList.add('js');
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var html = document.querySelector('html');
|
||
var sidebar = null;
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
html.classList.remove('sidebar-visible');
|
||
html.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 "><li class="part-title">eBPF 实践教程:基于 libbpf 和 CO-RE</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 追踪 HTTP 请求或其他七层协议</a></li><li class="chapter-item expanded "><a href="../29-sockops/index.html"><strong aria-hidden="true">23.</strong> 使用 sockops 加速网络请求转发</a></li><li class="chapter-item expanded "><a href="../24-hide/index.html"><strong aria-hidden="true">24.</strong> 使用 eBPF 隐藏进程或文件信息</a></li><li class="chapter-item expanded "><a href="../25-signal/index.html"><strong aria-hidden="true">25.</strong> 使用 bpf_send_signal 发送信号终止进程</a></li><li class="chapter-item expanded "><a href="../26-sudo/index.html"><strong aria-hidden="true">26.</strong> 使用 eBPF 添加 sudo 用户</a></li><li class="chapter-item expanded "><a href="../27-replace/index.html"><strong aria-hidden="true">27.</strong> 使用 eBPF 替换任意程序读取或写入的文本</a></li><li class="chapter-item expanded "><a href="../28-detach/index.html"><strong aria-hidden="true">28.</strong> BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序</a></li><li class="chapter-item expanded affix "><li class="part-title">bcc tutorial</li><li class="chapter-item expanded "><a href="../bcc-documents/kernel-versions.html"><strong aria-hidden="true">29.</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">30.</strong> Kernel Configuration for BPF Features</a></li><li class="chapter-item expanded "><a href="../bcc-documents/reference_guide.html"><strong aria-hidden="true">31.</strong> bcc Reference Guide</a></li><li class="chapter-item expanded "><a href="../bcc-documents/special_filtering.html"><strong aria-hidden="true">32.</strong> Special Filtering</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial.html"><strong aria-hidden="true">33.</strong> bcc Tutorial</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial_bcc_python_developer.html"><strong aria-hidden="true">34.</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">
|
||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</button>
|
||
<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 <vmlinux.h>
|
||
#include <bpf/bpf_helpers.h>
|
||
#include <bpf/bpf_tracing.h>
|
||
#include "biopattern.h"
|
||
#include "maps.bpf.h"
|
||
#include "core_fixes.bpf.h"
|
||
|
||
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(".maps");
|
||
|
||
SEC("tracepoint/block/block_rq_complete")
|
||
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 && targ_dev != dev)
|
||
return 0;
|
||
|
||
counterp = bpf_map_lookup_or_try_init(&counters, &dev, &zero);
|
||
if (!counterp)
|
||
return 0;
|
||
if (counterp->last_sector) {
|
||
if (counterp->last_sector == sector)
|
||
__sync_fetch_and_add(&counterp->sequential, 1);
|
||
else
|
||
__sync_fetch_and_add(&counterp->random, 1);
|
||
__sync_fetch_and_add(&counterp->bytes, nr_sector * 512);
|
||
}
|
||
counterp->last_sector = sector + nr_sector;
|
||
return 0;
|
||
}
|
||
|
||
char LICENSE[] SEC("license") = "GPL";
|
||
</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(".maps");
|
||
</code></pre>
|
||
<p>这部分代码定义了一个 BPF map,类型为哈希表。该映射的键是设备的标识符,而值是一个 <code>counter</code> 结构体,用于存储设备的 I/O 统计信息。</p>
|
||
<p>追踪点函数:</p>
|
||
<pre><code class="language-c"> SEC("tracepoint/block/block_rq_complete")
|
||
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 && targ_dev != dev)
|
||
return 0;
|
||
|
||
counterp = bpf_map_lookup_or_try_init(&counters, &dev, &zero);
|
||
if (!counterp)
|
||
return 0;
|
||
if (counterp->last_sector) {
|
||
if (counterp->last_sector == sector)
|
||
__sync_fetch_and_add(&counterp->sequential, 1);
|
||
else
|
||
__sync_fetch_and_add(&counterp->random, 1);
|
||
__sync_fetch_and_add(&counterp->bytes, nr_sector * 512);
|
||
}
|
||
counterp->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->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, &lookup_key, &next_key)) {
|
||
err = bpf_map_lookup_elem(fd, &next_key, &counter);
|
||
if (err < 0) {
|
||
fprintf(stderr, "failed to lookup counters: %d\n", err);
|
||
return -1;
|
||
}
|
||
lookup_key = next_key;
|
||
total = counter.sequential + counter.random;
|
||
if (!total)
|
||
continue;
|
||
if (env.timestamp) {
|
||
time(&t);
|
||
tm = localtime(&t);
|
||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||
printf("%-9s ", ts);
|
||
}
|
||
partition = partitions__get_by_dev(partitions, next_key);
|
||
printf("%-7s %5ld %5ld %8d %10lld\n",
|
||
partition ? partition->name : "Unknown",
|
||
counter.random * 100L / total,
|
||
counter.sequential * 100L / total, total,
|
||
counter.bytes / 1024);
|
||
}
|
||
|
||
lookup_key = -1;
|
||
while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
|
||
err = bpf_map_delete_elem(fd, &next_key);
|
||
if (err < 0) {
|
||
fprintf(stderr, "failed to cleanup counters: %d\n", 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" 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" 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>
|