Hero Image
RISC-V eBPF 系列 1:eBPF 简介

前言 本文会对 eBPF 技术进行初步的介绍,包括基本原理,相关基础设施以及主要的应用场景,帮助读者对 eBPF 技术有一个总体的认识,了解涉及的众多概念,方便后续需要深入了解时知道要查找什么内容。 背景 BPF BPF (Berkeley Packet Filter) 是类 Unix 系统上数据链路层的一种原始接口,提供原始链路层封包的收发。Linux 内核采用 BPF 作为网络数据包过滤技术。 包括像 tcpdump 的底层也是采用 BPF 作为底层包过滤技术。 eBPF eBPF 是一项革命性技术,它能在内核中运行沙箱程序(sandbox programs),而无需修改内核源码或者加载内核模块。 eBPF (extended Berkeley Packet Filter) 是在 BPF 基础上经过重新设计,逐步演进为一个通用执行引擎,允许用户在操作系统的内核中加载和运行自定义程序,可用于扩展甚至修改内核行为。 eBPF 最早出现在 3.18 内核版本中,此后原来的 BPF 就被称为经典 BPF,缩写为 cBPF(classic BPF)。 Why eBPF Linux 内核的主要作用是管理硬件或虚拟硬件,并提供一致的 API(系统调用),以实现应用程序运行和共享资源。Linux 划分了一组子系统来分别管理。每个子系统一般在一定程度上都提供了可配置的功能,来满足用户的不同需求。如果满足不了需求,则需要对内核进行更改,可以通过直接修改子系统或者编写一个内核模块两种方式,但都存在痛点: 直接给对应子系统提交 Patch: 需要说服 Linux 内核社区相信更改是有必要的 新的内核版本一般需要几年时间才能够商用 编写一个内核模块: 需要定期修复内核模块,因为每个内核版本都可能损坏该模块 缺乏安全边界,可能会损坏 Linux 内核 而使用 eBPF,可以使用一种新方法来重新对内核行为进行编程,而无需更改内核源码或者加载内核模块。 基本概念和原理 Hook(钩子) eBPF 程序是事件驱动的,当内核或应用程序执行到某个钩子时运行。预定义的钩子包括系统调用,函数进入/退出,内核跟踪点,网络事件等。如果预定义的钩子不满足需求,可以创建 kprobe 或 uprobe 来作为自定义钩子触发 ePBF 程序。

Hero Image
RISC-V Syscall 系列 3:什么是 vDSO?

概述 本文阐述了什么是 vDSO 技术,以及该技术解决的问题是什么,解决效果如何,并举例说明用户程序如何使用它。 说明:文中涉及的 Linux 源码是基于 5.17 版本 背景 在 Linux 众多的系统调用中,有一部分存在以下特点: 系统调用本身很快,主要时间花费在 trap 过程 无需高特权级别权限 这部分系统调用如果能够直接在用户空间中执行,则能够对性能有较大的改善。gettimeofday 就是一个典型的例子,它仅仅只是读取内核中的时间信息,而且对于许多应用程序来说,读取系统时间是必要的同时也是频率很高的行为。 为了改善这部分系统调用的性能,先后出现了 vsyscall, vDSO 机制来加速系统调用。 vsyscall vsyscall 或 virtual system call 是第一种也是最古老的一种用于加快系统调用的机制,最早在 Linux 2.5.53 被引入内核。vsyscall 的工作原则其实十分简单。Linux 内核在用户空间映射一个包含一些变量及一些系统调用的实现的内存页。因此这些系统调用将在用户空间下执行,而不需要触发 trap 机制进入内核。 但是 vsyscall 存在以下问题: vsyscall 映射到内存的固定位置 ffffffffff600000 处,有潜在的安全风险 vsyscall 内存页不包含符号表等信息,在程序出错时进行 core dump 会比较麻烦 为了解决上述问题,从而设计了 vDSO 机制,也就是本文讨论的主题。 vDSO vDSO (virtual dynamic shared object) 也是一种系统调用加速机制。vDSO 和 vsyscall 的基本原理类似,都是通过映射到用户空间的代码和数据来模拟系统调用,来达到加速的目的。而它们的主要区别在于: vDSO 是一个 ELF 格式的动态库,拥有完整的符号表信息 依赖 ASLR 技术,对 vDSO 的地址进行随机化 linux-vdso.

Hero Image
RISC-V Syscall 系列 2:Syscall 过程分析

概述 本文主要对 Linux 在 RISC-V 架构下的 Syscall 机制进行分析,探究计算机是如何一步一步从应用程序开始,到执行 Syscall,最后返回应用程序的全过程。 文章因涉及技术术语,建议读者阅读本文前先熟悉以下知识: Syscall 概念 RISC-V 规范 C 语言 说明:文中涉及的 Linux 源码是基于 5.17 版本 Syscall 开始 首先,Syscall 是如何开始的呢?当应用程序需要使用操作系统提供的一系列功能时,一般会通过操作系统提供的 C 标准库来进行 Syscall 的调用。而实际上,C 标准库的内部则使用了 ecall 指令来触发了整个 Syscall 流程。 ecall 先介绍一下 ecall 指令。ecall 指令以前叫做 scall,用于执行环境的变更,它会根据当前所处模式触发不同的执行环境切换异常: in U-mode: environment-call-from-U-mode exception in S-mode: environment-call-from-S-mode exception in M-mode: environment-call-from-M-mode exception Syscall 场景下是在 U-mode(用户模式)下执行 ecall 指令,主要会触发如下变更: 处理器特权级别由 User-mode(用户模式)提升为 Supervisor-mode(内核模式) 当前指令地址保存到 sepc 特权寄存器 设置 scause 特权寄存器 跳转到 stvec 特权寄存器指向的指令地址 简单来说,ecall 指令将权限提升到内核模式并将程序跳转到指定的地址。操作系统内核和应用程序其实都是相同格式的文件,最关键的区别就是程序执行的特权级别不同。所以 Syscall 的本质其实就是提升特权权限到内核模式,并跳转到操作系统指定的用于处理 Syscall 的代码地址。