目录

Paper Reading: Making Kernel Bypass Practical for the Cloud with Junction

Making Kernel Bypass Practical for the Cloud with Junction

NSDI 24 的论文

motivation 还是和以前的类似,OS kernel 比如 TCP 栈带来了 IO overheads,在 cloud/datacenter 有很多 OS bypass 技术,但目前 cloud 很难实现

原因主要是 density 和 compatibility 只有少量应用可以

Github Repo: https://github.com/JunctionOS/junction

Abstract

内核旁路系统相对于传统的操作系统(OSes)在网络密集型应用的吞吐量和尾部延迟方面展示了数量级的改进。然而,为了实现如此卓越的性能,它们依赖于专用资源(例如,spinning cores, pinned memory),并需要应用程序的重写。这对于云运营商来说是不可取的,

由于这两个原因,现有的内核旁路技术在云环境中是不切实际的。在本文中,我们展示了这些妥协并非解锁内核旁路全部好处的必要条件。我们提出了 Junction,这是第一个能够在机器上密集打包数千个实例,同时提供与未修改的 Linux 应用程序兼容性的内核旁路系统。

Junction 的扩展能力比现有的内核旁路系统高出 19-62 倍,并且可以在不进行代码更改的情况下实现相似或更好的性能。此外,Junction 为以前不支持内核旁路的应用程序带来了显著的性能提升,包括那些依赖于 Go、Java、Node 和 Python 等运行时系统的应用程序。在与原生 Linux 的比较中,Junction 在七个应用程序中将吞吐量提高了 1.6-7.0 倍,同时使用的核心数量减少了 1.2-3.8 倍。

性能很夸张

Introduction

网络密集型应用从内核旁路系统中获得了显著的性能提升(即尾部延迟和吞吐量的数量级改善)。其核心思想是将网络队列映射到用户空间,使得应用程序可以直接与网卡通信,从而避免内核开销。

Junction 是一个针对云应用(如微服务、无服务器等)的新型内核旁路系统,与之前的内核旁路系统类似,Junction 提供了显著的性能提升,包括更高的吞吐量和更大的 CPU 效率,以及相对于传统操作系统的尾部延迟数量级的减少。

Junction 在保持应用程序之间严格隔离的同时,提供了比现有云隔离方案更窄的攻击面。

性能指标:吞吐量,CPU 效率,尾部延迟

之前的内核旁路系统做出了一些妥协,使得它们在云环境中不切实际。例如,它们需要专用的、繁忙旋转的核心和固定内存,因此在机器上只能打包非常少的实例。此外,它们对编程模型进行了重大更改[6, 14, 21, 33, 52],这破坏了兼容性并牺牲了现有软件的巨大投资。最后,大多数内核旁路系统本身不提供隔离,因此必须与虚拟化结合(及其相关的开销,如 VM 退出成本、嵌套页表等)才能在云环境中安全部署。

Junction 通过一系列设计贡献,在不做出这些妥协的情况下保留了内核旁路的全部性能优势,这些贡献针对隔离、密度和兼容性。为了在避免虚拟化开销的同时实现强隔离,Junction 在每个实例内部运行一个普通的 Linux 进程,并安装一个限制系统调用访问的过滤器。在实例内部,Junction 作为一个库运行,与应用程序共享一个地址空间。由于 Junction 能够在内核旁路硬件(如网卡队列、CPU 特性等)之上构建其所有操作系统抽象,因此它只需要与内核进行最少的交互,仅足以实现资源复用(≈ 十几个系统调用)。

为了实现高密度,Junction 高效地复用了核心和内存。

为此,Junction 创新地使用了网卡硬件特性,该特性在专用队列上传递数据包到达通知,而不是要求每个接收队列单独轮询。

Junction 还安全地暴露了 Linux 页面缓存,以在实例之间共享只读内存

pull -> push

为了实现 Linux 兼容性,Junction 提供了 Linux 内核系统调用接口的自有实现。这在保持内核旁路性能优势的同时是一项挑战。Junction 利用其与应用程序运行在同一地址空间的事实,解锁了最小化兼容性成本的优化。例如,Junction 安全地将系统调用转换为函数调用,避免瞬态执行缓解,直接访问参数而不复制它们,利用未定义行为消除锁定,并使用向量指令而不需要保存和恢复寄存器状态。Junction 还提供了现有内核旁路系统中缺失的许多操作系统特性(如信号、线程本地存储、随机性、文件系统、定时器等),但这些特性对于支持云应用至关重要。它通过利用现代 CPU 扩展来避免陷入内核,从而保持了内核旁路的方法来提供这些特性。

和应用程序在同一地址的好处是什么

这样的云相关的设计,在个人看来是比较优秀的,保证兼容性做性能,更加实际

Background & Motivation

内核旁路系统消除了内核在网络数据路径中的角色,并将其替换为一个优化的用户级网络栈,该栈直接与网卡通信。在本节中,我们首先讨论为什么现有的内核旁路方法存在缺陷,阻碍了其在云环境中的采用,特别是在密度和兼容性至关重要的云环境中。接下来,我们讨论当前使内核旁路更具通用性的进展。最后,我们讨论缺乏安全性如何进一步加剧这些问题。

Density challenges 不幸的是,当前的内核旁路系统只能在机器上支持有限数量的实例。一个主要问题是广泛使用繁忙旋转和专用核心[30, 67]。因为这种方法要求每个实例至少一个核心(通常更多),所以实例的最大数量受限于核心数量。

新的 CPU 调度方法通过加快核心分配可以克服这一限制,并在不牺牲尾部延迟的情况下消除繁忙旋转的浪费[15, 42, 50]。然而,这些系统依赖于专用核心来进行调度决策,因此其可扩展性仍然有限。例如,Caladan 由于其调度器核心的瓶颈,无法扩展到数百个实例以上。

最后,内核旁路系统的内存占用也是实现高密度的一个重大障碍。

Compatibility challenges. 理想情况下,内核旁路系统应支持未修改的二进制文件。现有的内核旁路系统反而要求开发人员移植应用程序

Making kernel bypass general purpose. 最近有几个努力使内核旁路更具通用性。例如,通常内核旁路系统使用 run-to-completion 来优化短请求[6],但这在请求服务时间分散时会导致高尾部延迟。Shinjuku[29]和 Perséphone[9]分别通过高效、细粒度的抢占和将短请求引导到单独的核心来解决这个问题。许多内核旁路系统还使用“无共享”设计,这在负载不平衡时会损害尾部延迟,ZygOS 通过工作窃取解决了这个问题[47]。Demikernel 在不同的硬件后端(如 RDMA 与以太网)之上提供了统一的抽象,以减少开发人员的工作量[67]。最后,Arachne[50]和 Shenango[42]展示了线程化可以变得足够快,以便与内核旁路网络一起使用。Junction 采用了这些系统中的几个想法,同时解决了之前未解决的密度和兼容性挑战。

Security challenges 大多数内核旁路系统回避隔离,必须以 root 权限运行。因此,它们依赖其他隔离机制才能在云中安全部署。最可行的选择是让每个实例在单独的虚拟机中运行,但这会增加开销,包括额外的 TLB 未命中、VM 退出成本和由客户内核引起的更大的内存占用。在大多数情况下,虚拟机也无法利用页面缓存,这进一步限制了它们的密度。

Junction Overview

Junction 的概述,并突出了其主要组件。Junction 设计用于在机器上处理数千个实例。一个实例是一个隔离的容器,运行一个或多个应用程序二进制文件。从主机内核的角度来看,这个容器由一个单一进程(称为 kProc)和一组固定的线程(称为 kThreads)组成,这些线程在启动时静态初始化。kThreads 由一个集中式调度器调度到核心上(图 1 的左侧)。一个实例可以在其共享地址空间中加载和运行多个二进制文件(将每个二进制文件放置在虚拟内存的不同偏移量处)。实例中的每个二进制文件在一个称为 uProc 的用户空间进程抽象中运行。

https://s2.loli.net/2024/10/21/PzlSBdOfUe87oL9.png

Junction 内核的副本在每个实例内部运行,并与 uProcs 共享一个地址空间。它直接处理来自 uProcs 的系统调用,并在用户空间中提供操作系统抽象(如线程、网络、文件系统、信号等),类似于库操作系统[10, 46]。Junction 内核支持 Linux 系统调用接口,因此它可以运行现有软件而无需修改。

Junction 内核使用内核旁路硬件

网络和通信。与其他内核旁路系统类似,Junction 实例为每个 kThread 提供一对 NIC 发送和接收队列。这通过允许并发访问 NIC 而无需同步来提高性能。Junction 内核提供了一个高性能的 TCP/IP 和 UDP 网络栈,使 uProcs 能够与外界通信。同一实例中的 uProcs 可以使用标准的进程间通信(IPC)原语(如管道)相互通信,但同一主机上的不同实例只能通过 NIC 的环回网络进行通信。

线程。Junction 内核包含一个高性能的用户级线程库,该库使用工作窃取来平衡轻量级用户级线程(uThreads)在 kThreads 之间的负载。uProc 线程(即在启动时或通过 clone3()创建的线程)映射到 uThreads。uThreads 还用于各种内部任务,如网络协议处理。每个 kThread 运行一个调度循环,轮询本地队列中的数据包和超时,并运行挂起的 uThreads。

核心调度。Junction 依赖于微内核风格的调度器来进行核心分配决策[15, 23, 42, 48]。它在专用核心上运行,并繁忙轮询控制信号以决定何时以及如何将核心分配给每个实例(如图 1 中的红色双箭头所示)。实例在空闲时可以使用少至零个核心,如果需求合理,则可以使用多个核心(直至每个实例的限制)。对于每个实例,调度器监控线程和网络队列中的计时器到期和排队延迟,这些信息通过共享内存对调度器可见。当调度器将核心授予实例时,它会从中选择一个空闲的 kThread,并将其固定在该核心上,直到授予结束

除了执行核心分配外,调度器还通过发送用户 IPI(UIPI)[25]来协助线程库实现细粒度的时间片轮转,以处理运行 uThread 已超过其时间片量子的核心。这确保了所有 uThreads 都能取得进展,并且数据包队列能及时清空。当服务时间分散度高时,这对减少尾部延迟也是有利的[29]。作为一种优化,这些中断仅在有排队数据包或可运行线程等待处理时发送。

由于 Junction 旨在支持数千个实例,它采用了新颖的技术来确保控制信号能够以可扩展的方式进行监控。计时器轮跟踪每个实例的下一个超时时间,而 NIC 事件队列提供数据包到达的通知。这些减少了调度器必须轮询每个实例内部共享状态以确定其是否可以从额外核心中受益的频率。我们在第 5 节中更详细地讨论了这些优化。

Security

传统观点认为,应使用虚拟机作为云的隔离边界,以减少不受信任代码与主机内核之间的交互。然而,虚拟化仍然会执行主机内核中大量受信任的代码,从而导致显著的攻击面[3, 65]。相比之下,Junction 直接在内核旁路硬件之上提供操作系统抽象,显著减少了对主机内核的依赖。在本节中,我们将更详细地讨论 Junction 的安全设计。

没懂

Threat Model

Host Kernel Isolation

Page cache?

Optimizing for Density

Junction 的目标是提供高网络吞吐量和低延迟,类似于现有的内核旁路系统,同时还能在机器上打包更多的实例。这要求我们解决与使用大量 NIC 接收队列相关的几个问题。

仍然必须为每个实例分配足够的接收队列,以便每个可能运行的 kThread 都有一个可用的队列。因此,所需的队列数量是每个实例的最大核心数乘以实例数量。

现代网卡可以轻松扩展到数千个队列,但使用它们在机器上打包许多实例仍然面临重大挑战。首先,缓冲区内存消耗是密度的关键限制,因为每个接收队列必须发布足够的缓冲区以容纳最坏情况下的数据包突发,这在核心之间的流量不均匀时会加剧。其次,核心调度器轮询每个队列的成本变得过高,缓存污染导致性能崩溃。我们现在更详细地讨论我们针对这些问题的解决方案。

队列数量很多

Minimizing Buffer Memory Consumption

Scalable Queue Polling

首先,它以新颖的方式使用一组网卡特性来避免持续轮询空闲网络队列。Junction 为调度器核心分配了一个单一的事件队列和专用的门铃页面。每次调度器观察到一个空接收队列时,它会通过标记当前头指针的索引并写入门铃来武装队列。当数据包到达一个武装队列时,网卡将一个事件写入事件队列并解除队列的武装。调度器不断轮询事件队列,并在数据包到达空闲队列时立即做出反应。此特性在现代 Mellanox 网卡上可用。

硬件限制必须用新的?

Linux Compatibility

Adapting OS Features to Kernel Bypass

ELK 加载器

vfork()

Performance Optimizations

seccomp 拦截

Implementation

Evaluation

https://s2.loli.net/2024/10/21/ZMuNo4jpFgyeX7w.png

这里的几个例子还是挺有意思的

Methodology

只说了应用,但是怎么没说开了多少 instances?

Comparison to other kernel bypass systems

Density

横坐标是 number of applications

Linux 内存占用几乎不动

但是 FireCracker, Caladan 占用很高

Junction 呈线性增加,为什么会增加呢?队列?

Compatibility

Attack Surface

Junction 使用内核旁路技术减少了相对于现有以安全性为重点的库操作系统的主机内核攻击面。

能提供 attack 安全性的测试倒是很有意思

Performance Analysis

Performance optimizations

Pinned memory

Discussion

硬件限制,CPU、NIC

Conclusion

..

囫囵吞枣

看不太懂里面的一些设计,看完也不知道怎么实现的内核旁路,更像是 userspace 之类的

evaluation 也没比较 cpu 占用率什么的

每个核心一个 buffer queue 改成了 shared buffer queue

兼容性和 density 应该是最亮眼的,性能可能受硬件限制很严重

论文太乱了,感觉他们设计应该是很多东西可以讲的

Appendix UIPI Support