libbpf-tips

使用 libbpf 编写 BPF 应用程序进阶技巧

本文地址:https://www.ebpf.top/post/top_and_tricks_for_bpf_libbpf

原文地址:https://www.pingcap.com/blog/tips-and-tricks-for-writing-linux-bpf-applications-with-libbpf/

2020 年初,当使用 BCC 工具分析我们数据库性能瓶颈并从 GitHub 上拉取代码时,我意外地发现 BCC 项目中额外多出了一个 libbpf-tools目录。我学习了 BPF 可移植性和 BCC 到 libbpf 转换文章,并且根据所学知识将之前提交的 bcc-tools 转换为了 libbpf-tools。最后,我完成了近 20 个工具的转换工作(参见 为什么我们将 BCC-Tools 转换为 libbpf-tools 用于 BPF 性能分析)。

在此过程中,我有幸得到了 Andrii Nakryiko(libbpf + BPF CO-RE 项目的负责人)的大量帮助。这是一段有趣的经历,我也学到了很多。在本文中,我将分享我在使用 libbpf 编写 BPF 程序方面的经验。我希望本文能对 libbpf 感兴趣的人有所帮助,帮助他们进一步开发和完善使用 libbpf 的 BPF 应用程序。

不过在继续阅读之前,建议先阅读这些文章以获取重要的背景信息:

  • BPF Portability and CO-RE
  • HOWTO: BCC to libbpf conversion
  • Building BPF applications with libbpf-boostrap

本文假设你已经阅读了上述文章,因此这里不会有任何系统性的描述。相反,我会针对程序的某些细节部分提供对应的技巧。

程序框架(skeleton)

合并 open 和 loader 阶段

如编写的 BPF 代码不需要任何运行时调整(如调整 map 大小或设置额外配置),你可以调用 <name>__open_and_load() 将两个阶段合并,这会使我们的代码看起来更加简洁。例如:

obj = readahead_bpf__open_and_load();
if (!obj){fprintf(stderr, "failed to open and/or load BPF objectn");return 1;
}
err = readahead_bpf__attach(obj);

你可以在 readahead.c 中查看完整代码样例。【后续版本已经调整,原始提交参见 init readahead.c】

选择性附着 (attach)

默认情况下,<name>__attach() 会附加所有可自动 attach 的 BPF 程序。然而,有时你可能希望根据命令行参数选择性地 attach 对应的 BPF 程序。这种情况下,你可以选择主动调用 bpf_program__attach() 函数。例如:

err = biolatency_bpf__load(obj);
[...]
if (env.queued){obj->links.block_rq_insert =bpf_program__attach(obj->progs.block_rq_insert);err = libbpf_get_error(obj->links.block_rq_insert);[...]
}
obj->links.block_rq_issue =bpf_program__attach(obj->progs.block_rq_issue);
err = libbpf_get_error(obj->links.block_rq_issue);
[...]

你可以在 biolatency.c 看到完整的代码样例。【init biolatency.c 】

自定义 load 和 attach

框架适用于几乎所有情景,但有一种特殊情况:性能事件(perf events)。 这种情况下,你不需要使用 struct <name>__bpf 中的 link ,而是需要定义一个数组结构:struct bpf_link *links[]。这是因为 perf_event 需要在每个 CPU 上单独打开。

之后,你还需要自行 openattach perf_event

static int open_and_attach_perf_event(int freq, struct bpf_program *prog,struct bpf_link *links[])
{struct perf_event_attr attr = {.type = PERF_TYPE_SOFTWARE,.freq = 1,.sample_period = freq,.config = PERF_COUNT_SW_CPU_CLOCK,};int i, fd;for (i = 0; i < nr_cpus; i++){fd = syscall(__NR_perf_event_open, &attr, -1, i, -1, 0);if (fd < 0){fprintf(stderr, "failed to init perf sampling: %s\n",strerror(errno));return -1;}links[i] = bpf_program__attach_perf_event(prog, fd);if (libbpf_get_error(links[i])){fprintf(stderr, "failed to attach perf event on cpu: ""%d\n", i);links[i] = NULL;close(fd);return -1;}}return 0;
}

最后,在清理阶段,记得要销毁 links 中的每个 link,然后销毁 links 本身。

你可以在 runqlen.c 中看到完整的代码。

同一事件的多 BPF 处理程序

从 v0.2 开始,libbpf 支持在同一可执行文件和可链接格式(ELF)部分中有多个入口点 BPF 程序。因此,你可以将多个 BPF 程序附加到同一事件(例如 tracepoints 或 kprobes),而不必担心 ELF 部分名称冲突。有关详细信息,请参见 Add libbpf full support for BPF-to-BPF calls。现在,你可以自然地在类似下文事件中定义多个处理程序来处理:

SEC("tp_btf/irq_handler_entry")
int BPF_PROG(irq_handler_entry1, int irq, struct irqaction *action)
{[...]
}SEC("tp_btf/irq_handler_entry")
int BPF_PROG(irq_handler_entry2)
{[...]
}

你可以在 hardirqs.bpf.c 中看到完整的代码(代码基于 libbpf-bootstrap 构建)。【备注该文件已经不存在】

如果使用 libbpf 版本早于 v2.0,想要为一个事件定义多个处理程序,你必须使用多个程序类型,例如:

SEC("tracepoint/irq/irq_handler_entry")
int handle__irq_handler(struct trace_event_raw_irq_handler_entry *ctx)
{[...]
}SEC("tp_btf/irq_handler_entry")
int BPF_PROG(irq_handler_entry)
{[...]
}

你可以在 hardirqs.bpf.c 中看到完整的代码。

Map

减少预分配(pre-allocation)开销

【备注:https://github.com/iovisor/bcc/pull/4044 该参数会触发死锁,已经移除?

Using hash maps with BPF_F_NO_PREALLOC flag triggers a warning (0), and according
to kernel commit 94dacdbd5d2d,
this may cause deadlocks. Remove the flag from libbpf tools.】

从 Linux 4.6 开始,BPF hash maps 会默认执行内存预分配,并引入 BPF_F_NO_PREALLOC 标志。这样做的动机是为了避免 kprobe + bpf 死锁。社区尝试了其他解决方案,但最终,预分配所有 map 元素是最简单的解决方案,并且不影响用户空间的行为。

当完整的 map 预分配过于昂贵时,可使用 BPF_F_NO_PREALLOC 标志定义 map 以保持早期行为。详情请参阅 bpf: map pre-alloc。当 map 大小不大时(比如 MAX_ENTRIES = 256),这个标志是不必要的,因为 BPF_F_NO_PREALLOC 速度较慢。

以下是一个使用示例:

struct {__uint(type, BPF_MAP_TYPE_HASH);__uint(max_entries, MAX_ENTRIES);__type(key, u32);__type(value, u64);__uint(map_flags, BPF_F_NO_PREALLOC);
} start SEC(".maps");

你可以在 libbpf-tools 中看到更多的案例。

运行时确定 map 大小

libbpf-tools 的一个优点是可移植,因此 map 所需的最大空间可能因不同的机器而异。在这种情况下,你可以在加载之前定义 map 而不指定大小,然后运行时调整。例如:

<name>.bpf.c 中,定义 map :

struct {__uint(type, BPF_MAP_TYPE_HASH);__type(key, u32);__type(value, u64);
} start SEC(".maps");

open 阶段之后,调用 bpf_map__resize() 进行动态调整。例如:

struct cpudist_bpf *obj;[...]
obj = cpudist_bpf__open();
bpf_map__resize(obj->maps.start, pid_max);

你可以在 cpudist.c 中查看完整的代码。【最新代码已经通过 bpf_map__set_max_entries 来调整?】

Per-CPU

在选择 map 类型时,如果与同一 CPU 相关联并发生多个事件,则可以使用 per-CPU 数组来跟踪时间戳,这比使用 hash map 更加简单和高效。然而,你必须确保内核在两次 BPF 程序调用之间不会将进程从一个 CPU 迁移到另一个 CPU。因此,你并非总是能使用这个技巧。下面的示例分析了软中断,并且满足了这两个条件:

struct {__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);__uint(max_entries, 1);__type(key, u32);__type(value, u64);
} start SEC(".maps");SEC("tp_btf/softirq_entry")
int BPF_PROG(softirq_entry, unsigned int vec_nr)
{u64 ts = bpf_ktime_get_ns();u32 key = 0;bpf_map_update_elem(&start, &key, &ts, 0);return 0;
}SEC("tp_btf/softirq_exit")
int BPF_PROG(softirq_exit, unsigned int vec_nr)
{u32 key = 0;u64 *tsp;[...]tsp = bpf_map_lookup_elem(&start, &key);[...]
}

你可以在 softirqs.bpf.c 看到完整的代码。

全局变量

不仅可以使用全局变量来自定义 BPF 程序逻辑,你还可以使用它们来替代 map,这使程序更加简单和高效。全局变量可以是任意大小。你可设定全局变量为一个固定的大小。

例如,因为 SOFTIRQ 类型的数量是固定的,你可以在 softirq.bpf.c 中定义全局数组来保存计数和直方图:

__u64 counts[NR_SOFTIRQS] = {};
struct hist hists[NR_SOFTIRQS] = {};

然后,你可以直接在用户空间遍历这个数组:

static int print_count(struct softirqs_bpf__bss *bss)
{const char *units = env.nanoseconds ? "nsecs" : "usecs";__u64 count;__u32 vec;printf("%-16s %6s%5sn", "SOFTIRQ", "TOTAL_", units);for (vec = 0; vec < NR_SOFTIRQS; vec++){count = __atomic_exchange_n(&bss->counts[vec], 0,__ATOMIC_RELAXED);if (count > 0)printf("%-16s %11llun", vec_names[vec], count);}return 0;
}

你可以在 softirqs.c 看到完整的代码。

注意直接通过指针访问字段

正如你在 BPF 可移植性和 CO-RE 文章中所了解的一样,libbpf + BPF_PROG_TYPE_TRACING 的方法为 BPF 验证器提供了依据。验证器能够原生地理解和追踪 BTF,并允许你直接(而且安全地)跟踪指针并读取内核内存。例如:

u64 inode = task->mm->exe_file->f_inode->i_ino;

这使用起来非常酷。然而,当你在条件语句中使用这样的表达式时,,会由于分支被优化掉在某些内核版本中引入 bug 。在这种情况下,直到 bpf: fix an incorrect branch elimination by verifier 被广泛引入之前,请使用 BPF_CORE_READ 以确保内核兼容性。你可以在 biolatency.bpf.c 中找到一个示例:

SEC("tp_btf/block_rq_issue")
int BPF_PROG(block_rq_issue, struct request_queue *q, struct request *rq)
{if (targ_queued && BPF_CORE_READ(q, elevator))return 0;return trace_rq_start(rq);
}

你可以看到,即使它是一个 tp_btf 程序且 q->elevator 速度更快,我还是使用了 BPF_CORE_READ(q, elevator)

结论

本文介绍了使用 libbpf 编写 BPF 程序的一些技巧。你可以在 libbpf-tools 和 bpf中找到许多实际的示例。如果你有任何问题,欢迎加入 Slack 上的 TiDB 社区并向我们发送反馈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/626010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

numpy 筛选多段数据

目录 掩码方式 利用切片 掩码方式 range_to_remove list(range(77-1, 111-1)) list(range(122-1, 135-1))keep_mask np.ones(image0_cut.shape[0], dtypebool)keep_mask[range_to_remove] Falseprocessed_data image0_cut[keep_mask] 利用切片 import numpy as np# 假设…

基于Pytorch的身份证及其他证件检测矫正模型应用

前言 在做身份证和其他证件识别的时候&#xff0c;图片基本都不是摆正的状态&#xff0c;此时在进行OCR文字识别的提取文字信息的时候会出现很多误差&#xff0c;如何将证件摆正&#xff0c;再进行OCR文字识别就可以大大提高准确率。 准备工作 1、Python环境&#xff0c;在P…

tda7294功放电路图大全

简易电子管功放电路图&#xff08;一&#xff09; 6P3P单端A类电子管功放电路图 如图为6P3P单端A类电子管功放电路图。VT1、VT2直流通路串联。VT1构成普通的三极管共阴放大器&#xff0c;VTr2构成阴极输出器&#xff0c;对VT1而言VT2是一个带电流负反馈的高阻负载。音频信号由…

Leetcode2696. 删除子串后的字符串最小长度

Every day a Leetcode 题目来源&#xff1a;2696. 删除子串后的字符串最小长度 解法1&#xff1a;暴力 暴力做法是不断把 AB 和 CD 去掉&#xff0c;直到 s 中没有 AB 和 CD 为止。 代码&#xff1a; /** lc appleetcode.cn id2696 langcpp** [2696] 删除子串后的字符串最…

漏洞复现-金和OA jc6/servlet/Upload接口任意文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

关于浮点数的四舍五入问题

最近有关注到&#xff0c;在C/C中&#xff0c;对于浮点数的四舍五入&#xff0c;与实际的有一些出入&#xff0c;我打算今天总结一下&#xff0c;并解释一下这是为啥&#xff0c; 好了&#xff0c;下面进入正题&#xff0c;都是干货哦&#xff0c;认真看完&#xff0c;留下你的…

【大模型应用】小白借助chatgpt开发谷歌插件

大模型正缓慢地渗透进入我们的生活&#xff0c;尽管目前还没有现象级的产品应用&#xff0c;但它已足以让我痴迷于它&#xff0c;我对它能够提升程序员的生产效率笃定无疑。 本次我用一个下午做了一次尝试&#xff0c;使用大模型帮助我开发一个谷歌插件。开发之前&#xff0c;…

西米支付:到底什么是NFT(数字藏品支付通道)(NFT支付通道)

NFT到底指的是什么呢&#xff1f; 数字藏品的实际意义在于它们打破了传统艺术品的物质形态束缚。数字藏品可以通过虚拟现实和区块链技术进行创作、展示和交易。它们不仅可以满足人们对艺术品的审美需求&#xff0c;还可以成为一种投资和资产保值增值的方式。数字藏品的实际意义…

排序——归并排序

文章目录 基本思想递归版本思路代码实现 非递归版思路代码实现 特性结果演示 基本思想 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andConquer&#xff09;的一个非常典型的应用。将已有序的子…

开发实践6_缓存^中间件

以下学习 朔宁夫 开发工程师 课程。 缓存可提高程序响应速度。数据库缓存(可过期)/ Redis缓存(Key:Value)/ Memcacheed缓存/ 程序层缓存。 一 缓存 1. 数据库缓存 创建缓存数据表 // python manage.py createcachetable cache_table setting // # 缓存配置 CACHES {def…

第十部分 make 的运行

目录 一、make 的退出码 二、指定 Makefile 三、指定目标 “all” “clean” “install” “print” “dist” “TAGS” “check”和“test” 四、检查规则 五、make 的参数 一般来说&#xff0c;最简单的就是直接在命令行下输入 make 命令&#xff0c;make 命令会…

代码随想录算法训练营第20天(二叉树6 | 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

二叉树 part06 654.最大二叉树解题思路 617.合并二叉树解题思路 700.二叉搜索树中的搜索解题思路 98.验证二叉搜索树解题思路误区 654.最大二叉树 又是构造二叉树&#xff0c;昨天大家刚刚做完 中序后序确定二叉树&#xff0c;今天做这个 应该会容易一些&#xff0c; 先看视频&…

12.云原生之kubesphere中应用部署方式

云原生专栏大纲 文章目录 k8s中应用部署Kubernetes常用命令 kubesphere中可视化部署应用创建工作负载服务暴露 helm部署应用helm命令行部署应用kubesphere中使用应用仓库 k8s中应用部署 在k8s中要想部署应用&#xff0c;需要编写各种yaml文件&#xff0c;一旦应用依赖比较复杂…

不同整数的最少数目和单词直接最短距离

写是为了更好的思考&#xff0c;坚持写作&#xff0c;力争更好的思考。 今天分享两个关于“最小、最短”的算法题&#xff0c;废话少说&#xff0c;show me your code&#xff01; 一、不同整数的最少数目 给你一个整数数组arr和一个整数k。现需要从数组中恰好移除k个元素&…

蓝桥杯备赛 | 洛谷做题打卡day2

​ 蓝桥杯备赛 | 洛谷做题打卡day2 嵌套循环yyds&#xff01;&#xff01; 题目来源&#xff1a;洛谷P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n n n 行 m m m 列的雷区中有一些格子含有地雷&am…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性&#xff0c;包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型&#xff1a;选择适合你的直播电商系统的技术栈。考虑前端框架&#xff08;如React、Vue.js&#xff09;、后…

ardupilot开发 --- 固件定制(OEM) 篇

0. 前言 固件功能定制OEM Customization&#xff1a; 原厂设备制造商OEM&#xff08;Original Equipment Manufacturer&#xff09;、代工功能勾选参数预设固件名称自定义 1. 基于某个飞控硬件来定制自己的飞控产品 可以自定义的包括&#xff1a;固件名称、预设参数、lua脚本…

C语言:编译和链接

目录 一&#xff1a;翻译环境和运行环境 二&#xff1a;翻译环境 2.1预处理&#xff08;预编译&#xff09; 2.2编译 2.2.1 词法分析&#xff1a; 2.2.2语法分析 2.2.3语义分析 2.3 汇编 三&#xff1a;运行环境 一&#xff1a;翻译环境和运行环境 在ANSI C的任何一种…

【go语言】读取toml文件

一、简介 TOML&#xff0c;全称为Toms Obvious, Minimal Language&#xff0c;是一种易读的配置文件格式&#xff0c;旨在成为一个极简的数据序列化语言。TOML的设计原则之一是保持简洁性&#xff0c;易读性&#xff0c;同时提供足够的灵活性以满足各种应用场景。 TOML文件由…

力扣每日一练(24-1-16)

我一开始想到的是&#xff0c;如果数字相同则加一。 然而&#xff0c;对了一点点&#xff0c;而已。 高手的方法不是普通人在几分钟内能想得出来的&#xff0c;hh 继续补充&#xff1a; 如果数字不同则减一&#xff0c;如果计数到达了0&#xff0c;则更新数字&#xff0c;最…