Part XII: Simple File System
What do we have now
We made our bootloader a while ago, if you remember, in our
bootloader we were reading from a disk (this disk being the kernel.img
disk image) and loading the data directly into memory at 0x9000. What
we have right now is not a filesystem, although we did
interface with some sort of disk or secondary storage.
We can keep this primitive method of loading the kernel, we will not make the filesystem responsible for loading our kernel into memory for our operating system (although you can). Our bootloader can continue using the primitive method I just described. Just like all our other technologies, the filesystem can be initialized and access the rest of the disk once the storage driver is available.
We don't have a filesystem, but what we do have is a (virtual) disk
that is a part of our virtual machine that we
have left untouched (other than in the bootloader). On our disk
there is a structure such as:
[bootloader][kernel][kernel][kernel]... The filesystem we make is
essentially a way we are going to interpret and communicate with the
data on this disk. For example, we may have:
Sector 0
bootloader
Sectors 1-50
kernel (and future additions to the kernel...)
Sector 51
filesystem superblock
Sector 52
free-space bitmap
Sectors 53-100
inode table
Sectors 101+
data region
At this state, the first Makefile setup I showed you in our first few chapters
made the kernel.img disk image only have enough space to contain the bootloader and
the kernel, so you will have to expand this with dd command.
What should we make?
The filesystem
We are making a block based, inode based, semi Unix like filesystem. It
will be similar to filesystems such as: ext2, ext3 and ext4.
The disk would just be a sequence of storage units called blocks. Each block would be 512 bytes long and something like a text file may occupy a certain number of blocks. We need a way to track the relationship between blocks and files, this is where inodes come in.
An inode would essentially be the filesystem's record describing files. An inode would contain information such as: file type, file size, file permissions, ownership, timestamps, pointers to blocks.
You may then notice that when it comes to inodes, we didn't mention the
filename, this is because a filename would be stored within a directory,
a directory essentially is a mapping between a filename and it's
respective inode. When we access "/hello.txt" our filesystem does:
"/hello.txt" -> root directory (find "hello.txt") -> inode x (find data blocks -> found blocks y and z -> parse file contents
We also mentioned a superblock, this is metadata about the whole filesystem, ours may contain the: type, size of each block, block count, location of inode table, location of free-space map, location of data region. Without the superblock, we would have to assume a lot of things about our filesystem, which is not perfect practice.
What's the free-space bitmap? This is essentially the same thing we did within our physical memory manager but for the filesystem, it describes what blocks are occupied and which aren't within a simple bitmap.
And then finally we have the data region, where our blocks are actually contained.
The drivers
When we were in our BIOS, int 13h allowed us to
communicate with our disk. But here, we will have to write our own
driver to communicate with the disk. QEMU can emulate many
different types of storage devices and controllers, but we will be
writing our driver for the ATA PIO. ATA and PIO are two different
things, but are a combination used to make up our whole driver.
After our driver is created, we will start writing the code for our filesystem as we described above. If you then want to make new drivers for new types of storage devices, it will be easy to do so due to the filesystem being abstracted from the driver.
The manager
When I walk you through the implementation of the filesystem, you will see
that it can quickly become a maze of about 20 functions that all interact with
another and can become confusing to navigate. Due to this, I will make a filesystem manager
this will include functionality like fs_open(), fs_close(), fs_read(), fs_write().
I will also make ls, mkdir, touch, rm, these would typically be their own
user space programs, but I'm going to be embedding them into the filesystem manager, so our
filesystem becomes easier to use earlier on.
How do we make this?
First the driver, next the filesystem, then a manager for the filesystem.