Ringbahn:Rust中io-uring的安全符合人体工程学的API

在我以前的文章中,我讨论了Linux的新io-uring接口,以及如何创建用于使用Rust的io-uring的安全API。 自从发布该文章以来,我已经实现了此类API的原型。 该create称为Ringbahn,旨在使用户能够在io-uring机制上使用IO,而不会造成内存不安全的风险。

对于将来在Linux上开发异步IO来说,io-uring将变得至关重要。Linux是Rust的主要用户,用于各种高性能网络服务的最常用平台。

Rust不仅需要有一种使用iouring的解决方案,而且还应该拥有最好的解决方案。

用户应该能够从符合人体工程学,内存安全且抽象良好的API轻松访问iouring的全部功能。 Ringbahn尚未做好生产准备,但正朝着这个目标前进。

这是一个代码示例,展示了在Ringbahn上使用IO的过程有多么容易:

use std::fs::File;
use futures::io::AsyncRead;

use ringbahn::Ring;

async fn read_on_ringbahn(file: File, buf: &mut [u8]) -> io::Result<usize> {
    // the `Ring` adapter takes a std IO object and runs its IO on io-uring
    let mut file: Ring<File> = Ring::new(file);
    file.read(buf).await
}

我希望写几篇文章来介绍ringbahn的内部结构和设计。

Events(事件), drivers(宏), and submissions

Ringbahn’s core concept is the Submission future, which allows users to safely run an IO event on an io-uring instance managed by a driver.

Submission provides a contract between two types:

Events: are IO events (like reads and writes) which can be scheduled on an io-uring instance.
Drivers: implement the Drive trait and manage an io-uring instance onto which events can be scheduled.

Ringbahn的核心概念是Submission future,它使用户可以在io-uring管理的实例上安全地运行IO事件。

Submission提供两种类型的合同:

  • Events(事件):被io-uring调度的IO事件(如读取和写入事件)。
  • Drivers:实现Drive trait并管理一个被io-uring实例调度的事件。

Submission类型负责处理离散IO事件和io-uring实例管理的 的driver之间的交换,以使接口具有内存安全性和直接性。

一旦driver有足够的空间来提交事件,则Submission future会管理事件的提交,并准备一个内部完成事件,以在事件完成时可以唤醒自己。
( TODO preparing an internal Completion to wake itself when the event completes.)

什么是driver

Ringbahn最重要的创新之一是driver的概念,它可以管理一个io-uring实例。

实际上,ringbahn并不仅仅是为io-uring提供接口,它还允许最终用户替换默认driver并对其IO的调度方式进行低级控制。

driver负责三件事:

  • 准备提交队列中的一个点以提交事件。如果没有空间提交其他事件,则Drivers可以进行队列背压。
  • 在提交队列中提交所有准备好的事件。同样,如果需要,drivers也可以在此处进行背压。
  • 处理完成队列中的所有事件,以便将它们传递给complete函数将其唤醒。

有多种不同的模式可用于实现的drivers并确定其实际提交事件和处理其完成的方式。通过将其作为一个抽象点,用户将能够试验不同的drivers模式并确定它们在不同种类的负载下的性能,而无需自己重写所有内存安全方面。

当前版本的ringbahn包含一个演示driver,该driver适用于测试目的,但不是性能最高的实现。在以后的文章中,我将探讨driver的不同设计以及如何实现它们。

The event contract and ownership lifecycle

contract的也是一个事件类型,它表示安排一个被io-uring调度的单个IO事件。

Event trait有一个有趣的特点:它包含一个不安全的方法Event::prepare。

重要的是要了解unsafe方法的含义:unsafe方法是不安全的调用-这意味着调用方在调用它们时必须保证其他不变式。但是,它们是可以安全实施的(只要您在其中没有做任何不安全的事情)。
因此,当用户实现Event trait时,他们将给出关于如何调用此方法的其他保证,这通常是他们无法设定的。(they are given additional guarantees about how this method will be called that normally they could not assume.)

具体来说,可以保证在调用此方法后,直到准备好的事件完成或取消后,事件类型才可以再次访问。
换句话说,它的作用就像 Event::prepare将事件的所有权“传递给内核”。
这样,Event的实现者可以安全地赋予缓冲区缓冲区内核所有权,而不必担心程序的其他部分会访问缓冲区。

事件API还支持取消API,该API构造了“取消回调”。从高层次上讲,概念是这样的:如果用户取消某事件的兴趣,则构造取消回调。事件结束后,无需取消用户的任务(不再关心该事件),而是调用取消回调来清除内核已拥有的所有对象。

这意味着事件API为调度IO事件提供了一种内存安全的抽象,它完全支持取消操作而不会泄漏任何资源。

Ring接口和缓冲区管理

Ringbahn中最高级的接口是Ring类型。受smol的Async类型启发,它包装了一个标准IO对象并使用io-uring对它执行IO,而不是阻塞IO。

Ring类型的行为类似于反复向其io-uring驱动程序提交事件的类型(在内部,它不直接使用Submission和Event API,但是代码从根本上反映了这些API)。

Ring实现了AsyncRead,AsyncWrite和AsyncBufRead。

当前,Ring类型使用其自己的托管缓冲区提交事件。但是,从长远来看,Ring最好是在多种不同的缓冲区管理策略上抽象的-尤其是包括那些将缓冲区预先注册到内核的策略,这将是最佳方法。

Ring将提供使用io-uring执行异步IO的简单明了的方式,并且将始终是最易于访问的高级API。用户可以选择使用较低级别的Event API来构建更复杂的IO模式。

下一步

Ringbahn目前只是一个原型,这就是为什么我将其以0.0.0-experimental.1的形式发布到crates.io的原因。 它包含许多不安全的代码,尚未经过充分的审核或强化,因此用户不应该在生产中使用它。

在不久的将来,我想更深入地介绍Ringbahn设计的各个方面。 我希望这将有助于将Rust的继续支持的future引向富有成效的方向。 预计在接下来的几周中,会在完成状态机,driver接口,缓冲区管理等方面看到一系列文章。

从长远来看,我希望在Rust中存在一个生产就绪的,同类最佳的iouring接口。 但是,我不知道我将有多少时间致力于该项目,或者它是否会长期持续发展。 如果有人有兴趣认真资助这项工作,我将有兴趣私下听取他们的意见。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐