科技
智汇华云 | ebpf介绍与使用-凯发游戏
linux在日常的使用中已经无处不在了,从人们的手机,到电视机,再到路由器,都运行在linux内核之上,而ebpf就是linux内核的一把瑞士军刀,通过ebpf可以在linux内核中运行程序,运维人员可以使用它对linux内核进行监控,开发者可以使用它对linux内核功能进行定制修改,做到很多以前无法实现的功能。
官方的说法: ebpf is a revolutionary technology with origins in the linux kernel that can run sandboxed programs in an operating system kernel.
个人认为,简单的说,ebpf可以理解为一个框架,通过这个框架,我们可以在内核中执行自己编写的程序代码。
ebpf基本概念
ebpf的前身是bpf(extended berkeley packet filter),bpf是内核中用来做高效过滤网络报文的,tcpdump里面就是用的bpf技术过滤报文。2014年内核3.18中ebpf第一次出现,此时的ebpf已经成为内核的顶级子系统,演进为一个通用执行引擎,允许用户在内核中运行自己的程序。
挂载点(hook)ebpf程序基于事件触发,当内核代码走到对应的挂载点就会执行挂载在此处的ebpf程序。常见的挂载点有:系统调用,内核函数进入/退出,内核跟踪点,网络数据包等等。
映射(maps)ebpf程序中用来存储数据,共享数据的结构体就是maps,内核内置了多种类型的maps给开发者使用,常见的有哈希表,数组,lru,ring buffer等等。
帮助函数(helper calls)内核为了保证安全,运行在内核中的ebpf程序只能调用当前内核版本预定义好的函数,不能随意调用其他内核函数,函数名称都是以bpf_开头命名。例如u32 bpf_get_smp_processor_id(void),可以获取当前ebpf程序运行在哪个cpu上。
加载与验证(loader & verification)编写好的ebpf程序需要先通过编译,生成字节码,之后通过调用bpf系统调用将字节码加载到内核,此时内核会运行自己的验证器来检验ebpf程序,确保程序是安全的,有限循环的,不会把linux系统搞坏。
ebpf特点
强大的内核可编程性。随着内核版本的升级,内核中可以运行ebpf程序的地方越来越多,ebpf可以做的事情也越来越多。不同内核功能加入ebpf版本列表:
xdp(快速数据路径)kernel 4.8
lirc(红外)kernel 4.18
限制100万条指令kernel 5.2
socket lookup(控制数据包入socket)kernel 5.9
开发方便开发者不需要自己定义数据结构,直接使用现成的maps进行存储共享数据,只需要关注具体的业务实现代码。由于ebpf程序先编译成字节码,之后内核自己校验通过之后再生成可用的内核代码,所以可以一次编译处处运行,不需要像内核模块一样,每次更新内核之后都要重新编译。
可以在x86上编译mips架构上运行的ebpf字节码。免去交叉编译的痛苦。
安全数据操作都是通过maps,操作maps的函数也是预先定义好的,不存在访问空指针。
ebpf使用
介绍了这么多ebpf的概念,接下来实际操作一下,看看ebpf程序如何编译和使用,这里采用原汁原味的linux源代码编译演示。
使用最新的archlinux系统,其他系统也差不多,稍微按照实际情况改一下。
# asp checkout linux# 准备linux内核源码
# makepkg -o -d –skippgpcheck# 下载linux源代码
# make mrproper
# zcat /proc/config.gz >.config
# make headers_install
# make modules_prepare
# make vmlinux_btf=/sys/kernel/btf/vmlinux m=samples/bpf
# asp checkout linux# 准备linux内核源码
# makepkg -o -d –skippgpcheck# 下载linux源代码
# make mrproper
# zcat /proc/config.gz >.config
# make headers_install
# make modules_prepare
# make vmlinux_btf=/sys/kernel/btf/vmlinux m=samples/bpf
这里把内核自带的bpf示例程序都编译出来了,在目录samples/bpf下面。
这里我们具体看一个sampleip的ebpf程序,看看ebpf程序是如何编写的
直接上代码:
首先17到23行定义了一个maps,叫ip_map,类型是哈希,键是u64,值是u32,最大长度8192。
之后定义了一个do_sample函数,函数参数类型是bpf_perf_event_data,里面有当前内核ip指令指针寄存器的内容。通过调用bpf_map_lookup_elem函数来更新ip_map。
运行内核编译好的sampleip程序,默认是采样5秒,每秒采样99次,程序结束后会把ip_map采集到的信息打印出来。
从这个实例中可以看出,开发的ebpf程序比传统的内核开发方便了很多,数据结构不用操心,可以调用的函数也不用操心,都是预先定义好的,只需要实现自己的业务逻辑即可。
ebpf使用场景
linux性能分析,性能调优上面的sampleip就是简单的内核性能分析,可以看出当前内核经常调用的函数。ebpf对于内核开销很小,可以在生产环境排查问题的时候进行精确定位,同时由于ebpf的安全性,不用担心会把内核搞挂。有兴趣的可以去看一下bcc,里面对于内核每个子系统都有对应的ebpf监控程序,非常方便。
linux网络加速ebpf在这个领域中也是牛的很,底层有xdp快速数据路径,可以直接在网卡收到数据包的同时进行处理,避免内核分配skb开销,可以用来实现ddos,负载均衡,性能媲美dpdk。再往上一点内核的tc也可以hook ebpf程序实现自定义流量分类,再向上的socket层还可以调用ebpf实现动态修改socket选项,甚至tcp的拥塞算法内核也提供了ebpf挂载的地方,可以自己实现一套新的拥塞算法。
安全管理systemd中使用ebpf控制服务可以监听的端口,libvirtd也使用ebpf进行设备的访问控制,社区还有ebpf控制进程允许访问的文件,允许读写哪些/sys文件。
ebpf的出现让linux内核开发变得简单,降低了内核开发门槛,为普通人了解深入linux内核提供了途径,真的是一个革命性的发明,有linux的地方就有ebpf ^^。