在 Android 中使用 eBPF:开篇
若干年前,我还在做 Android 客户端性能优化的时候,读到了 OpenResty 作者章亦春老师的文章:动态追踪技术漫谈,当时被深深地震撼到了,原来通过使用各种高级的调试技术,解决问题竟然可以做到如此精准而优雅。
然而当我真正要解决 Android 系统上应用程序的性能问题时,才发现理想很丰满现实很骨感——手头趁手的工具几乎没有。文章中提到的内核态追踪技术 SystemTap / DTrace 在 Android 系统上压根不存在,用户态的追踪技术开销大到可怕:TraceView 开启后程序性能直接下降十倍不止,Systrace 当时功能半残废,使用起来还需要自己插桩;simpleperf 能使,但就是有点 simple……到最后,真正要解决问题的时候,还是靠经验、二分法和 inline hook;为了定位 Android 虚拟机的性能问题,我甚至还自己造了个 ART HOOK 的轮子。
然而,时间来到 2022 年,世界已焕然一新:eBPF 这种革命性的技术改变了一切。
eBPF 是什么?
eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in a privileged context such as the operating system kernel. It is used to safely and efficiently extend the capabilities of the kernel without requiring to change kernel source code or load kernel modules.
简单来说,eBPF 是一个运行在 Linux 内核里面的虚拟机组件,它可以在无需改变内核代码或者加载内核模块的情况下,安全而又高效地拓展内核的功能。
eBPF 的前身是 BPF(Berkeley Packet Filter) 技术,它原始的含义是 extended BPF。BPF 是一种古老的技术,最早可以追溯到 1992 年,它是为捕捉和过滤网络数据包而设计的,鼎鼎大名的抓包软件 Wireshark 就是基于它实现的。然而,经过若干年的发展,eBPF 早已脱胎换骨,成为 Linux 内核可观测技术事实上的标准。
在 eBPF 之前,给内核拓展功能非常麻烦。由于内核是硬件资源的管理者,其对安全性和稳定性的要求非常高,所以它的功能迭代周期相比应用程序慢得多。如果想要在内核中添加新功能,要么就直接修改内核代码,要么通过内核模块(LKM)实现。修改源码的话,自己维护成本高,进主干发布周期长,改完替换内核还有风险;用内核模块的话,由于 Linux 内核不提供稳定的 API,其维护成本会很高,内核模块在面临不同的内核版本时会面临巨大的困难。
eBPF 通过在内核中实现一个轻量级虚拟机,可以动态地加载和运行自定义的程序,通过这个自定义的程序可以轻松地拓展内核的功能。虚拟机在运行程序之前会进行校检,可以确保其不会导致 panic(回想一下内核模块,一不小心系统分分钟重启给你看..)另外,在 BTF 的加持下,eBPF 程序可以在跨内核版本上做兼容(内核模块再次泪目…
扯了这么多,有人会说,这玩意不就是一个运行在内核里面的解释器嘛。。就像运行在浏览器里面的 Javascript 引擎一样,有什么好稀罕。这个类比好像没毛病,但由于它运行在内核里,Linux 上独此一家,因此得万千宠爱于一身。因为,Linux 内核它真的太需要一个虚拟机了…
为什么是现在?
eBPF 技术最早在 Linux 3.15 被引进,从时间上来看已经不算是一种新技术了,而且它在服务端的应用已经有很长的历史,为何到现在咱才想起它呢?
这玩意它首先得益于云原生技术的蓬勃发展,不过跟咱要讲的 Android 没啥关系,暂且不表。对 Android 系统来说,GKI(General Kernel Image) 的出现,让 eBPF 登上了历史舞台。
GKI——通用内核镜像,是 Google 为了解决 Android 碎片化而提出的一种技术。
在 GKI 之前,Android 内核的碎片化非常严重,不同的设备制造商、不同的设备型号,甚至不同的设备版本,其运行的内核代码都不一样;这就导致在内核里面添加功能维护成本巨高,进而使得内核升级几无可能(回想一下以前的 Android 设备,其内核版本出厂之后除了安全补丁,基本上被锁死了)
而 eBPF 作为内核的一个功能,它的启用需要依赖特定的内核编译配置,如果某个内核没有开启某编译选项,eBPF 可能就用不了;在 Android 内核碎片化的年代,想要搞到能完整支持 eBPF 的设备,那是挺难的;如果想用 eBPF 这个功能,只有自己去改内核代码然后自己编译,然而你会遇到各种设备驱动问题,比如触屏失灵,Wifi 不工作等等等等。。当你好不容易在自己设备上折腾好,想要在别的设备上运行时,哦豁,它不支持 BTF,你的 eBPF 程序跑不了。。
GKI 通过统一核心内核,把其他功能(如 SoC,ODM 等提供的)从内核剥离并提供稳定接口(KMI),一举解决了碎片化问题。并且,Google 强制要求,Android 12 以上版本的设备,出厂必须使用 GKI 内核。
更重要的是,GKI 内核的编译选项,完整支持 eBPF 的几乎所有功能!!也就是说,你拿到一个 GKI 的设备,无需自己编译内核代码,它必然支持 eBPF;你在一个 GKI 设备上编写 的 eBPF 程序,可以轻松地拓展到其他的 GKI 设备!
所以现在这个时间点,在 Android 12 已经发布,Android 13 已经 beta 的情况下,是时候开始学习和使用 eBPF 了。
eBPF 能干什么?
长篇大论了这么多,把...
剩余内容已隐藏