《Linux超能力BPF技术介绍及学习资料.docx》由会员分享,可在线阅读,更多相关《Linux超能力BPF技术介绍及学习资料.docx(11页珍藏版)》请在优知文库上搜索。
1、BPF是什么?需要回答BPF是什么?就得先回答为什么需要BPF?多年前很多程序,例如网络监控器,都是作为用户级进程运行的。为了分析只在内核空间运行的数据,它们必须将这些数据从内核空间复制到用户空间的内存中去,并进行上下文切换。这与直接在内核空间分析这些数据相比,导致了巨大的性能开销。而随着近年来网络速度和流量井喷式增长,一些应用程序必须处理大量的数据(如音频、视频流媒体数据)。要在用户空间监控分析那么多的流量数据已经不可行了,因而BPF应运而生种在内核空间执行高效安全的程序的机制。BPF全称是BerkeleyPacketFilteid,翻译过来是伯克利包过滤器,顾名思义,它是在伯克利大学诞生的
2、,1992年StevenMcCanne和VanJacobson写了一篇论文:TheBSDPacketFilter:ANeWArehiteCtUrefOrUSer-IeVeIPaCketCaPture,第一次提出了BPF技术,在文中,作者描述了他们如何在UniX内核实现网络数据包过滤,这种新的技术比当时最先进的数据包过滤技术快20倍。下图为BPF概览,来自上面的论文:一个新的虚拟机(VM)设计,可以有效地工作在基于寄存器结构的CPU之上;应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息,最大程度地减少BPF处理的数据,提高处理效率。我们熟悉的Mpdump就是基于BPF技术,
3、成为了站在神器肩膀上的神器。发展到现在名称升级为eBPF:extendedBerkeleyPacketFiltero演进成一套通用执行引擎,提供了可基于系统或程序事件高效安全执行特定代码的通用能力,通用能力的使用者不再局限于内核开发者。其使用场景不再仅仅是网络分析,可以基于eBPF开发性能分析、系统追踪、网络优化等多种类型的工具和平台。eBPF由执行字节码指令、存储对象和帮助函数组成,字节码指令在内核执行前必须通过BPF验证器的验证,同时在启用BPFJlT模式的内核中,会直接将字节码指令转成内核可执行的本地指令运行,具有很高的执行效率。下图是eBPF工作原理演示:Controller向54TC
4、omPiIerEIXnUsendmsg()recvmsg()SocketsTCP/IPNetworkDeviceProcess原来的BPF就被称为CBPF(ClaSSiCBPF),目前已基本废弃。当前1.inUX内核只运行eBPF,内核会将CBPF透明转换成eBPF再执行。下文提到的BPF字样没有特别说明的话,是泛指CBPF和eBPF。BPF技术发展史从1992年发布以来,BPF技术快速发展,除了技术本身得到了升级,基于它的工具和平台也如雨后春笋一般层出不穷,下面是我整理的BPF技术发展史,里面罗列几个重要的eBPF发展里程碑和基于eBPF技术的工具和平台的诞生事件,其中包括BCC、Ciliu
5、mFalco、bpftracekubecti-trace,还有最近非常热门的腾讯云独创的IPVS-BPF模式。强安全,即不能允许不可信的代码运行在内核中,这是头等重要的事情高性能,作为承载千百万服务的操作系统内核,如果没有高性能的保障,互联网蓬勃发展将收到严重影响持续交付,在越来越多应用进入到云原生时代的今天,持续交付这个命题,一点都不陌生,而在内核开发领域,这点也至关重要,每次功能的升级,都需要你重新安装新的系统,大多数人都不会买账。我们希望做到跟ChrOme浏览器升级一样,用户都不会注意到升级完成了(除非有一些视觉上的变化),实现真正的无缝升级。想要实现上面的目标,没有想象中那么简单。我们
6、来看进行1.inux内核开发的一般解决方案以及它们的缺陷。直接修改内核代码进行开发,通过APl暴露能力,可能要等上n年用户才能更新到这个版本来使用,而且每次的功能更新都可能需要重新编译打包内核代码。开发新的可即时加载的内核模块,用户可以在运行时加载到1.inUX内核中,从而实现扩展内核功能的目的。然而每次内核版本的官方更新,可能会引起内核API的变化,因此你编写的内核模块可能会随着每一个内核版本的发布而不可用,这样就必须得为每次的内核版本更新调整你的模块代码,你得非常小心,不然就会让内核直接崩溃。来看BPF带来的解决方案,它是如何实现上面3个目标的:强安全:BPF验证器(verifier)会保
7、证每个程序能够安全运行,它会去检查将要运行到内核空间的程序的每一行是否安全可靠,如果检查不通过,它将拒绝这个程序被加载到内核中去,从而保证内核本身不会崩溃,这是不同于开发内核模块的。比如以下几种情况是无法通过的BPF验证器的:BPF验证机制很像Chrome浏览器对于JaVaSCriPt脚本的沙盒机制。没有实际加载BPF程序所需的权限访问任意内核空间的内存数据将任意内核空间的内存数据暴露给用户空间高性能:一旦通过了BPF验证器,那么它就会进入Jrr编译阶段,利用Just-In-Time编译器,编译生成的是通用的字节码,它是完全可移植的,可以在x86和ARM等任意球CPU架构上加载这个字节码,这样
8、我们能获得本地编译后的程序运行速度,而且是安全可靠的。持续交付:通过JlT编译后,就会把编译后的程序附加到内核中各种系统调用的钩子(hook)上,而且可以在不影响系统运行的情况下,实时在线地替换这些运行在1.inux内核中的BPF程序。举个例子,拿一个处理网络数据包的应用程序来说,在每秒都要处理几十万个数据包的情况下,在一个数据包和下一个数据包之间,加载到网络系统调用hook上的BPF程序是可以自动替换的,可以预见到的结果是,上一个数据包是旧版本的程序在处理,而下一个数据包就会看到新版本的程序了,没有任何的中断。这就是无缝升级,从而实现持续交付的能力。因此,大名鼎鼎的系统性能优化专家Brend
9、anGregg对于BPF的到来,就给出了以下的名言:uSuperpowershavefinallycometo1.inux,一BrendanGreggBPF的超能力第一个,BPFHooks,即BPF钩子,也就是在内核中,哪些地方可以加载BPF程序,在目前的1.inUX内核中已经有了近10种的钩子,如下所示:kernelfunctions(kprobes)userspacefunctions(uprobes)systemcallsfentry/fexitTracepointsnetworkdevices(tc/xdp)networkroutesTCPcongestionalgorithmssoc
10、kets(datalevel)从文件打开、创建TCP链接、SOCket链接到发送系统消息等几乎所有的系统调用,加上用户空间的各种动态信息,都能加载BPF程序,可以说是无所不能。它们在内核中形成不同的BPF程序类型,在加载时会有类型判断。下图的内核代码片段是用来判断BPF程序类型:staticintload_and_attach(constcharevent,structbpfvinsnprog,intsize)77(boolis_socketstrnc(eventfsocket,r6)00;boolI1.kProbe-strnc(event,kprobc1,7)-O;boolis.krtpro
11、bstrncsp(event,tprobe/,r10)三0;boolie_trcpoint,Strncnpteventrtrcepoin*.11)三t=0;boolis.raw_tracepointstrncap(eventzrawtracepoint-f15)=0;boolis_xdpstrncap(event,xdp,3)三0;boolis_perf_event-Strncnpievent,perfevent,10)0;booli-cgroup-skb-Strncnpieventr,10)-0;boolis_cgroup_,kstrncnp(event,roupsock*,11)三0;bo
12、olis_sockops-strncsp(event,ockops*r7)三三0;boolis_sk_skb=Strncap(event,k-skbr6)三三0;boolis.skmsg-Strncap(event,skjnsg,r6)三-0;sixe_tinsns_cnt-size/sizeof(structbpf_insn;enumbpf_prog_typeprog-type;charbuf(256)7intfdrfdrerr,id;structperf_vent_attrattr三(;95一一attr,typePERjunTPEJRACEPOlBrT;attr.aple-typeePER
13、F_6AMP1.E_RAW;attr.sA*ple_period-1;attr.wakupvnts1;100一if(iseocket)prog_typeBPFePROGeTTPBeSOCKETeFI1.TBR;elseif(iskprobeiskretprobe)prog-typeBPP_PROG_TYPE_KPROBEelseif(s_tracpoint(pro9-typeBPF_PROG_TTPE_TRACEPOXNT; elseif(is_raw_tracepoint)proqtypeBPFPRoGTYPERAWTRACEPOINT; elseif(is_xdp)(pro9_typeBP
14、F_PROG_TTPE_XDP; elsif(is-prf-vnt)pr9-typBPF_PROG_TXPE_PERF_BVBHT;elseif(is_cgroup_skb)progtypeBPF_PROG_TTPB_CGROUP_SKB;elseif(iscgroupsk)UnuxvS.8.7第二个核心技能点BPFMapo一个程序通常复杂的逻辑都有一个必不可少的部分,那就是记录数据的状态。对于BPF程序来说,可以在哪里存储数据状态、统计信息和指标信息呢?这就是BPFM叩的作用,BPF程序本身只有指令,不会包含实际数据及其状态。我们可以在BPF程序创建BPFMap,这个M叩像其他编程语言具有的
15、Map数据结构类似,也有很多类型,常用的就是HaSh和AiTay类型,如下所示:Hashtables,Arrays1.RU(1.eastRecentlyUsed)RingBufferStackTrace1.PM(1.ongestPrefixmatch)下图所示是一个典型的BPFMap创建代码:structbpf_map_defSEC(maps)my_bpf_map=.type=BPF_MAP_TYPE_HASHr.key_size-sizeof(int),.value_size=sizeof(int),.max_entries=100,6. .map-flags-BPF_F_NO_PREA1.1.OC,;值得一提的是:BPFM叩是可以被用户空间访问并操作的BPFMap是可以与BPF程序分离的,即当创建一个BPFMap的BPF程序运行结束后,该BPFM叩还能存在,而不是随着程序一起消亡基于上面两个特点,意味着我们可以利用BPFMap持久化数据,在不丢失重要数据的同时,更新BPF程序逻辑,实现在不同程序之间共享信息,在收集统计信息