引言
最近在阅读 《Linux内核观测技术BPF》这本书,非常薄的一本书却能带领我走入 Bpf 技术的世界,但是它只能起到一个入门的作用,用于工作的时候还是有点捉襟见肘,这篇文章只能当成一个较为深入的理解,不能取代系统性的学习。
当我们使用网络应用时,性能和可靠性是至关重要的。然而,由于内核网络栈的设计和实现方式,传统的用户空间网络应用在处理大量网络流量时会面临性能瓶颈。为了解决这一问题,Linux 内核引入了一种名为 eBPF(Extended Berkeley Packet Filter)的技术,它允许开发人员在内核中运行自定义的代码,从而提高网络应用程序的性能和灵活性。而下面要重点提到的 XDP(eXpress Data Path)是 eBPF 的一个子集,提供了一种快速的数据处理方法,它通过在数据包接收阶段运行代码来提高网络应用的性能。
这里有个点需要注意一下,eBPF 和 BPF 都是一种可以在Linux内核中使用的虚拟机技术,但是它们有一些不同之处,BPF 最初是一种用于数据包过滤的技术。随着时间的推移,它的功能逐渐扩展,被用于在内核中运行的各种任务,例如系统跟踪和安全审计等。BPF 程序在内核中直接执行,它可以访问内核数据结构,但是它的功能和灵活性受到了一些限制。
而 eBPF 是对 BPF 的扩展,即 “extended BPF”。它通过引入一个新的指令集,允许在内核中运行的 BPF 程序更加灵活和强大。eBPF 可以在安全的环境下执行用户定义的代码,而不会影响内核的稳定性和安全性。eBPF 还支持在内核和用户空间之间进行通信,并提供了一些新的功能,例如支持用户定义的映射和钩子程序。
总的来说,eBPF 是 BPF 的一个更加灵活和功能强大的版本,它为内核和用户空间提供了更多的通信和交互能力,同时保持了与内核的兼容性和安全性。我们入门的话会从 BPF 开始进行解析。
微服务时代Linux内核的问题
如果论起 eBpF 最出名的案例,莫过于 Cilium 了,我参考了 Cilium 项目发起人的演讲《How to Make Linux Microservice-Aware with Cilium and eBPF》
,在云原生时代,为什么 eBpf 至关重要。而他其中列出了四个为什么当前 Linux 内核不适合当前的时代(背景为内核尚未对云原生做支持的时代)。
Abstractions 抽象
上图是一张和网络相关的抽象,它清晰地展示了 Linux 内核中各个部分的抽象,例如如果我们想用 Netfilter 做包过滤,那就必须要经过 socket 和 TCP 协议栈。而如果从网卡往上看的话,包需要经过 Netdevice -> traffic shaping -> Ethernet 等等才能到达上部分应用。
这种抽象会带来几方面的好处:
- 用户空间 API 强兼容性
- 使得大部分的内核代码不依赖硬件
相应的,抽象带来的坏处也有:
- 巨大的性能开销
- 很难绕过抽象层,即需要层层递进
Per Subsystem APIs 子系统
第二个问题为每个子系统都有自己的
用户态和内核态?
在关于 Linux 的技术讨论中,我们常常会脱口而出两个名词,即用户态
和内核态
,这其实是操作系统中两种不同的运行级别,用以区分应用程序和操作系统内核的执行上下文。
在用户态下,应用程序运行在自己的地址空间中,使用用户空间的资源,例如CPU、内存等,期间应用程序可以在受到操作系统的保护和限制下执行逻辑操作,但不能直接访问系统资源和硬件设备,必须借助操作系统进行调用。
而在内核态下,操作系统内核运行在特殊的内核地址空间中,具有更高的特权级别,可以访问系统的全部资源和硬件设备。内核可以执行各种系统级别的任务,例如管理进程、文件系统、网络协议栈等等。
当应用程序需要执行一些需要系统资源或硬件设备支持的操作时,例如文件访问、网络通信等,就需要切换到内核态来执行相应的系统调用,操作系统内核会代表应用程序执行相关的操作,并返回结果到用户态。这种切换会带来额外的开销和延迟,因此需要尽可能减少。
当然,你可能会有点疑惑,应用程序是如何指挥操作系统进行借代访问内核态的资源特权,或者中间的过程是什么样的。
用户态进程会通过系统调用
主动要求切换到内核态,其本质就是操作系统为用户提供的一系列API,系统调用将用户的请求发给内核,内核执行完以后,将结果返回给用户,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如 fork
实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还使用了操作系统为用户特别开放的一个中断来实现,例如 Linux 的 Int0X80 软中断。
总结一下:当用户进程发起系统调用时,会通过软中断将 CPU 的控制权交给内核,即从用户态切换到内核态。在内核态中,内核会根据系统调用号及其参数执行相应的操作,完成服务的提供。当内核完成服务后,再通过系统调用返回值将结果返回给用户进程,并将 CPU 的控制权交还给用户进程,即从内核态切换回用户态。
注意在用户态和内核态之间切换时,需要经过一定的开销。当应用程序需要执行系统调用时,需要将控制权交给操作系统,这个过程需要切换到内核态,需要保存当前应用程序的上下文信息,然后将控制权交给内核,内核执行相应的操作后,再将控制权交还给应用程序,这个过程叫做上下文切换。由于上下文切换需要保存和恢复大量的上下文信息,所以会产生一定的开销,从而降低系统的效率。