---
title: 为什么使用 AsyncRent 作为 IO 抽象
date: 2021-11-24 20:00:00
author: ihciah
---

# 为什么使用 AsyncRent 作为 IO 抽象

我们使用 AsyncRent 作为 IO 抽象。

## io_uring 的需要
1. buffer 的位置固定

因为我们只是将 buffer 提交给 kernel，我们不知道 kernel 什么时候会向其中写数据或从中读数据。我们必须保证在 IO 完成前，buffer 地址是有效的。

2. buffer 声明周期保证

    考虑下面这种情况：
    1. 用户创建了 Buffer
    2. 用户拿到了 buffer 的引用（不管是 `&` 还是 `&mut`）来做 read 和 write。
    3. Runtime 返回了 Future，但用户直接将其 Drop 了。
    4. 现在没有人持有 buffer 的引用了，用户可以直接将其 Drop 掉。
    5. 但是，buffer 的地址和长度已经被提交给内核，它可能即将被处理，也可能已经在处理中了。我们可以推入一个 `CancelOp` 进去，但是我们也不能保证 `CancelOp` 被立刻消费。
    6. Kernel 这时已经在操作错误的内存啦，如果这块内存被用户程序复用，会导致内存破坏。

所以，我们想要保证在 IO 完成前，buffer 地址固定且是有效的。在 Rust 中，如果不拿 buffer 的所有权，这件事几乎不可能做到。

为什么 Tokio 的 AsyncIO 不需要 buffer 所有权呢？原因很简单：根本原因是 kernel 对 buffer 的操作是同步的，可以立刻取消 IO。所以我们可以只拿 buffer 的引用去做读写，然后我们可以构造捕获这个 buffer 的 Future，然后 Rust 编译器会知道它们的生命周期和引用关系。一旦 Future 完成了或被 Drop 了，这个引用就释放了，这时 kernel 是不可能操作到 buffer 的（在 epoll+syscall 下，syscall 是同步执行的，一旦用户拿到控制权，那么一定 kernel 一定不在操作）。换句话说，如果我们可以做 async drop，我们基于 io_uring 也能使用类似 Tokio 的只需要 buffer 引用的 IO 接口。

## 生态问题
最大的问题在于生态，基于 buffer 所有权的 IO 接口和目前的业界生态不搭。

为了缓解这个问题，我们为 `TcpStream` 之类的结构类提供了一个兼容的包装。用户可以使用 Tokio 的接口操作这些结构，但要付出多一次数据拷贝的开销。

并且，如果用户使用 BufRead 或 BufWrite，也没有必要非要使用带有 buffer 所有权的接口操作，因为它的内部本来就有 buffer，我们可以在 call 的时候立刻拷贝数据从而不必将用户传入的 buffer 置于一个不确定的状态（指不确定什么时候它被读写），我们也可以在没有新增的开销的情况下提供类似 Tokio 的接口。
