SCore implements a simple, inode-based file system named easy-fs. It is designed as a standalone crate to be modular and is integrated into the kernel to provide persistent storage capabilities. This document outlines its architecture, on-disk layout, and key components.
The file system is designed with a layered architecture to separate concerns and improve modularity:
- System Call Interface: Provides file-related system calls to user applications, such as
open,read,write, andclose. These are handled in theos/src/syscall/fs.rsmodule. - Virtual File System (VFS): This layer provides a unified interface for file operations. The central abstraction is the
Inode, which represents a file or directory. Each process maintains a file descriptor table in itsPCBthat maps file descriptors toInodeobjects. easy-fsImplementation: This is the concrete file system implementation. It manages the on-disk layout, including inodes, data blocks, and bitmaps.- Block Cache: An LRU (Least Recently Used) block cache sits between the file system and the block device. It caches frequently accessed disk blocks in memory to reduce I/O latency and improve performance.
- Block Device Driver: The lowest layer, which provides an abstract
BlockDevicetrait for reading and writing raw blocks from and to a storage device (e.g., an emulated disk in QEMU).
!File System Architecture
The easy-fs file system organizes the disk into several distinct sections:
- Superblock: Located at the beginning of the disk, it contains metadata about the entire file system, such as the total number of blocks, the locations and sizes of the inode bitmap, data bitmap, inode area, and data area.
- Inode Bitmap: A bitmap used to track the allocation status of inodes. A set bit indicates that the corresponding inode is in use.
- Data Block Bitmap: A bitmap used to track the allocation status of data blocks.
- Inode Area: A contiguous region of blocks where all
DiskInodestructures are stored. - Data Area: The remaining space on the disk, used for storing the actual file and directory content.
!Easy-FS Layout
The Inode is the core abstraction of the VFS. It represents an object in the file system (a regular file or a directory) and provides methods for file operations.
pub struct Inode {
block_id: usize,
block_offset: usize,
fs: Arc<dyn FileSystem>,
block_device: Arc<dyn BlockDevice>,
}
impl Inode {
pub fn read_all(&self) -> Vec<u8>;
pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize;
pub fn write_at(&self, offset: usize, buf: &[u8]) -> usize;
// ... and other file operations
}This struct represents a mounted easy-fs instance. It holds references to the block device and manages the allocation areas (bitmaps, inode area, data area).
pub struct EasyFileSystem {
pub block_device: Arc<dyn BlockDevice>,
pub inode_bitmap: Bitmap,
pub data_bitmap: Bitmap,
inode_area_start_block: u32,
data_area_start_block: u32,
}This manager implements the LRU block cache. It maintains a queue of BlockCache objects and provides a simple get_block_cache method to access cached blocks.
pub struct BlockCacheManager {
queue: VecDeque<(usize, Arc<Mutex<BlockCache>>)>,
// ...
}
impl BlockCacheManager {
pub fn get_block_cache(
&mut self,
block_id: usize,
block_device: Arc<dyn BlockDevice>,
) -> Arc<Mutex<BlockCache>>;
}Each process has a file descriptor table (fd_table) within its PCBInner. This table is a Vec<Option<Arc<Inode>>>. When a file is opened with sys_open, the kernel finds an empty slot in this table, allocates a new file descriptor (the index of the slot), and places the Inode object for the opened file into it. Subsequent sys_read and sys_write calls use this file descriptor to look up the Inode and perform the operation.
Standard input (stdin), standard output (stdout), and standard error (stderr) are pre-opened as file descriptors 0, 1, and 2, respectively.
To make the file system usable, we need a way to create a disk image and populate it with initial files (like the user shell and other test programs). The easy-fs-fuse utility serves this purpose. It runs on the host machine and uses FUSE (Filesystem in Userspace) to mount a local directory. Any files copied into this directory are automatically packed into an easy-fs formatted disk image (fs.img). This image is then loaded by QEMU and used by the SCore kernel.