oscourse-tsinghua / rcore_plus

Rust version of THU uCore OS. Linux compatible.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

the write operation on device?

chyyuu opened this issue · comments

目前lab8-rv32中,用mem来模拟disk。但我看到两种对Device::read_at/write_at的实现:

impl Device for MemBuf {
    fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option<usize> {
        let slice = self.0;
        let len = buf.len().min(slice.len() - offset);
        buf[..len].copy_from_slice(&slice[offset..offset + len]);
        Some(len)
    }
    fn write_at(&mut self, _offset: usize, _buf: &[u8]) -> Option<usize> {
        None
    }
}

/// Device which can only R/W in blocks
pub trait BlockedDevice: Send {
    const BLOCK_SIZE_LOG2: u8;
    fn read_at(&mut self, block_id: usize, buf: &mut [u8]) -> bool;
    fn write_at(&mut self, block_id: usize, buf: &[u8]) -> bool;
}
...
/// Helper functions to R/W BlockedDevice in bytes
impl<T: BlockedDevice> Device for T {
    fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option<usize> {...
    fn write_at(&mut self, offset: usize, buf: &[u8]) -> Option<usize> {...

我理解sfs应该用的是BlockedDevice的struct,但MemBuf 没有包括BlockedDevice的实现,且没有write_at的实现。

如何理解sfs中的虚拟设备是如何实现的?

@wangrunji0408

在sfs.rs中有实现了Device的两个函数read/write_block

impl Device {
    fn read_block(&mut self, id: BlockId, offset: usize, buf: &mut [u8]) -> vfs::Result<()> {
        match self.read_at(id * BLKSIZE + offset, buf)  ...
    }
    fn write_block(&mut self, id: BlockId, offset: usize, buf: &[u8]) -> vfs::Result<()> {
        match self.write_at(id * BLKSIZE + offset, buf) ...
    }

这样就对上了。
INodeImpl::read_at --> INodeImpl::_read_at --> INodeImpl::_io_at --> Device::read_block(&mut *self.fs.device.lock(), &range, buf_offset) -->Device::read_at(id * BLKSIZE + offset, buf) 即 BlockedDevice::read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option

直接用一个Device不就可以了?
看得累!!

这是确实是一个需要改进的地方。

目前的设计是:

  • 具体文件系统SFS依赖的设备层接口Device是按字节读写的
  • 真实的块设备(如x86下的IDE)要实现按字节读写的Device比较麻烦。于是后来引入了按块读写的BlockedDevice接口,并对所有BlockedDevice实现了Device。这样只需impl BlockedDevice for IDE,就自动impl Device for IDE,SFS就可以读写IDE了。
  • 然而由于SFS本来就是针对块设备设计的,因此实现中几乎都是按块读写,于是又引入了read_block 等辅助函数……

总结下来就是:现在SFS在字节读写的Device上绕了个大圈子,应该直接依赖BlockedDevice才更合理。

impl Device for MemBuf {...}
impl BlockedDevice for IDE {...}
impl<T: Device> BlockedDevice for T {...}
SimpleFileSystem::new(device: impl BlockedDevice) {...}

改起来意识到事情没有那么简单……

原因在于:虽然FS和设备都是按块读写,但它们的块大小可能是不同的。
例如SFS中块是4KB,IDE中块是512B。
如果SFS直接看到BlockedDevice,那么还要根据不同的块大小做不同处理。
这时就体现出按字节读写的Device接口作为中间层的好处了。

读文件函数的调用关系虽然复杂,但每一步都有它的作用:

  1. INodeImpl::read_at:检查操作的合法性
  2. INodeImpl::_io_at:将对文件的字节访问按块分解,并从INode中查询出每块在设备上的物理位置
  3. INodeImpl::_read_at:读取设备上一个块的内容(实际调用顺序是3,2)
  4. Device::read_block:将对设备的块访问转化为字节访问(BLOCK_SIZE=4096)
  5. Device::read_at:这是一个接口,对不同的设备而言……
    MemBuf:
  6. <MemBuf as Device>::read_at:直接对内存进行字节访问
    IDE:
  7. <IDE as Device>::read_at:将对设备的字节访问转化为块访问(BLOCK_SIZE=512)
  8. <IDE as BlockedDevice>::read_at:实际读取IDE的一个块/扇区