当前位置: 首页 > 科技观察

透视Linux内核,BPF神奇Linux技术介绍

时间:2023-03-22 11:59:17 科技观察

1.前言作为一名coder,你时不时会遇到性能问题。有想进入电脑看看是怎么回事的矛盾;而在优化性能的时候,不知道整个系统的短板是什么,如何优化呢?根本原因其实是对系统内核的了解不够,导致自己虽然有解决问题的热情和动力,但总是难以找到重点,犹豫不决,找不到门路。我被要求学习内核,但我退缩了。感觉难度还是太大了。有没有办法在不深入了解系统内核的情况下观察内核的行为呢?这个时候我发现了BPF和eBPF,通过它我有了一个视角。kernel的能力,让我开始了BPF的学习之旅。2.什么是BPF?BPF最初是BerkelyPacketFilter(伯克利数据包过滤器)的缩写。最初是为了提高pcap过滤的性能。它比当时最快的数据包过滤技术快20倍。其高性能的原因是因为它有效。在内核中,避免了将包从内核态拷贝到用户态,所以速度快。后来,AlexeiStarovoitovDaniel在2014年重新实现了BPF,并将其扩展为一个名为eBPF的通用执行引擎。官方缩写仍然是BPF。简单说明一下BPF的作用。BPF提供了在内核或特定于应用程序的事件发生时执行一段代码的能力。BPF采用了虚拟机指令规范,所以我们也可以看一个虚拟机的实现,这样就可以提供一种不用修改内核源码重新编译就可以扩展内核能力的方法。3、BPF能做什么?BPF程序不像普通程序那样可以独立运行。它们是被动运行的,需要被事件触发才能运行。有点类似于js中的监控。单击按钮时,将执行一小段代码。这些事件包括系统调用、内核跟踪、内核函数、用户函数、网络事件等,它具体能做什么呢?功能还是很强大的。它可以诊断系统故障,因为它具有看穿内核的能力;优化网络性能,因为它可以在内核态接收网络数据包,修改并转发它们;系统安全,因为它可以中断非法连接等;性能监控,因为它的透视能力,可以查看函数所花费的时间,这样我们就可以知道问题出在哪里。如下图所示:4.BPF是如何工作的经典的BPF工作模式是用户使用BPF虚拟机的指令集定义过滤表达式,传递给内核,由解释器运行,这样包过滤器就可以直接工作在内核态,避免了在用户态需要Copy数据来提高性能。例如tcpdump的BPF过滤命令示例如下:[root@localhost~]#tcpdump-dport80(000)ldh[12](001)jeq#0x86ddjt2jf10(002)ldb[20](003)jeq#0x84jt6jf4(004)jeq#0x6jt6jf5(005)jeq#0x11jt6jf23(006)ldh[54](007)jeq#0x50jt22jf8(008)ldh[56](009)jeq#0x50jt22jf23(010)jeq#0x800jt11jf23(011)ldb[23](012)jeq#0x84jt15jf13(013)jeq#0x6jt15jf14(014)jeq#0x11jt15jf23(015)ldh[20](016)jset#0x1fffjt23jf17(017)ldxb4*([14]&0xf)(018)ldh[x+14](019)jeq#0x50jt22jf20(020)ldh[x+16](021)jeq#0x50jt22jf23(022)ret#262144(023)ret#0执行过程如下:大牛埃里克杜马Zet在2011年7月发布的Linux3.0中加入了JIT(即时编译),性能比解释和执行更快。它更像是一个java虚拟机,既可以解释执行也可以即时编译。现在BPF的执行流程如下:编写eBPF代码;通过LLVM将eBPF代码转化为字节码;通过bpf系统调用提交给系统内核;内核通过验证器对代码进行安全验证(包括无界循环检查);只有验证通过的字节码才会提交给JIT编译成可以直接执行的机器指令;当事件发生时,调用这些指令执行,结果保存在map中;用户程序通过map获取执行结果。5.BPF和内核模块比较BPF程序会进行安全检查,内核模块可能会引入bug。BPF程序不能随意调用内核函数,只能调用一些辅助函数。BPF的栈空间最大为512字节,无法扩展,只能借助map存储。一个BPF程序可以一次编译到处运行,因为它所依赖的辅助函数、映射表、BPF指令集都是稳定的API。6.编写BPF程序6.1准备知识开发BPF指令显然不适合直接用BPF指令开发,所以大牛们开发了一些前端工具方便我们开发。比如我们可以用C写BPF程序,然后用LLVM编译成BPF。当然还是有负担,又是BCC和bpftrace。BCC是BPFCompilerCollection,它提供了开发BPF跟踪程序的高级框架,编写内核BPF程序的C语言环境,以及pyhton等多种高级语言的接口。同时BCC提供了很多BPF工具,方便我们进行性能分析和故障分析。开发BPF程序之前可以看一下。bpftrace比较适合写单行程序或者短脚本,BCC适合写复杂的脚本作为后台进程使用。libbcc和libbpf为两者提供低级支持。BPF编程可以借助BCC开发的动态跟踪工具集来编写6.2环境准备我的测试环境是centos8.5版本,内核版本是4.18,需要先升级BPF版本5.x内核。[root@localhost~]#cat/etc/centos-releaseCentOSLinuxrelease8.5.2111[root@localhost~]#uname-aLinuxlocalhost.localdomain4.18.0-348.7.1.el8_5.x86_64#1SMPWedDec2213:25:12UTC2021x86_64x86_64x86_64GNU/Linux内核升级步骤:#1。进入[https://www.kernel.org/](https://www.kernel.org/)查看稳定内核版本为5.16.10#2。下载编译wgethttps://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.10.tar.xztarxvflinux-5.16.10.tar.xzcdlinux-5.16.10/uname-acp/boot/config-4.18.0-348.7.1.el8_5.x86_64.config#注释掉CONFIG_SYSTEM_TRUSTED_KEYSmakemenuconfig#进入界面按tab键选择Load加载.config,保存后按原配置编译#compilekernelcoremake-j4makemodules_install#Installkernelcoremakeinstallgrub2-set-default0#0表示/boot/grub2/grub.cfg文件中的第一个menuentry段rebootmakemodules_preparemakescriptmakeheaders_installINSTALL_HDR_PATH=/usr/include#InstallbpfexamplemakeM=samples/bpf安装BPF相关库和工具:yuminstalllibbpf-develmakeclangllvmelfutils-libelf-develbpftoolbcc-toolsbcc-devellllvm:将eBPF程序编译成字节码工具。c代码构建工具make。eBPF工具集BCC及其依赖的头文件。libelf库和ebpf管理工具ebpftool。用户程序通过BPF映射查询BPF字节码的字节码执行结果。6.3依赖BCC开发BPFhelloworld的步骤如下:用C语言开发一个eBPF程序;用LLVM将eBPF程序编译成BPF字节码;通过bpf系统调用将BPF字节码提交给内核;内核验证并运行BPF字代码,并在BPF映射中保存相应的状态;用户程序通过BPF映射查询BPF字节码,得到执行结果;这个过程一般比较繁琐,可以通过使用BCC用python脚本加载BPF程序编译成字符段代码,通过系统调用运行BPF字节码来简化;6.3.1用C开发一个eBPF程序inthello(void*ctx){bpf_trace_printk("Hello,World!");return0;}bpf_trace_printk是一个常用的BPF辅助函数,它只是打印一个字符串;但是eBPF输出的是内核调试文件:/sys/kernel/debug/tracing/trace_pipe6.3.2使用python和BCC开发BPF加载器#!/usr/bin/envpython3#1)在BCC中导入BPF模块libraryfrombccimportBPF#2)加载C程序开发的BPF程序b=BPF(src_file="hello.c")#3)将BPF程序挂载到kernelprobe,其中do_sys_openat2是系统调用openat实现的内核b.attach_kprobe(event="do_sys_openat2",fn_name="hello_world")#4)读取并打印/sys/kernel/debug/tracing/trace_pipeb.trace_print()运行查看:>python3hello.pyb'pmdalinux-1298[007]d..316758.674383:bpf_trace_printk:你好,世界!'b'pmdalinux-1298[007]d..316758.674395:bpf_trace_printk:你好,世界!'b'pmdalinux-1298[007]d..316758.674410:bpf_trace_printk:你好,世界!'b'pmdalinux-1298[007]d..316758.674422:bpf_trace_printk:你好,世界!'b'pmdalinux-1298[007]d..316758.674426:bpf_trace_printk:你好,世界!'b'python3-73326[001]d..316758.674859:bpf_trace_printk:你好,世界!'b'irqbalance-942[006]d..316758.894331:bpf_trace_printk:你好,世界!'b'irqbalance-942[006]]d..316758.894593:bpf_trace_printk:Hello,World!'问题解决问题1:编译时磁盘空间已满关注[https://blog.csdn.net/xionglangs/article/details/108866146】扩容磁盘;(https://blog.csdn.net/xionglangs/article/details/108866146)问题二:make-j4编译报错BTF:.tmp_vmlinux.btf:pahole(pahole)isnotavailableFailedtogenerateBTFforvmlinuxTrytodisableCONFIG_DEBUG_INFO_BTFmake:***[Makefile:1106:vmlinux]错误1解决方法:在.config中注释掉CONFIG_DEBUG_INFO_BTF或者yuminstalldwarves问题3:编译时需要支持bpf编译内核,bpf编译选项开启,添加或修改.config文件中编译内核中开启bpf编译选项,在.config文件中添加或修改CONFIG_CGROUP_BPF=yCONFIG_BPF=yCONFIG_BPF_SYSCALL=yCONFIG_NET_SCH_INGRESS=mCONFIG_NET_CLS_BPF=mCONFIG_NET_CLS_ACT=yCONFIG_BPF_JIT=yCONFIG_LWTUNNEL_BPF=yCONFIG_HAVE_EBPF_JIT=yCONFIG_BPF_EVENTS=yCONFIG_TEST_BPF=m问题四:makeM=samples/bpf报错/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63:错误:未知类型名称'__u32'staticlong(*bpf_tail_call)(void*ctx,void*prog_array_map,__u32index)=(void*)12;^/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58:错误:未知类型名称'__u32'staticlong(*bpf_clone_redirect)(struct__sk_buff*skb,__u32ifindex,__u64flags)=(void*)13;^致命错误:发出的错误太多,现在停止[-ferror-limit=]1。makeM=samples/bpf报错/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63:错误:未知类型名称'__u32'staticlong(*bpf_tail_call)(void*ctx,void*prog_array_map,__u32index)=(void*)12;^/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58:错误:未知类型名称'__u32'staticlong(*bpf_clone_redirect)(struct__sk_buff*skb,__u32ifindex,__u64flags)=(void*)13;^fatalerror:toomanyerrorsemitted,stoppingnow[-ferror-limit=]解决方法:vim/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h添加头文件:#include#include问题五:无法从/root/core/linux-5.16.10/vmlinux加载BTF:NosuchfileordirectoryError:无法从/root/core/linux-5.16.10/vmlinux加载BTF:没有那个文件或目录make[2]:***[Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h]错误2make[1]:***[samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool]错误2make:***[Makefile:1846:samples/bpf]Error2[root@localhostlinux-5.16.10]#更改.config配置:CONFIG_DEBUG_INFO_BTF=ymake-j4问题六:fatalerror:'gnu/stubs-32.h'找不到文件make[2]:***[Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h]error2make[1]:***[samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool]Error2make:***[Makefile:1846:samples/bpf]Error2参考详细BPF程序编译过程生成字节码:https://www.cnblogs.com/lfri/p/15402973.htmlhttps://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)