Expand description
§Inode abstraction.
In a Unix-like file system, every object in a file system is represented by a data structure known as an inode. An inode (index node) stores metadata about a file system object, including its size, permissions, timestamps, and pointers to the underlying data blocks.
§Inode Internals
An Inode is the authoritative metadata structure for a file or
directory. It stores essential information such as the object’s type, size,
and pointers to data blocks, serving as the central handle for locating and
managing file data.
At its core, an inode functions as an indexing structure: it maps a
FileBlockNumber (a block’s position relative to the file) to a
LogicalBlockAddress (the actual block location on disk). This mapping
enables the file system to translate file-relative accesses into physical
disk operations, bridging the logical view of a file with the underlying
storage layout.
While the on-disk inode structure provides persistence,
the kernel maintains in-memory inodes to speed up access and coordinate
concurrent operations. To ensure this, KeOS provides the
FastFileSystemInner::get_inode API to maintains single, global view
of each inode inside the kernel.
The function looks up an inode number (InodeNumber) and returns a
TrackedInode, which is a consistent view for a reference-counted,
thread-safe wrapper around the in-memory inode. All kernel subsystems
interact with inodes through this wrapper, ensuring proper synchronization.
A TrackedInode provides two key capabilities:
- Read access via
TrackedInode::read, which acquires a shared guard for inspecting inode state (e.g., file size, permissions) without modification. - Write access via
TrackedInode::write_with, which locks both the in-memory and on-disk inode. Writes are performed inside a closure that receives aTrackedInodeWriteGuard. Changes must be explicitly finalized withTrackedInodeWriteGuard::submit, ensuring that memory and disk stay consistent.
Together, get_inode and TrackedInode enforce a disciplined access model:
there is exactly one in-memory representation of each inode, guarded by
lock. This design keeps inode state consistent across memory and disk, even
under concurrent file system activity.
§Inode Indexing in FFS
An inode does not store file data directly. Instead, it contains pointers to data blocks that hold the file’s contents. To balance efficiency for small files with scalability for large files, FFS adopts a tiered indexing scheme as follow:
┌───────────────────────────┐
│ Inode │
├───────────────────────────┤
│ dblocks[0] → Data blk 0 │
│ dblocks[1] → Data blk 1 │
│ ... │
│ dblocks[11] → Data blk11 │
│ │
│ iblock 0 ───────────────┐ │
│ │ │
│ diblock ─────────────┐ │ │
└──────────────────────┬──┬─┘
│ │
┌──────────────────────────┘ │
┌──────▼───────┐ ┌───▼──────────┐
│ Double ind. │ │ Indirect │
├──────────────┤ ├──────────────┤
│ → iblock 1 │ │ → Data blk12 │
│ → iblock 2 │─┐ │ → Data blk13 │
│ .. │ │ │ ... │
│ → iblock 512 │ │ │ → Data blk523│
└──────────────┘ │ └──────────────┘
│
┌──────────────┘
┌───▼──────────┐
│ Indirect blk │
├──────────────┤
│ → Data blk X │
│ → Data blk Y │
│ ... │
└──────────────┘- Direct blocks (
dblocks) The first 12 pointers directly reference data blocks. This makes access to small files very fast, as no additional lookup is required. Small files can therefore be served entirely from these direct entries. - Indirect block (
iblock) When the file grows beyond the direct blocks, the inode refers to a single indirect block. This block is itself an array of data block pointers, extending the maximum file size significantly. - Double indirect block (
diblock) For even larger files, the inode uses a double indirection. The inode points to a block that contains pointers to indirect blocks, each of which then contains pointers to data blocks. This extra level of indirection allows extremely large files to be addressed.
Together, these three levels form a hierarchical mapping from a
FileBlockNumber (position within a file) to a
LogicalBlockAddress (actual block on disk).
Your task is to implement the two core file access functions based on the
indexing structure: Inode::get and Inode::grow.
-
Inode::getretrieves the disk location of a specific file block. It traverses the inode’s indexing structure and returns the corresponding disk block address, orNoneif the block has not been allocated. -
Inode::growensures that the inode can cover a target file block number. If needed, it allocates new blocks and updates the inode’s indexing structure. All modifications are performed transactionally to guarantee consistency.
These functions use MetaData::load to access or create
IndirectBlocks, and they update these blocks via the transaction API
(using BlockPointsTo::write and
BlockPointsToWriteGuard::submit). For reference on using these APIs, you
may consult the documentation or implementation of the followings:
While implementing the requirements, you may encounter
RunningTransaction struct. You do not need to understand it until the
implementation of the journaling; for now, simply pass a reference to the
required methods.
§Implementation Requirements
You need to implement the followings:
After implement the functionalities, move on to the next section.
Structs§
- Inode
- Represents an inode in memory, the metadata structure for a file or directory.