The kernel is not written in C++, though, so we use another solution. The VFS layer defines structs for superblocks and inodes. These structures define the minimum information all implementations must store, for example, all inodes must contain the owner's user id. But VFS can't possibly know what fields an implementation will need in addition, so it provides an out: it adds a field that the particular implementation can use (u) to store whatever it wants.
That takes care of adding new fields to the class (using object-oriented terminology), but what about implementing the methods? This is done by having the filesystem implmentation declare a set of structs listing the supported methods for each data type. For example, in file.c, cse451fs declares a struct file_operations. That struct contains 4 function pointers. As it happens, cse451fs is similar enough to standard filesystems that 3 of these are the "generic" versions, and only one actually need be implemented by cse451fs. A pointer to this struct will wind up in every inode the represents a cse451fs file (in the i_fop field, put there by cse451_read_inode). Then, when an method needs to be invoked on an inode, the invoker just looks up the method in that struct.
Combined, we've got a somewhat clumsy object-oriented system. Interestingly, in implementation, it is very similar to what a C++ compiler does behind the scenes.
To make things slightly more confusing, inodes and superblocks also need to be stored on disk. Again, VFS does not care what format they are stored on disk. In cse451fs, the structure actually stored on disk for an inode is struct cse451_inode, while the actual superblock structure is struct cse451_super_block (both defined in cse451fs.h).
There are two pieces of the buffer manager: the data structures that it manages (struct buffer_head) and the actual data (pointed to by the b_data field of buffer_head). For any given disk block, the buffer manager may be:
Thus, when the filesystem asks for a block (by calling bread), the buffer manager first needs to find or create a buffer_head. It then needs to make sure the data is available.
The following is intended to serve as a quick-reference to the functions of the buffer manager. Throughout, bh refers to a struct buffer_head *, block and size are int, and dev is kdev_t.
Buffer Manager Function | Defined At | Description |
---|---|---|
bh = bread(dev, block, size) | fs/buffer.c:1177 | Get the buffer_head for the given disk block, ensuring that the data is in memory and ready for use. Increments ref count; always pair with a brelse. |
bh = getblk(dev, block, size) | fs/buffer.c:982 | Get the buffer_head for the given disk block. Does not guarantee anything about the state of the actual data. Increments ref count; always pair with a brelse. |
bh = get_hash_table(dev, block, size) | fs/buffer.c:547 | Find the buffer_head for the given block in the hash table. Does not guarantee anything about the state of the actual data. Increments ref count; always pair with a brelse. |
ll_rw_block(rw, numbufs, bufs) | drivers/block/ll_rw_blk.c:1021 | Lock buffer, begin an IO (either read or write), schedule a callback that will unlock buffer when IO finishes, and return (does not wait for IO completion - see wait_on_buffer). |
wait_on_buffer(bh) | include/linux/locks.h:17; see __wait_on_buffer fs/buffer.c:145 |
Wait for the buffer to be unlocked (e.g. read from disk to complete) |
mark_buffer_dirty(bh) | fs/buffer.c:1098 | Mark the buffer modified, meaning needs to be written to disk at some point. |
mark_buffer_uptodate(bh) | include/linux/fs.h:1023 | Indicate that the data pointed to by bh is valid. |
is_valid = buffer_uptodate(bh) | include/linux/fs.h:264 | Return whether the bh points to valid data. |
brelse(bh) | see __brelse, fs/buffer.c:1138 | Decrement the ref. count of the given buffer. |
Confusingly, cse451fs also defines some functions that are similar in name and purpose to those in the buffer manager (basically, they differ in that they take inodes and file block numbers rather than disk devices and disk block numbers):
cse451fs Function | Defined At | Description |
---|---|---|
bh = cse451_bread(inode, block, create) | super.c:187 | Similar to bread, but takes an inode and block number relative to that inode. |
bh = cse451_getblk(inode, block, create) | super.c:166 | Similar to getblk, but takes an inode and block number relative to that inode. Zeros out new blocks (required for security). |
err = get_block(inode, block, &bh, create) | super.c:118 | Fills in b_blocknr field of bh, allocating a new data block if necessary (and create is true). Does not guarantee data is in memory. |