简介: 干货必备!何为享受式开发?编者按:BPF 技术还在热火朝天的倒退着,本文先通过对 BPF 常识的介绍,率领大家入门 BPF,而后介绍 coolbpf 的近程编译(原名 LCC,LibbpfCompilerCollection),意为酷玩 BPF,指标把简单的 BPF 开发和编译过程简化,本文还会以一个示例来体验 coolbpf 的享受式开发。本文整顿自龙蜥大讲堂第 20 期,精彩分享视频回放已上传至龙蜥官网(首页-动静-视频),欢送查看!
一、 引言要实现一个 BPF 二进制程序的开发,须要搭建开发编译环境,要关注指标零碎的内核版本状况,须要把握从 BPF 内核态到用户态程序的编写,以及如何加载、绑定至对应的 HOOK 点期待事件触发,最初再对输入的日志及数据进行解决。这几个过程对于一个刚接触 BPF 的同学会感觉相当繁琐。本文先通过对 BPF 常识的介绍,率领大家入门BPF,而后介绍 coolbpf 的近程编译(原名 LCC,LibbpfCompilerCollection, 意为酷玩 BPF,指标把简单的 BPF 开发和编译过程简化),以一个示例来体验 coolbpf 的享受式开发。二、 BPF入门BPF 最早在 1992 年被提出,过后叫伯克利包过滤器(Berkely Packet Filter,个别称为cBPF),号称比过后最先进的数据包过滤技术快 20 倍,次要利用场景在 tcpdump、seccomp。2014年,Alexei Starovoitov 对 BPF 进行彻底地革新,提出 Extended Berkeley Packet Filter (eBPF)。eBPF 指令更靠近硬件的 ISA,便于晋升性能,提供了可基于零碎或程序事件高效平安执行特定代码的通用能力,通用能力的使用者不再局限于内核开发者。其应用场景不再仅仅是网络分析,能够基于 eBPF 开发性能剖析、零碎追踪、网络优化等多种类型的工具和平台。咱们通常不加区分,把 cBPF 和 eBPF 都称之为 BPF。2.1 BPF 知识点总结大家都在谈 BPF,介绍BPF的文章也很多,这里先总结 BPF 的知识点,最初咱们从最根底的 BPF 指令架构及 map 和 prog type 去介绍如何疾速入门。risc指令集:蕴含 11 个 64 位寄存器 (R0 ~ R10)。maps:BPF 程序之间或内核及用户态间数据交互。prog type:BPF 程序类型用来确定程序性能以及程序 attach 到什么地位。helper functions:通过辅助函数拜访内核数据,如拜访 task、pid 等。jit:将 BPF 程序的字节码转换成指标机的机器码。object pinning:提供 BPF 文件系统,缩短 map 和 prog 的生命周期。tail call:一个BPF程序能够调用另一个 BPF 程序,并且调用实现后不必返回到原来的程序。hardening:爱护 BPF 程序和其二进制程序不被毁坏(设置成只读)。2.2 BPF 倒退详情BPF 通过几十年的倒退,曾经从原来繁多的性能用处,到现在遍布各个领域的利用,包含云原生、企业服务器、平安零碎等都在使用这一技术,服务生产和生存。下图是 BPF 技术的发展史(图源:https://zhuanlan.zhihu.com/p/...)。
随着越来越多的个性被合入到 Linux 内核社区,BPF 反对的性能因为这些个性加持,曾经越来越丰盛。
2.3 BPF 和内核模块比照BPF 的劣势是灵便、平安。在没有引入 BPF 之前,如果咱们想要实现一些跟踪诊断的性能,能够应用 tracefs/debugfs 中导出的文件系统接口,或者编写内核模块插入到内核中运行。前者的性能较为固定,无奈灵便地实现对数据的过滤;后者尽管灵便,但容易导致内核 crash,不够平安。ftrace 和 kernel module 给咱们很大的自在去抓取内核的一些信息,然而却存在不少弊病。下图是内核模块和 BPF 的比照(图源:https://zhuanlan.zhihu.com/p/...)。
2.4 BPF 指令集BPF(默认指 eBPF 非 cBPF) 程序指令都是 64 位,应用了 11 个 64 位寄存器,32 位称为半寄存器(subregister)和一个程序计数器(program counter),一个大小为 512 字节的 BPF 栈。所有的 BPF 指令都有着雷同的编码方式。eBPF虚构指令系统属于 RISC,领有 11 个虚构寄存器、r0-r10,在理论运行时,虚构机会把这 11 个寄存器一 一对应于硬件 CPU 的物理寄存器。下图是新老指令的比照:
BPF 指令的外围构造体如下,每一条 eBPF 指令都以一个 bpf_insn 来示意,在 cBPF 中是其余的一个构造体(struct sock_filter ),不过最终都会转换成对立的格局,这里咱们只钻研 eBPF:
由构造体中的__u8 code 能够晓得,一条 BPF 指令是 8 个字节长。这 8 位的 code,第 0、1、2 位示意的是该操作指令的类别,共 8 种:
从最低位到最高位别离是:8 位的 opcode;有 BPF_X 类型的基于寄存器的指令,也有 BPF_K 类型的基于立刻数的指令4 位的指标寄存器 (dst)4 位的原始寄存器 (src)16 位的偏移(有符号),是绝对于栈、映射值(map values)、数据包(packet data)等的绝对偏移量32 位的立刻数 (imm)(有符号)
8 bit 的 opcode 进一步拆开,下图示意的是存储和加载类指令:
下图示意的是运算和跳转指令:
总之,BPF 指令很简洁,咱们未必会在开发过程中应用它来进行代码编写(相似于纯汇编,会让人解体),理解这些是有助于咱们更粗浅的了解 BPF 的运行原理。2.5 BPF 的 prog type 和 mapPROG TYPEBPF 相干的程序,首先须要设置为绝对应的的程序类型,截止 Linux 内核 5.8 程序类型定义有 29 个,而且还在继续减少中,BPF 程序类型(prog_type)决定了程序能够调用的内核辅助函数的子集,也决定了程序输出上下文 -- bpf_context 构造的格局。咱们常常应用BPF程序类型次要波及以下两类:跟踪大部BPF程序都是这一类,次要通过kprobe、tracepoint(rawtracepoint)等追踪零碎行为及获取零碎硬件信息。也能够拜访特定程序的内存区域,从运行过程中提取执行跟踪信息。网络这类BPF程序用于检测和控制系统的网络流量。能够对网络接口数据包进行过滤,甚至能够齐全回绝数据包。用户态是通过零碎调用来加载BPF程序到内核的,在加载程序时,须要传递的参数中有一个字段叫prog_type,这个就是BPF的程序类型,跟踪相干的是:BPF_PROG_TYPE_KPROBE和BPF_PROG_TYPE_TRACEPOINT,网络相干是:BPF_PROG_TYPE_SK_SKB、BPF_PROG_TYPE_SOCK_OPS等。上面是形容BPF程序类型的枚举构造:enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC, / Reserve 0 as invalid program type /
BPF_PROG_TYPE_SOCKET_FILTER,
BPF_PROG_TYPE_KPROBE,
BPF_PROG_TYPE_SCHED_CLS,
BPF_PROG_TYPE_SCHED_ACT,
BPF_PROG_TYPE_TRACEPOINT,
BPF_PROG_TYPE_XDP,
BPF_PROG_TYPE_PERF_EVENT,
BPF_PROG_TYPE_CGROUP_SKB,
BPF_PROG_TYPE_CGROUP_SOCK,
BPF_PROG_TYPE_LWT_IN,
BPF_PROG_TYPE_LWT_OUT,
BPF_PROG_TYPE_LWT_XMIT,
BPF_PROG_TYPE_SOCK_OPS,
BPF_PROG_TYPE_SK_SKB,
BPF_PROG_TYPE_CGROUP_DEVICE,
BPF_PROG_TYPE_SK_MSG,
BPF_PROG_TYPE_RAW_TRACEPOINT,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
BPF_PROG_TYPE_LWT_SEG6LOCAL,
BPF_PROG_TYPE_LIRC_MODE2,
BPF_PROG_TYPE_SK_REUSEPORT,
BPF_PROG_TYPE_FLOW_DISSECTOR,
/ See /usr/include/linux/bpf.h for the full list. /
};BPF MAPBPF 的 map 可用于内核 BPF 程序和用户应用程序之间实现双向的数据交换, 是重要根底数据结构,它能够通过申明 struct bpf_map_def 构造实现创立。对于 BPF 最吸引人的一个方面,就是运行在内核上的程序能够在运行时应用消息传递互相通信,而 BPF Map 就是用户空间和内核空间之间的数据交换、信息传递的桥梁。BPF Map 实质上是以键/值形式存储在内核中的数据结构。在内核空间的程序创立 BPF Map 并返回对应的文件描述符,在用户空间运行的程序就能够通过这个文件描述符来拜访并操作 BPF Map。依据申请内存形式的不同,BPF Map 有很多种类型,罕用的类型是BPF_MAP_TYPE_HASH 和 BPF_MAP_TYPE_ARRAY,它们背地的内存治理形式跟咱们相熟的哈希表和数组基本一致。随着多 CPU 架构的成熟倒退,BPF Map 也引入了per-cpu 类型,如 BPF_MAP_TYPE_PERCPU_HASH、BPF_MAP_TYPE_PERCPU_ARRAY 等,每个 CPU 都会存储并看到它本人的 Map 数据,从属于不同 CPU 之间的数据是相互隔离的。上面是形容 BPF map 的枚举构造:enum bpf_map_type {
BPF_MAP_TYPE_UNSPEC,
BPF_MAP_TYPE_HASH,
BPF_MAP_TYPE_ARRAY,
BPF_MAP_TYPE_PROG_ARRAY,
BPF_MAP_TYPE_PERF_EVENT_ARRAY,
BPF_MAP_TYPE_PERCPU_HASH,
BPF_MAP_TYPE_PERCPU_ARRAY,
BPF_MAP_TYPE_STACK_TRACE,
BPF_MAP_TYPE_CGROUP_ARRAY,
BPF_MAP_TYPE_LRU_HASH,
BPF_MAP_TYPE_LRU_PERCPU_HASH,
BPF_MAP_TYPE_LPM_TRIE,
BPF_MAP_TYPE_ARRAY_OF_MAPS,
BPF_MAP_TYPE_HASH_OF_MAPS,
BPF_MAP_TYPE_DEVMAP,
BPF_MAP_TYPE_SOCKMAP,
BPF_MAP_TYPE_CPUMAP,
BPF_MAP_TYPE_XSKMAP,
BPF_MAP_TYPE_SOCKHASH,
BPF_MAP_TYPE_CGROUP_STORAGE,
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
BPF_MAP_TYPE_QUEUE,
BPF_MAP_TYPE_STACK,
BPF_MAP_TYPE_SK_STORAGE,
BPF_MAP_TYPE_DEVMAP_HASH,
BPF_MAP_TYPE_STRUCT_OPS,
BPF_MAP_TYPE_RINGBUF,
BPF_MAP_TYPE_INODE_STORAGE,
BPF_MAP_TYPE_TASK_STORAGE,
BPF_MAP_TYPE_BLOOM_FILTER,
};三、BPF 的开发姿态3.1 BPF 开发框架
上图就是十分经典的 BPF 开发框架图了,个别开发流程都是先将用户编写的特定程序通过 LLVM 编译为 BPF 字节码,在注入到内核中时会通过 verifier 严格的查看,确保代码不会呈现死循环、宕机之类的问题,而后再通过jit将其翻译为 native code 进行执行,用户能够通过查看内核透出的数据,理解零碎的运行状况。 尽管 BPF 有十分多的利用,但应用的时候也有许多的限度。比方:不能有不确定的循环操作;内核要确保执行流肯定能够从BPF程序中进去。不容许睡眠;睡眠可能导致内核执行流始终出不来。不容许批改内核数据结构;个别不能批改数据报文。栈空间无限;以后只有 512 字节。不容许被间接调用内核函数;必须通过辅助函数。3.2 开源工具和平台BPF的编程分为两局部,一部分是运行在内核态,这是 BPF 编程的外围;另一部分是运行在用户态,这部分代码次要用来加载BPF,采集并解决数据等。libbpflibbpf是官网的用户态库。和编译一般的c程序会失去一个.o文件一样,LLVM编译完BPF 程序也会失去一个的.o文件(个别咱们命名为xx.bpf.o)。这个 bpf.o 文件依照 elf 的格局组织 BPF 程序的字节码、map定义以及符号等信息,libbpf 库负责解析这些信息,创立 map,注入 BPF 程序到内核工作。因而,对于用户来说,须要做很多繁琐的工作:用 C 语言来编写 BPF 程序;编写 makefile,调用 LLVM 编译生成 .o 文件;调用 libbpf 的接口加载 .o 文件;应用 libbpf 的接口获取 map 中的数据。BCCBCC 是现在最热门也是对老手最敌对的开源平台。它用 python 封装了编译、加载和读取数据的过程,提供了很多十分好用的 API。和 libbpf 须要提前把 bpf 程序编译成 bpf.o 不同,BCC 是在运行时才调用 LLVM 进行编译的,所以要求用户环境上有 llvm 和 kernel-devel。这个就会像前面咱们提到的,它会呈现运行时偏移导致的刹时资源冲高问题。bpftracebpftrace 是单命令工具,让用户像应用脚本一样应用 BPF。它自定义了本人的 DSL 作为前端,底层也是调用 LLVM 的。事实上,它依赖于 BCC 提供的 libbcc.so 文件。3.3 libbpf CORE后面提到 libbpf 开发,成为以后最支流的开发方式,然而须要为每个内核版本都开发特定的二进制程序,不能达到同一个 BPF 程序在不同内核版本上平安运行。因为不同内核版本数据的内存布局不同,就须要反对CORE(CompileOnce、Runeverywhere),提到 CORE 咱们要先来理解一下 BTF。BTF(BPF Type Format)是一种相似于 DWARF 的格局,专用于形容程序中数据类型。其次要存在于两个中央:一是 vmlinux 镜像内。二是用户编写的 BPF 程序内。二者存在着肯定的差别。第一个差别是:BPF 程序内除了存在 .btf 段外,还存在.btf.ext 段,专用于记录 BPF 程序内应用的数据类型的状况。第二个差别是:vmlinux 镜像内的 BTF 程序由原存在于该镜像内的 dwarf 信息简化而来,而 BPF 程序内的 BTF 段由 CLANG 编译器生成,须要在编译时指定 --target bpf。利用 BPF 程序里的 BTF 段和寄存在每个零碎上的 BTF 文件,咱们将这些信息进行重定位,就能确定每个数据结构的偏移,达到 CORE 的目标。要害组件:BTF: 形容内核镜像,获取内核及BPF程序类型和代码的要害信息(http://pylcc.openanolis.cn/)Clang 开释 bpf 程序重定位信息到 .btf 段Libbpf CO-RE 依据 .btf 段重定位 bpf 程序目前在 libbpf CO-RE 中须要进行重定位的信息次要有三类:1)构造体相干重定位,这部分和 BTF 非亲非故;Clang 通过 __builtin_preserve_access_index() 记录成员偏移量u64 inode = task->mm->exe_file->f_inode->i_ino;
u64 inode = BPF_CORE_READ(task, mm, exe_file, f_inode, i_ino);2)map fd、全局变量、extern 等重定位,这部分次要依赖于 ELF 重定位机制。通过查找 ELF 重定位段收集重定位信息,更新相应指令的 imm 字段。skel->rodata->my_cfg.feature_enabled = true;
skel->rodata->my_cfg.pid_to_filter = 123;
extern u32 LINUX_KERNEL_VERSION __kconfig;
extern u32 CONFIG_HZ __kconfig;3)子函数重定位,也是依赖于 ELF 重定位机制。然而目标不一样,子函数重定位是为了将 eBPF 程序调用的子函数同主函数放在同一块间断的内存中,便于一起加载到内核。例如将所有子程序拷贝到主程序所在区域, always_inline 函数。libbpf CORE 开发步骤:1)生成带所有内核类型的头文件 vmlinux.hbpftoolbtf dump file vmlinux format c > vmlinux.h2)应用 Clang (版本 10 或更新版本)将BPF程序的源代码编译为 .o 对象文件;3)从编译好的 BPF 对象文件中生成BPF skeleton 头文件 bpftool gen 命令生成;4)在用户空间代码中蕴含生成的 BPF skeleton 头文件;5)编译用户空间代码,这样会嵌入 BPF 对象代码,后续就不必公布独自的文件。6)生成的 BPF skeleton 应用如下步骤加载、绑定、销毁:<name>__open() – 创立并关上 BPF 利用,之后能够设置 skel->rodata 变量。<name>__load() – 初始化,加载和校验BPF 利用局部。<name>__attach() – 附加所有能够主动附加的BPF程序 (可选,能够间接应用 libbpf API 作更多管制)。<name>__destroy() – 拆散所有的 BPF 程序并应用其应用的所有资源。四、 coolbpf 享受式开发后面咱们曾经对 BPF 有了一个意识,到此曾经入门了,同时也学习了 BPF 的高阶常识:libbpf 的 CORE,它也是将来的一个方向,然而咱们也看到这外面,还须要写一堆代码:open、load、attach 等等。咱们是否把这所有进行简化呢?能不能享受式的进行开发。别急,先来看看罕用的开发方式:4.1 BPF 开发罕用计划比照1)原生 libbpf,无 CO-RE (内核 samples/bpf 示例)劣势:资源占用量低毛病:须要搭建代码工程、开发效率低;不同内核版本兼容性差;2)BCC(BPF Compile Collection、python 代码)劣势:开发效率高,可移植性好,反对动静批改内核局部代码毛病:部署依赖的 Clang/LLVM;每次运行都要执行 Clang/LLVM 编译,争抢内存 CPU 内存等资源;依赖指标环境头文件;3)BPF CO-RE(libbpf-tools 上面的代码)劣势:不依赖在环境中部署 Clang/LLVM,资源占用少毛病:仍须要搭建编译编译工程;局部代码绝对固定,无奈动静配置;用户态开发反对信息较少,不足高级语言对接;综上所述,上述计划不能很好适配生产环境中,多内核并存、疾速批量部署等需要4.2 Coolbpf(能够酷玩的 BPF)解决的问题通过将 BPF 的三种开发方式比照,咱们发现都不能完满的在生产环境中解决如下几个问题:装置依赖库和内核头文件;CPU 和内存等资源刹时冲高;BTF 须要按版本随 BPF 二进制程序公布。为解决这几个问题,咱们提出一个 coolbpf 的开发编译平台,目前蕴含 pylcc、rlcc、golcc 等目录,别离是高级语言 python、rust 和 go 语言反对近程和本地编译的能力。代码链接地址:https://gitee.com/anolis/cool...这里不对 coolbpf 过多介绍,具体内容请参考《龙蜥社区开源 coolbpf,BPF 程序开发效率晋升百倍》。4.3 pyLCC 的享受式为了介绍什么叫享受式开发,在这里咱们拿 coolbpf 的 pyLCC 进行演示:import sys
from pylcc.lbcBase import ClbcBase, CexecCmd //import pylcc base库
bpfProg = r"""
struct data_t {
int cpu;int type; // 0: irq, 1:sirqu32 stack_id;u64 delayed;
};
LBC_PERF_OUTPUT(e_out, struct data_t, 128); //定义perf event output array map
LBC_STACK(call_stack, 256); //定义stack 的map
SEC("kprobe/check_timer_delay")
int j_check_timer_delay(struct pt_regs *ctx)
{
struct data_t data = {};data.cpu = PT_REGS_PARM2(ctx);data.type = PT_REGS_PARM1(ctx);data.delayed = PT_REGS_PARM3(ctx);data.stack_id = bpf_get_stackid(ctx, &call_stack, KERN_STACKID_FLAGS);bpf_perf_event_output(ctx, &e_out, BPF_F_CURRENT_CPU, &data, sizeof(data));return 0;
}
"""
class Crunlatency(ClbcBase):
def __init__(self, lat=10): self._exec = CexecCmd self.setupKo(lat >> 1)//只须要简略的init,就能够把open load attach 等动作做好,而后专一于数据处理 super(Crunlatency, self).__init__("runlatency", bpf_str=bpfProg)def _cb(self, cpu, data, size): stacks = self.maps['call_stack'].getStacks(e.stack_id) print("call trace:") //call back函数里分心解决数据 for s in stacks: print(s)大家看到下面的示例,只须要以下三步,就能够实现一个程序的开发:1)pip install coolbpf。2)编写 bpf.c 代码。3)编写 python,通过 init() 加载之后,就专一性能开发。 你能够不必关怀 BPF 的汇编、字节码,也不必装置 Clang,不必装置 kernel-dev 头文件,不必本人生成 BTF 文件(它会主动到近程服务器下载),只需专一你的性能开发,比方剖析网络流量、监控文件关上和敞开、跟踪零碎调用。 总结来看,BPF 技术还在热火朝天的倒退着,但只有把握了这些根底的知识点,就可能举一反三,利用好已有的工具或平台,更加能锦上添花。通过后面的介绍,咱们不仅对 BPF 有了比拟粗浅的了解,还能借助 coolbpf,十分酷的玩了一把享受式的开发,简洁如此,谁能不爱呢? 对于回放和课件获取【视频回放】:视频回访已上传至龙蜥官网(可浏览原文中转):https://openanolis.cn/video 查看。 【PPT课件获取】:关注微信公众号(OpenAnolis),回复“龙蜥课件” 即可获取。有任何疑问请随时征询龙蜥助手—小龙(微信:openanolis_assis)。 —— 完 ——退出龙蜥社群退出微信群:增加社区助理-龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】与你同在;退出钉钉群:扫描下方钉钉群二维码。欢送开发者/用户退出龙蜥社区(OpenAnolis)交换,独特推动龙蜥社区的倒退,一起打造一个沉闷的、衰弱的开源操作系统生态!
对于龙蜥社区龙蜥社区(OpenAnolis)由企事业单位、高等院校、科研单位、非营利性组织、集体等在被迫、平等、开源、合作的根底上组成的非盈利性开源社区。龙蜥社区成立于 2020 年 9 月,旨在构建一个开源、中立、凋谢的Linux 上游发行版社区及翻新平台。龙蜥社区成立的短期指标是开发龙蜥操作系统(Anolis OS)作为 CentOS 停服后的应答计划,构建一个兼容国内 Linux 支流厂商的社区发行版。中长期指标是摸索打造一个面向未来的操作系统,建设对立的开源操作系统生态,孵化翻新开源我的项目,凋敝开源生态。目前,Anolis OS 8.6 已公布,更多龙蜥自研个性,反对 X86_64 、RISC-V、Arm64、LoongArch 架构,欠缺适配 Intel、兆芯、鲲鹏、龙芯等芯片,并提供全栈国密反对。欢送下载:https://openanolis.cn/download退出咱们,一起打造面向未来的开源操作系统!https://openanolis.cn原文链接:http://click.aliyun.com/m/100...本文为阿里云原创内容,未经容许不得转载。