14 个版本
新版本 0.2.2 | 2024 年 8 月 17 日 |
---|---|
0.2.0 | 2024 年 6 月 14 日 |
0.1.7 | 2024 年 3 月 30 日 |
0.1.5 | 2023 年 8 月 11 日 |
0.0.0 | 2021 年 7 月 17 日 |
#112 在 异步
145 每月下载量
450KB
10K SLoC
A10
A10 是一个针对荷兰 A10 高速公路的 io_uring 库。
这个库旨在作为低级库安全地公开 io_uring API。为了简单起见,它只包含两种主要类型和一些辅助类型
Ring
是 io_uring 的包装器,用于轮询完成事件。AsyncFd
是文件描述符的包装器,提供了一个安全的 API 来调度操作。
Linux 要求
目前这需要相当新的 Linux 内核版本,Linux v6.1 及以上版本应该都能正常工作。
示例
A10 预计将被集成到 Future
运行时中,但它也可以作为一个独立的库使用。
use std::future::Future;
use std::io;
use std::path::PathBuf;
use a10::{AsyncFd, Extract, Ring, SubmissionQueue};
fn main() -> io::Result<()> {
// Create a new I/O uring supporting 8 submission entries.
let mut ring = Ring::new(8)?;
// Get access to the submission queue, used to... well queue submissions.
let sq = ring.submission_queue().clone();
// A10 makes use of `Future`s to represent the asynchronous nature of
// io_uring.
let future = cat(sq, "./src/lib.rs");
// This `block_on` function would normally be implement by a `Future`
// runtime, but we show a simple example implementation below.
block_on(&mut ring, future)
}
/// A "cat" like function, which reads from `filename` and writes it to
/// standard out.
async fn cat(sq: SubmissionQueue, filename: &str) -> io::Result<()> {
// Because io_uring uses asychronous operation it needs access to the
// path for the duration the operation is active. To prevent use-after
// free and similar issues we need ownership of the arguments. In the
// case of opening a file it means we need ownership of the file name.
let filename = PathBuf::from(filename);
// Open a file for reading.
let file: AsyncFd = a10::fs::OpenOptions::new().open(sq.clone(), filename).await?;
// Next we'll read from the from the file.
// Here we need ownership of the buffer, same reason as discussed above.
let buf = file.read(Vec::with_capacity(32 * 1024)).await?;
// Let's write what we read from the file to standard out.
let stdout = a10::io::stdout(sq);
// For writing we also need ownership of the buffer, so we move the
// buffer into function call. However by default we won't get it back,
// to match the API you see in the standard libray.
// But using buffers just once it a bit wasteful, so we can it back
// using the `Extract` trait (the call to `extract`). It changes the
// return values (and `Future` type) to return the buffer and the amount
// of bytes written.
let (buf, n) = stdout.write(buf).extract().await?;
// All done.
Ok(())
}
/// Block on the `future`, expecting polling `ring` to drive it forward.
fn block_on<Fut, T>(ring: &mut Ring, future: Fut) -> Fut::Output
where
Fut: Future<Output = io::Result<T>>,
{
use std::ptr;
use std::task::{self, Poll, RawWaker, RawWakerVTable};
// Pin the future to the stack so we don't move it around.
let mut future = std::pin::pin!(future);
// Create a task context to poll the future work.
let waker = unsafe { task::Waker::from_raw(RawWaker::new(ptr::null(), &WAKER_VTABLE)) };
let mut ctx = task::Context::from_waker(&waker);
loop {
match future.as_mut().poll(&mut ctx) {
Poll::Ready(result) => return result,
Poll::Pending => {
// Poll the `Ring` to get an update on the operation(s).
//
// In pratice you would first yield to another future, but
// in this example we don't have one, so we'll always poll
// the `Ring`.
ring.poll(None)?;
}
}
}
// A waker implementation that does nothing.
static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
|_| RawWaker::new(ptr::null(), &WAKER_VTABLE),
|_| {},
|_| {},
|_| {},
);
}
更多示例请参见示例目录。
依赖项
~360KB