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中的虚拟设备是如何实现的?
在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
接口作为中间层的好处了。
读文件函数的调用关系虽然复杂,但每一步都有它的作用:
INodeImpl::read_at
:检查操作的合法性INodeImpl::_io_at
:将对文件的字节访问按块分解,并从INode中查询出每块在设备上的物理位置INodeImpl::_read_at
:读取设备上一个块的内容(实际调用顺序是3,2)Device::read_block
:将对设备的块访问转化为字节访问(BLOCK_SIZE=4096)Device::read_at
:这是一个接口,对不同的设备而言……
MemBuf:<MemBuf as Device>::read_at
:直接对内存进行字节访问
IDE:<IDE as Device>::read_at
:将对设备的字节访问转化为块访问(BLOCK_SIZE=512)<IDE as BlockedDevice>::read_at
:实际读取IDE的一个块/扇区