驱动和块设备驱动

什么是块设备

注意到我们在介绍 virtio 时提到了 virtio-blk 设备,这种设备提供了以整块为粒度的读和写操作,一般对应到真实的物理设备是那种硬盘。而之所以是以块为单位是为了加快读写的速度,毕竟硬盘等设备还需要寻道等等操作,一次性读取很大的一块将会节约很多时间。

抽象驱动

在写块设备驱动之前,我们先抽象驱动的概念,也方便后面网络设备等的介入。

os/src/drivers/driver.rs

/// 驱动类型
///
/// 目前只有块设备,可能还有网络、GPU 设备等
#[derive(Debug, Eq, PartialEq)]
pub enum DeviceType {
    Block,
}

/// 驱动的接口
pub trait Driver: Send + Sync {
    /// 设备类型
    fn device_type(&self) -> DeviceType;

    /// 读取某个块到 buf 中(块设备接口)
    fn read_block(&self, _block_id: usize, _buf: &mut [u8]) -> bool {
        unimplemented!("not a block driver")
    }

    /// 将 buf 中的数据写入块中(块设备接口)
    fn write_block(&self, _block_id: usize, _buf: &[u8]) -> bool {
        unimplemented!("not a block driver")
    }
}

lazy_static! {
    /// 所有驱动
    pub static ref DRIVERS: RwLock<Vec<Arc<dyn Driver>>> = RwLock::new(Vec::new());
}

这里暂时只有块设备这个种类,不过这样写还是为了方便未来的扩展。

抽象块设备

有了驱动的概念,我们进一步抽象块设备:

os/src/drivers/block/mod.rs

/// 块设备抽象(驱动的引用)
pub struct BlockDevice(pub Arc<dyn Driver>);

/// 为 [`BlockDevice`] 实现 [`rcore-fs`] 中 [`BlockDevice`] trait
///
/// 使得文件系统可以通过调用块设备的该接口来读写
impl dev::BlockDevice for BlockDevice {
    /// 每个块的大小(取 2 的对数)
    ///
    /// 这里取 512B 是因为 virtio 驱动对设备的操作粒度为 512B
    const BLOCK_SIZE_LOG2: u8 = 9;

    /// 读取某个块到 buf 中
    fn read_at(&self, block_id: usize, buf: &mut [u8]) -> dev::Result<()> {
        match self.0.read_block(block_id, buf) {
            true => Ok(()),
            false => Err(dev::DevError),
        }
    }

    /// 将 buf 中的数据写入块中
    fn write_at(&self, block_id: usize, buf: &[u8]) -> dev::Result<()> {
        match self.0.write_block(block_id, buf) {
            true => Ok(()),
            false => Err(dev::DevError),
        }
    }

    /// 执行和设备的同步
    ///
    /// 因为我们这里全部为阻塞 I/O 所以不存在同步的问题
    fn sync(&self) -> dev::Result<()> {
        Ok(())
    }
}

这里所谓的 BlockDevice 其实就是一个 Driver 的引用。而且利用 rcore-fs 中提供的 BlockDevice trait 实现了为文件系统的接口,实际上是对上传文件系统的连接。

virtio-blk 块设备驱动

最后,我们来实现 virtio-blk 的驱动(主要通过调用现成的库完成):

os/src/drivers/block/virtio_blk.rs

/// virtio 协议的块设备驱动
struct VirtIOBlkDriver(Mutex<VirtIOBlk<'static>>);

/// 为 [`VirtIOBlkDriver`] 实现 [`Driver`] trait
///
/// 调用了 [`virtio_drivers`] 库,其中规定的块大小为 512B
impl Driver for VirtIOBlkDriver {
    /// 设备类型
    fn device_type(&self) -> DeviceType {
        DeviceType::Block
    }

    /// 读取某个块到 buf 中
    fn read_block(&self, block_id: usize, buf: &mut [u8]) -> bool {
        self.0.lock().read_block(block_id, buf).is_ok()
    }

    /// 将 buf 中的数据写入块中
    fn write_block(&self, block_id: usize, buf: &[u8]) -> bool {
        self.0.lock().write_block(block_id, buf).is_ok()
    }
}

/// 将从设备树中读取出的设备信息放到 [`static@DRIVERS`] 中
pub fn add_driver(header: &'static mut VirtIOHeader) {
    let virtio_blk = VirtIOBlk::new(header).expect("failed to init blk driver");
    let driver = Arc::new(VirtIOBlkDriver(Mutex::new(virtio_blk)));
    DRIVERS.write().push(driver.clone());
}

需要注意的是,现在的逻辑怎么看都不像是之前提到的异步 DMA + IRQ 中断的高级 I/O 操作技术,而更像是阻塞的读取。实际上的确是阻塞的读取,目前 virtio-drivers 库中的代码虽然调用了 DMA,但是返回时还是阻塞的逻辑,我们这里为了简化也没有设计 IRQ 的响应机制。

小结

至此,我们完成了全部的驱动逻辑,我们总结一下目前的设计模式如下所示:

其中 Driver 作为一个核心 trait 为上提供实现,上层也就是 Driver 的使用侧(设备的抽象),而下层则是 Driver 的实现侧(设备的实现)。而下一个小节,我们将利用这些驱动来实现文件系统。

results matching ""

    No results matching ""

    results matching ""

      No results matching ""