凌云时刻 · 技术

导读:eBPF 技术是近几年 Linux 社区一颗闪亮的明星,如果你还没有开始接触并编写过 eBPF 程序,那么不要错过,接下来的一系列文章非常适合你。(本文为连载文章,上一篇:eBPF Internal: Instructions and Runtime)

作者 | 荣旸

来源 | 凌云时刻(微信号:linuxpk)

前言

eBPF Up & Running 系列文章目前主要包括下面三篇:

  • Overview 总览,你的第一个 eBPF 程序将会在这里诞生;

  • Tracing 跟踪,eBPF 如何在 tracing 场景大展身手;

  • Network 网络,如何使用 eBPF 增强内核网络;

接下来会随着 eBPF 的发展和大家的反响情况,不断丰富扩展更多的文章。废话不多说,让我们开始接下来的干货时间。

简介

在写第一行代码之前,首先让我们了解一下 eBPF 技术前世今生。

回到 1992 年,eBPF 的前身 BPF (Berkeley Packet Filter),现在被成为 cBPF (classic BPF)  ,主要用于 tcpdump 和 seccomp,场景无外乎是一种灵活的 DSL + 虚拟机帮助用户筛选数据。用户的过滤条件会被编译为 cBPF  程序,程序由预先定义的指令组成,这些指令在内核中解释运行,并通过 JIT 加速执行速度,每当数据经过时,会触发程序执行并判断是否满足筛选条件。

时间很快来到了 2011 年,eBPF 在这一年诞生了。经过社区的不断迭代和完善,eBPF 在各个方面得到增强:

  1. 定义了全新的指令架构,复用了 cBPF 的 opcode 格式和命令,并引入了大量的寄存器;

  2. LLVM 后端支持 BPF target,可以将 C 程序编译为 eBPF 字节码并在内核中运行;

  3. 引入了 bpf.h 等头文件的支持和丰富的 helpers 函数,简化了 eBPF 程序的编写;

eBPF 是如何在内核中的执行的?eBPF Internal: Instructions and Runtime  文章中有更详细的介绍,下图展示了 eBPF 程序的大体执行流程。

其实 eBPF  本身其实远没有想象中的复杂,我们可以把内核想象成一个庞大而复杂的电路,如果电路出现了异常,我们可以通过使用万用表测量电路,如果当前电路不能满足需求,但又不能推倒重新设计,我们需要串入其他元件。eBPF 之于电路,既是万用表,也是各种功能元件。再回到内核中的 eBPF,内核预先在各个关键路径埋设了 eBPF 程序入口,用户可以编写不同类型的  eBPF 程序,将 eBPF 程序 attach 在内核中不同路径中执行。

示例:流量协议统计

 搭建脚手架

在编写第一个程序之前,我们需要准备一个顺手的开发环境。内核源码仓库 samples/bpf 目录下包含了数十个典型的 eBPF 示例程序,除了可供学习和参考之外,我们可以将程序代码放在这个目录,无需自己额外花时间编写和调试 Makefile。

 代码编写

eBPF 程序本质上是一种字节码,我们只需将编写的代码编译成 eBPF 字节码,即可在内核的 eBPF  虚拟机中运行。理论上我们可以使用各种现有的语言编写 eBPF 程序,只要确保这门语言具有对应的 LLVM 前端,帮助我们将其翻译为 LLVM  IR 中间代码,并通过 LLVM 最终编译为 eBPF 字节码。通常情况下,推荐使用 C 进行编写,当前内核提供了完备的 C  library。因此我们需要确保 clang 已经正确配置好,如果已经按照上面步骤正确配置了脚手架,我们无需再配置 clang 和 LLVM。

对于大部分场景下 eBPF 编程模型,eBPF 程序不是单独出现,而是由用户态控制平面 control plane 和内核态数据平面 data plane 两部分组成,经典的数据面和控制面分离。

接下来我们将以samples/bpf/{xdp1_kern.c,xdp1_user.c} 程序为例,这个示例不只是简单的打印,它可以统计网卡的数据报文的协议分布数量。

首先是内核态的 eBPF 代码:xdp1_kern.c,主要功能是从数据报文中解析并判断协议类型,统计数据报文中不同协议的数量分布情况。

/* 其他省略,完整源码请参考 samples/bpf/xdp1_kern.c *//* 定义数据结构存放统计信息 */
struct bpf_map_def SEC("maps") rxcnt = {.type = BPF_MAP_TYPE_PERCPU_ARRAY,.key_size = sizeof(u32),.value_size = sizeof(long),.max_entries = 256,
};SEC("xdp1")
/* eBPF XDP 类型程序的特定函数签名,ctx 包含数据包的上下文信息 */
int xdp_prog1(struct xdp_md *ctx)
{/* 报文结束位置 */void *data_end = (void *)(long)ctx->data_end;/* 报文开始位置 */void *data = (void *)(long)ctx->data;struct ethhdr *eth = data;long *value;u16 h_proto;u64 nh_off;u32 ipproto;nh_off = sizeof(*eth);if (data + nh_off > data_end)/* 对于非法报文直接返回 XDP_DROP 并丢弃 */return XDP_DROP;h_proto = eth->h_proto;/* 接下来会根据数据报文依次解析协议类型 */if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {struct vlan_hdr *vhdr;vhdr = data + nh_off;nh_off += sizeof(struct vlan_hdr);/* 判断数据包是否合法,否则直接丢弃 */if (data + nh_off > data_end)return XDP_DROP;h_proto = vhdr->h_vlan_encapsulated_proto;}if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {struct vlan_hdr *vhdr;vhdr = data + nh_off;nh_off += sizeof(struct vlan_hdr);/* 判断数据包是否合法,否则直接丢弃 */if (data + nh_off > data_end)return XDP_DROP;h_proto = vhdr->h_vlan_encapsulated_proto;}if (h_proto == htons(ETH_P_IP))/* 从 payload 中解析 Ipv4 protocol */ipproto = parse_ipv4(data, nh_off, data_end);else if (h_proto == htons(ETH_P_IPV6))/* 从 payload 中解析 Ipv6 protocol */ipproto = parse_ipv6(data, nh_off, data_end);elseipproto = 0;/* 统计不同协议的访问次数 */value = bpf_map_lookup_elem(&rxcnt, &ipproto);if (value)*value += 1;/* 返回 XDP_PASS 传递报文至下层处理逻辑 */return XDP_PASS;
}

接下来是用户态代码:xdp1_user.c,主要功能是载入 eBPF 程序到内核中,并从内核共享的 map 中获得统计信息并展示。

/* 其他省略,完整源码请参考 samples/bpf/xdp1_kern.c */static int ifindex;
static __u32 xdp_flags;static void int_exit(int sig)
{/* 将 XDP 与网卡解绑 */bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);exit(0);
}static void poll_stats(int map_fd, int interval)
{unsigned int nr_cpus = bpf_num_possible_cpus();const unsigned int nr_keys = 256;__u64 values[nr_cpus], prev[nr_keys][nr_cpus];__u32 key;int i;memset(prev, 0, sizeof(prev));while (1) {sleep(interval);for (key = 0; key < nr_keys; key++) {__u64 sum = 0;/* 从 map 中获取统计信息 */assert(bpf_map_lookup_elem(map_fd, &key, values) == 0);for (i = 0; i < nr_cpus; i++)sum += (values[i] - prev[key][i]);if (sum)printf("proto %u: %10llu pkt/s\n",key, sum / interval);memcpy(prev[key], values, sizeof(values));}}
}int main(int argc, char **argv)
{struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};struct bpf_prog_load_attr prog_load_attr = {/* 指定程序类型,这里为 XDP */.prog_type      = BPF_PROG_TYPE_XDP,};const char *optstr = "SN";int prog_fd, map_fd, opt;struct bpf_object *obj;struct bpf_map *map;char filename[256];while ((opt = getopt(argc, argv, optstr)) != -1) {switch (opt) {case 'S':/* XDP 两种工作模式,第三篇文章会详细展开 */xdp_flags |= XDP_FLAGS_SKB_MODE;break;case 'N':/* XDP 两种工作模式,第三篇文章会详细展开 */xdp_flags |= XDP_FLAGS_DRV_MODE;break;default:usage(basename(argv[0]));return 1;}}/* 省略不重要代码 */snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);prog_load_attr.file = filename;/* 载入 eBPF 字节码至内核 */if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd))return 1;map = bpf_map__next(NULL, obj);if (!map) {printf("finding a map in obj file failed\n");return 1;}/* 获得 eBPF 中定义的数据结构,用以获取统计信息 */map_fd = bpf_map__fd(map);/* 省略不重要代码 *//* 设置 XDP 与网卡的绑定,eBPF 字节码将会在指定网卡收包路径执行  */if (bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags) < 0) {printf("link set xdp fd failed\n");return 1;}/* 定时从 map fd 中轮询统计数据 */poll_stats(map_fd, 2);return 0;}

编译运行

在 Linux 源码仓库根目录执行 make samples/bpf/,编译完成后会在 samples/bpf 目录下会得到用户态可执行文件 xdp1 和 eBPF 字节码文件 xdp1_kern.o,直接在当前目录直接执行即可。如果是自己编写的代码,可以将文件放入 samples/bpf 目录下,并在 Makefile 中加入这对应文件的编译配置。

如果遇到了任何环境或者编译的问题,可以参考内核文档 samples/bpf/README.rst

基础概念

通过上面的示例程序之后,想必大家已经对如何编写一个 eBPF 程序有了初步的印象。以刚才编写的程序为例,我们可以发现 eBPF 程序包含三个基础的概念:程序类型、数据结构和 helpers。

程序类型

eBPF 的程序类型 (program type),决定了 eBPF 程序如何载入内核,以及在内核哪些路径执行 eBPF 程序。例如需要 kprobe 跟踪函数执行,则需要在载入时指定 eBPF 程序的类型为 BPF_PROG_TYPE_KPROBE 。对于 XDP 则需要指定为 BPF_PROG_TYPE_XDP

以 XDP 类型 eBPF 程序为例,用户通过 netlink 调用 dev_change_xdp_fd 为指定 dev 设置 eBPF 程序,例如 veth 网卡 XDP 的支持在 4.14 内核引入,通过 veth_xdp_set 为网卡绑定 eBPF 字节码,当 NAPI poll 执行时于每个 XDP frame 依次调用 veth_xdp_rcv_one,并执行对应的 eBPF 程序,根据 eBPF 程序的返回结果决定丢弃、通过或者重定向报文。

全量的程序类型定义在include/uapi/linux/bpf.h,如需了解详细的程序类型请参考:BCC 文档。

数据结构

对于任何程序而言,都离不开各种各样的数据结构。eBPF 提供了各种常用的数据结构,从而实现内核内部数据的组织,以及用户态和内核态的通信;

当前 eBPF 定义了26中基础的数据结构,涵盖了 hash、stack、array 和 ringbuf。以 hash 为例,可以指定 key、leaf 的数据类型以及大小,同时提供了相应的 helpers 操作函数 BPF_FUNC_map_lookup_elem()BPF_FUNC_map_push_elem() 等。

全量的数据结构定义在 include/uapi/linux/bpf.h,如需了解详细的数据结构列表请参考:BCC 文档。

Helpers

如同其他语言生态会提供丰富的 library,eBPF 也包含了各种常用的 helpers 函数,例如打印输出 BPF_FUNC_trace_printk 等;

eBPF 定义的 helpers 函数不仅为了简化复杂的 eBPF 操作,同时也会将部分危险的操作封装成安全的 helpers 函数,对于内核数据结构的访问和操作需要借助 helper 完成,确保了 eBPF 程序的安全性。

全量的 helpers 定义在include/uapi/linux/bpf.h,详细的 helps 列表请参考:BCC 文档

快速上手的应用

学习并掌握一门技术的最好方式是付诸于实践。现在你的脑海中可能已经迸发出各种各样的想法,迫不及待编写 eBPF 程序验证和实践。为了更方便的去验证和实践,除了上面提到的基于 samples/bpf 示例程序之外,我们还可以基于下面一些社区已有的可供我们快速上手的应用。

BCC

BCC 是一个包含丰富的内核跟踪分析的 eBPF 工具集,用户也可以基于 BCC 创建自己的 eBPF 工具。当前 BCC 工具提供了 Python / Lua 和 Go 语言的 binding,用户可以使用这三种语言编写自己的 eBPF 工具。BCC 提供一个非常友好的 tutorial 可供大家快速上手。其中 iovisor/gobpf 库,可以通过 Go 生态将 eBPF 与云原生、k8s 或各种运维工具相结合。

bpftrace

如果使用过 systemtap 动态跟踪分析内核,那么 bpftrace 是一个很好的替代方案。bpftrace 提供了一种类 awk 和 C  的语言,使用 bpftrace 语言编写各种跟踪和分析脚本,并编译成 eBPF 字节码与内核交互,从而实现动态跟踪 Linux  内核。使用文档可以参考:使用 bpftrace 分析内核,和 The bpftrace One-Liner Tutorial。

ubpf / generic-ebpf

假如对于 eBPF 字节码和虚拟机非常感兴趣,ubpf 提供了一个用户态实现的虚拟机,包含了解释运行和 JIT 特性。不仅帮助我们更好的理解 eBPF 虚拟机的实现,而且可以将 ubpf 嵌入到应用中,以执行编写好的 eBPF 程序,从而实现 Lua 或 WASM 的功能。generic-ebpf 则更进一步,提供了更完善的运行时机制和库函数,并将 eBPF 作为一种通用的字节码嵌入到交换机等硬件中并运用在生产环境。

总结

eBPF 为内核提供了更多的可能性,社区仍在不断拓宽 eBPF 的场景,通过这篇文章我们了解了如何编写 eBPF 程序,接下来几篇文章将会为大家带来 eBPF 在跟踪和网络方面的特性和应用。

END

往期精彩文章回顾

容器技术在企业落地的最佳实践

节约服务器成本50%以上,独角兽完美日记电商系统容器化改造历程

投入产出比增长2倍以上!银泰抛弃传统数据库转投阿里云PolarDB

阿里云SAE助力百富旅行实现Serverless+微服务完美结合

关于Kubernetes规划的灵魂N问

饿了么四年、阿里两年:研发路上的一些总结与思考

再启程,Service Mesh 前路虽长,尤可期许

数据湖:设计更好的架构,存储,安全和数据治理

RocketMQ助力编程猫构建稳定的业务系统

云上发展,唯快不破!IT部门是数字化转型的变革者

长按扫描二维码关注凌云时刻

每日收获前沿技术与科技洞见

eBPF Up Running: Overview相关推荐

  1. eBPF学习仓库bpf_study-996station GitHub鉴赏官

    推荐理由:eBPF学习参考资料,Linux内核观测技术BPF免费下载(英文) "eBPF 是我见过的 Linux 中最神奇的技术,没有之一,已成为 Linux 内核中顶级子模块,从 tcpd ...

  2. (gopher)一无所知学ebpf

    前言 我相信仍然有很多人不知道ebpf为何物,也不知道从何学起.但他似乎正在成为云原生开发性能优化的技术手段的事实标准,尤其是ebpf在容器网络性能.安全方面的巨大优势,几乎所有的云厂商无不在ebpf ...

  3. AS问题解决系列1—Unable to execute DX错误

    为什么80%的码农都做不了架构师?>>>    今天在将一个开源Android代码import到Android Studio 1.2.2中,在编译期间出现一个"Unable ...

  4. XDP/eBPF — Overview

    目录 文章目录 目录 XDP XDP 设计原则 XDP技术优势 XDP 的工作模式 XDP 的适用场景 XDP v.s. DPDK XDP XDP(eXpress Data Path,快速数据面)是 ...

  5. 深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)

    [BPF入门系列-1]eBPF 技术简介 | 深入浅出 eBPF[BPF入门系列-1]eBPF 技术简介https://www.ebpf.top/post/bpf_intro_blog/ 目录 eBP ...

  6. Sysdig and Falco now powered by eBPF

    By Gianluca Borello on February 27, 2019 https://sysdig.com/ 目录 An introduction to eBPF eBPF purpose ...

  7. Linux内核 eBPF基础: 探索USDT探针

    目录 Motivation Tracing System Overview Terminology术语 Evoluction of Linux Tracing Linux Tracing Techni ...

  8. 【BPF入门系列-1】eBPF 技术简介

    由范老师和我一起翻译的图书 <Linux内核观测技术BPF> 已经在 JD 上有现货,欢迎感兴趣 BPF 技术的同学选购.链接地址 https://item.jd.com/72110825 ...

  9. An overview of gradient descent optimization algorithms

    转载自:http://sebastianruder.com/optimizing-gradient-descent/ 梯度下降优化及其各种变体.1.随机梯度下降(SGD) 2.小批量梯度下降(mini ...

  10. Services overview

    原文链接:https://developer.android.com/guide/components/services Services overview A Service is an appli ...

最新文章

  1. 分享Kali Linux 2017年第12周镜像文件
  2. C++ 设计People类-1
  3. linux 学习教程
  4. 谈谈自动微分(Automatic Differentiation)
  5. 记录执行hivesql时报org.apache.hadoop.hdfs.BlockMissingException:Could not obtain block错误
  6. compat包_使用Compat Patchers,API的稳定性既便宜又容易!
  7. JUnit4常用的注解
  8. Spring+Mybatis+Mysql搭建分布式数据库访问框架
  9. java 导出表格打包zip文件下载_asyExcel导出excel并打包成zip压缩包下载
  10. UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0x80 in position 2
  11. YOLO-V4解读:速度与精度的完美结合
  12. 洛谷P2082 区间覆盖(加强版)(珂朵莉树)
  13. 个人档案php源码,php人事档案管理系统
  14. 中职学计算机专业考本科难吗,国际本科难不难(中职考本科难不难)
  15. 还在想假期去哪玩?直接做一个旅游攻略小程序
  16. 30s快速开通微商城,零售+分销结合,支持直播美颜
  17. Android开发之获取手机运行时间
  18. 大疆创新 java面试_如何成功应聘大疆创新科技?
  19. Linux字符界面与图形界面的切换
  20. Codeforces Round #682 (Div. 2)

热门文章

  1. 源代码安装mysql配置步骤
  2. 数据保护:从“随意架构”到简单可控
  3. [C/C++] ccpuid:CPUID信息模块 V1.02版,支持Mac OS X,支持纯C,增加CPUF常数
  4. 第2.01节 发布版本vs源码编译
  5. iphone-common-codes-ccteam源代码 CCCompile.h
  6. 网络系统设计的一般步骤
  7. mysql 中将汉字(中文)按照拼音首字母排序
  8. 架构 - 洋葱图架构
  9. pytest框架(三)
  10. atan和atan2反正切计算