透视Linux内核,BPF神奇的Linux技术入门
itomcoil 2025-01-03 16:31 46 浏览
一 前言
作为一个coder,时不时会遇到性能问题,有时候明明看资源,cpu,io都占用不高,程序的性能就是上不去,真有一种想进入到计算机里面看看到底发生什么的冲突;还有优化性能的时候不知道整个系统的短板到底是哪一块,如何去优化它?
根本原因其实是对系统的内核不够了解,导致虽然有解决问题的激情和动力,但是总是难找到关键点,彷徨而不得其门。让我学习内核,却又望而退步,觉得难度还是太大,有没有不用深入了解系统内核,但是又能深入观察内核行为的办法那,这时候我发现了BPF和eBPF,通过它有了透视内核的能力,所以就开始了BPF学习之旅。
二 BPF是个什么
BPF原来是Berkely Packet Filter(伯克利数据包过滤器)的缩写,原来是提升pcap过滤性能的,比当时最快的包过滤技术快20倍,只所以性能高,是因为它工作在内核中,避免包从内核态复制到用户态所以速度快,后来Alexei Starovoitov 大牛在2014年重新实现了BPF,将其扩展成了通用的执行引擎,称为eBPF,官方缩写仍是BPF。
简单解释BPF作用,BPF提供了一种当内核或应用特定事件发生时候,执行一段代码的能力。BPF 采用了虚拟机指令规范,所以也可以看一种虚拟机实现,使我们可以在不修改内核源码和重新编译的情况下,提供一种扩展内核的能力的方法。
三 BPF能干嘛
BPF程序不像一般程序可以独立运行,它是被动运行的,需要事件触发才能运行,有点类似js里面的监听,监听到按钮点击执行一小段代码。这些事件包括系统调用,内核跟踪,内核函数,用户函数,网络事件等。
具体能干嘛那,作用还是很强大,可以进行系统故障诊断,因为其有透视内核的能力;网络性能优化,因为它可以在内核态接收网络包,并做修改和转发;系统安全,因为它可以中断非法连接等;性能监控,因为其透视能力,可以查看函数耗费时间从而我们可以知道问题到底出在哪里。 如下图:
四 BPF如何工作
经典的BPF的工作模式是用户使用BPF虚拟机的指令集定义过滤表达式,传递给内核,由解释器运行,使得包过滤器可以直接在内核态工作,避免向用户态复制数据,从而提升性能,比如tcpdump的BPF过滤指令实例如下:
[root@localhost ~]# tcpdump -d port 80
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 10
(002) ldb [20]
(003) jeq #0x84 jt 6 jf 4
(004) jeq #0x6 jt 6 jf 5
(005) jeq #0x11 jt 6 jf 23
(006) ldh [54]
(007) jeq #0x50 jt 22 jf 8
(008) ldh [56]
(009) jeq #0x50 jt 22 jf 23
(010) jeq #0x800 jt 11 jf 23
(011) ldb [23]
(012) jeq #0x84 jt 15 jf 13
(013) jeq #0x6 jt 15 jf 14
(014) jeq #0x11 jt 15 jf 23
(015) ldh [20]
(016) jset #0x1fff jt 23 jf 17
(017) ldxb 4*([14]&0xf)
(018) ldh [x + 14]
(019) jeq #0x50 jt 22 jf 20
(020) ldh [x + 16]
(021) jeq #0x50 jt 22 jf 23
(022) ret #262144
(023) ret #0
执行过程如下:
后来又一位大牛EricDumazet在2011年7月发布的Linux 3.0中增加了JIT(即时编译),性能比解释执行更快,多像java的虚拟机,可以解释执行也可以即时编译执行。
现在BPF的执行过程如下示意图:
- 编写eBPF 代码。
- 将eBPF代码通过LLVM把编写的eBPF代码转成字节码;
- 通过bpf系统调用提交给系统内核;
- 内核通过验证器对代码做安全性验证(包括对无界循环的检查);
- 只有校验通过的字节码才会提交到JIT进行编译成可以直接执行的机器指令;
- 当事件发生时候,调用这些指令执行,将结果保存到map中。
- 用户程序通过映射来获取执行结果。
四 BPF 和内核模块对比
- BPF程序会进行安全检查,内核模块可能会引入Bug。
- BPF程序不能随意调用内核函数,只能调用部分辅助函数。
- BPF的栈空间最大为512个字节,不能扩大,只能借助map存储;
- BPF程序可以一次编译到处运行,因为它依赖的辅助函数,映射表,BPF指令集属于稳定的API。
五 编写BPF程序
5.1 准备知识
开发BPF指令显然不适合直接用BPF指令开发,所以大牛们开发了一些前端工具让我们可以更方便的开发,比如我们可以通过C来编写BPF程序,然后通过LLVM编译成BPF。
当然还是负载,又有了BCC和bpftrace。BCC即BPF Compiler Collection,提供了开发BPF跟踪程序的高级框架,提供编写内核BPF程序的C语言环境,同时提供了许多高级语言的接口,比如pyhton等。同时BCC中提供了很多BPF工具,让我们可以方便使用用于性能分析和故障分析,在开发BPF程序之前可以看看。
bpftrace编写单行程序或短小脚本更加适合,BCC适合编写复杂的脚本和作为后台进程使用。libbcc和libbpf为两者提供底层支持。
BPF程序编写可以借助工具
BCC开发的动态追踪工具集
5.2 环境准备
我的测试环境是centos8.5版本,内核版本为4.18,而BPF最好用5.x版本的内核需要先升级下。
[root@localhost ~]# cat /etc/centos-release
CentOS Linux release 8.5.2111
[root@localhost ~]# uname -a
Linux localhost.localdomain 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
内核升级步骤:
#1. 到[https://www.kernel.org/](https://www.kernel.org/)查看稳定的内核版本为5.16.10
#2. 下载编译
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.10.tar.xz
tar xvf linux-5.16.10.tar.xz
cd linux-5.16.10/
uname -a
cp /boot/config-4.18.0-348.7.1.el8_5.x86_64 .config
#注释掉CONFIG_SYSTEM_TRUSTED_KEYS
make menuconfig
#进入界面按tab 选择Load 加载.config ,在Save后即可用原来配置编译
#编译内核核心
make -j 4
make modules_install
#安装内核核心
make install
grub2-set-default 0 #0表示 /boot/grub2/grub.cfg 文件中排在第一位的 menuentry 段
reboot
make modules_prepare
make script
make headers_install INSTALL_HDR_PATH=/usr/include
#安装bpf 实例
make M=samples/bpf
安装BPF相关库和工具:
yum install libbpf-devel make clang llvm elfutils-libelf-devel bpftool bcc-tools bcc-devel
- llvm : 将eBPF程序编译成字节码工具。
- c代码构建工具make
- eBPF工具集BCC和它依赖的头文件。
- libelf库以及ebpf管理工具ebpftool。
- 用户程序通过BPF映射查询到BPF字节码的字节码运行结果。
5.3 依赖BCC开发BPF的helloworld
步骤如下:
- 用C语言开发一个eBPF程序;
- 用LLVM把eBPF程序编译成BPF字节码;
- 通过bpf系统调用,把BPF字节码提交给内核;
- 内核验证并运行BPF字节码,并把相应状态保存到BPF映射中;
- 用户程序通过 BPF 映射查询 BPF 字节码,得到执行结果;
这个流程一般比较麻烦,可以利用BCC来简化,用python脚本加载BPF程序,编译为字节码,并通过系统调用将BPF字节码,运行BPF字节码;
5.3.1 用C开发一个eBPF程序
int hello(void *ctx)
{
bpf_trace_printk("Hello, World!");
return 0;
}
bpf_trace_printk 是常用的BPF辅助函数,它就是简单的打印一个字符串;不过eBPF输出是内核调试文件: /sys/kernel/debug/tracing/trace_pipe
5.3.2 使用python和BCC开发BPF的加载程序
#!/usr/bin/env python3
# 1) 导入BCC库中的BPF模块
from bcc import BPF
# 2) 加载C程序开发的BPF程序
b = BPF(src_file="hello.c")
# 3) 将此BPF程序挂载到内核探针,其中do_sys_openat2是系统调用openat 在内核实现
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")
# 4) 读取和打印 /sys/kernel/debug/tracing/trace_pipe
b.trace_print()
运行查看:
> python3 hello.py
b' pmdalinux-1298 [007] d..31 6758.674383: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674395: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674410: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674422: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674426: bpf_trace_printk: Hello, World!'
b' python3-73326 [001] d..31 6758.674859: bpf_trace_printk: Hello, World!'
b' irqbalance-942 [006] d..31 6758.894331: bpf_trace_printk: Hello, World!'
b' irqbalance-942 [006] d..31 6758.894593: bpf_trace_printk: Hello, World!'
问题解决
问题一 编译过程磁盘空间满了
按照[https://blog.csdn.net/xionglangs/article/details/108866146]扩展磁盘;(https://blog.csdn.net/xionglangs/article/details/108866146)
问题二 make -j4 编译报错
BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
Failed to generate BTF for vmlinux
Try to disable CONFIG_DEBUG_INFO_BTF
make: *** [Makefile:1106: vmlinux] Error 1
解决办法: 注释掉.config中的CONFIG_DEBUG_INFO_BTF 或 yum install dwarves
问题三 编译需要支持bpf
编译内核的时候bpf的编译选项打开,在.config文件中添加或修改
CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_SCH_INGRESS=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_CLS_ACT=y
CONFIG_BPF_JIT=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_TEST_BPF=m
问题四 make M=samples/bpf报错
1. make M=samples/bpf报错
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
^
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
^
fatal error: too many errors emitted, stopping now [-ferror-limit=]1. make M=samples/bpf报错
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
^
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
解决办法:
vim /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h
添加头文件:
#include <asm/types.h>
#include <linux/types.h>
问题五 failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory
Error: failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory
make[2]: *** [Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h] 错误 2
make[1]: *** [samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool] 错误 2
make: *** [Makefile:1846:samples/bpf] 错误 2
[root@localhost linux-5.16.10]#
更改.config 配置:
CONFIG_DEBUG_INFO_BTF=y
make -j4
问题六 fatal error: 'gnu/stubs-32.h' file not found
升级:
yum install glibc-devel
yum install glibc-devel.i686
参考
[详细介绍了BPF程序编译生成字节码过程](https://www.cnblogs.com/lfri/p/15402973.html)
[https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM](https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM)
[技术|深入理解 BPF:一个阅读清单 (linux.cn)](https://linux.cn/article-9507-1.html)
相关推荐
- selenium(WEB自动化工具)
-
定义解释Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7,8,9,10,11),MozillaF...
- 开发利器丨如何使用ELK设计微服务中的日志收集方案?
-
【摘要】微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等。我们将重点介绍微服务架构中...
- 高并发系统设计:应对每秒数万QPS的架构策略
-
当面试官问及"如何应对每秒几万QPS(QueriesPerSecond)"时,大概率是想知道你对高并发系统设计的理解有多少。本文将深入探讨从基础设施到应用层面的解决方案。01、理解...
- 2025 年每个 JavaScript 开发者都应该了解的功能
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.Iteratorhelpers开发者...
- JavaScript Array 对象
-
Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...
- Gemini 2.5编程全球霸榜,谷歌重回AI王座,神秘模型曝光,奥特曼迎战
-
刚刚,Gemini2.5Pro编程登顶,6美元性价比碾压Claude3.7Sonnet。不仅如此,谷歌还暗藏着更强的编程模型Dragontail,这次是要彻底翻盘了。谷歌,彻底打了一场漂亮的翻...
- 动力节点最新JavaScript教程(高级篇),深入学习JavaScript
-
JavaScript是一种运行在浏览器中的解释型编程语言,它的解释器被称为JavaScript引擎,是浏览器的一部分,JavaScript广泛用于浏览器客户端编程,通常JavaScript脚本是通过嵌...
- 一文看懂Kiro,其 Spec工作流秒杀Cursor,可移植至Claude Code
-
当Cursor的“即兴编程”开始拖累项目质量,AWS新晋IDEKiro以Spec工作流打出“先规范后编码”的系统工程思维:需求-设计-任务三件套一次生成,文档与代码同步落地,复杂项目不...
- 「晚安·好梦」努力只能及格,拼命才能优秀
-
欢迎光临,浏览之前点击上面的音乐放松一下心情吧!喜欢的话给小编一个关注呀!Effortscanonlypass,anddesperatelycanbeexcellent.努力只能及格...
- JavaScript 中 some 与 every 方法的区别是什么?
-
大家好,很高兴又见面了,我是姜茶的编程笔记,我们一起学习前端相关领域技术,共同进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力在JavaScript中,Array.protot...
- 10个高效的Python爬虫框架,你用过几个?
-
小型爬虫需求,requests库+bs4库就能解决;大型爬虫数据,尤其涉及异步抓取、内容管理及后续扩展等功能时,就需要用到爬虫框架了。下面介绍了10个爬虫框架,大家可以学习使用!1.Scrapysc...
- 12个高效的Python爬虫框架,你用过几个?
-
实现爬虫技术的编程环境有很多种,Java、Python、C++等都可以用来爬虫。但很多人选择Python来写爬虫,为什么呢?因为Python确实很适合做爬虫,丰富的第三方库十分强大,简单几行代码便可实...
- pip3 install pyspider报错问题解决
-
运行如下命令报错:>>>pip3installpyspider观察上面的报错问题,需要安装pycurl。是到这个网址:http://www.lfd.uci.edu/~gohlke...
- PySpider框架的使用
-
PysiderPysider是一个国人用Python编写的、带有强大的WebUI的网络爬虫系统,它支持多种数据库、任务监控、项目管理、结果查看、URL去重等强大的功能。安装pip3inst...
- 「机器学习」神经网络的激活函数、并通过python实现激活函数
-
神经网络的激活函数、并通过python实现whatis激活函数感知机的网络结构如下:左图中,偏置b没有被画出来,如果要表示出b,可以像右图那样做。用数学式来表示感知机:上面这个数学式子可以被改写:...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)