rcore-os / virtio-drivers

VirtIO guest drivers in Rust.
MIT License
209 stars 63 forks source link

Problems with asynchronous interfaces #151

Open Dr-TSNG opened 3 months ago

Dr-TSNG commented 3 months ago

考虑我们有这样的结构,它主要实现了这样的功能:

  1. 发送读请求,将 waker 保存,让出 CPU。
  2. 中断到来,根据 token 取出 waker,唤醒 Future。
  3. 完成读取。

这套方案存在两个问题:

  1. 在非理想情况下,Future 可能被其他事件取消(信号/进程终止等),此时后续的 complete_read_blocks 不会被执行,因此需要驱动支持直接取消一个 token,而不需要完成数据复制工作。(不能创建一个新的 buffer 放到全局区,把 complete 操作移到 handle_irq 处,因为这会导致额外的复制开销。)
  2. peek_used 方法只会取出一个待处理的 token(而不会消费),而在这个 token 被处理前无法得知其他 token 是否完成,从而将对应任务唤醒。这会导致其他的 Future 因为后续无法收到中断信号而永远被挂起(假设中断发生时所有任务都已经就绪)。

这样的问题在目前的异步接口下很难解决。

struct ReqGuard<'a>(&'a VirtIOBlkDevice, u16);

impl Drop for ReqGuard<'_> {
    fn drop(&mut self) {
        let mut inner = self.0.inner.lock();
        inner.tokens.remove(&self.1);
        inner.block.// How: cancel_request(self.1);
    }
}

async fn read_block(&self, block_id: usize, buf: &mut [u8]) -> SyscallResult {
    let mut req = BlkReq::default();
    let mut resp = BlkResp::default();
    let token = loop {
        let token = unsafe {
            self.inner.lock().block.read_blocks_nb(block_id, &mut req, buf, &mut resp)
        };
        // Error handling...
    };
    let guard = ReqGuard(self, token);
    poll_fn(|cx| {
        self.inner.lock().tokens.insert(token, cx.waker().clone());
        Poll::<()>::Pending
    }).await;
    core::mem::forget(guard);
    let res = unsafe {
        self.inner.lock().block.complete_read_blocks(token, &mut req, buf, &mut resp)
    };
    // Error handling...
}

fn handle_irq(&self) {
    let mut inner = self.inner.lock();
    if let Some(token) = inner.block.peek_used() {
        if let Some(waker) = inner.tokens.remove(&token) {
            waker.wake();
        }
    }
}