Writing Filesystems with Libfs

Libfs is a library that provides several very generic standard routines that can be used to create small filesystems that serve one specific purpose. The routines are well suited for in-memory files without a backing store. Obviously the code cannot provide means to interact with specific on-disk formats; this needs to be handled properly by full filesystem implementations. The library code is contained in a single file, fs/libfs.c.

The prototypes are defined in <fs.h>; there is no <libfs.h>! Routines provided by libfs are generally prefixed by simple_. Recall from Chapter 8 that the kernel also provides several generic filesystem routines that are prefixed by generic_. In contrast to libfs routines, these can also be used for full-blown filesystems.

The file and directory hierarchy of virtual filesystems that use libfs is generated and traversed using the dentry tree. This implies that during the lifetime of the filesystem, all dentries must be pinned into memory. They must not go away unless they are explicitly removed via unlink or rmdir. However, this is simple to achieve: The code only needs to ensure that all dentries always have a positive use count.

To understand the idea of libfs better, let's discuss the way directory handling is implemented. Boilerplate instances of inode and file operations for directories are provided that can immediately be reused for any virtual filesystem implemented along the lines of libfs:

fs/libfs.c const struct file_operations simple_dir_operations = {

.open = dcache_dir_open,

.release = dcache_dir_close,

.llseek = dcache_dir_lseek,

.read = generic_read_dir,

.readdir = dcache_readdir,

.fsync = simple_sync_file,

const struct inode_operations simple_dir_inode_operations = { .lookup = simple_lookup,

In contrast to the convention introduced above, the names of the routines that make up simple_dir_operations do not start with simple_. Nevertheless, they are defined in fs/libfs.c. The nomenclature reflects that the operations solely operate on objects from the dentry cache.

If a virtual filesystem sets up a proper dentry tree, it suffices to install simple_dir_operations and simple_dir_inode_operations as file or inode operations, respectively, for directories. The libfs functions then ensure that the information contained on the tree is exported to userland via the standard system calls like getdents. Since constructing one representation from another is basically a mechanical task, the source code is not discussed in detail.

Instead, it is more interesting to observe how new files are added to a virtual filesystem. Debugfs (discussed below) is one filesystem that employs libfs. New files (and thus new inodes) are created with the following routine:

fs/debugfs/inode.c static struct inode *debugfs_get_inode(struct super_block *sb, int mode, dev_t dev) {

struct inode *inode = new_inode(sb);

inode->i_mode = mode; inode->i_uid = 0; inode->i_gid = 0; inode->i_blocks = 0;

inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;

default:

init_special_inode(inode, mode, dev); break; case S_IFREG:

inode->i_fop = &debugfs_file_operations; break;

case S_IFDIR:

inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations;

/* directory inodes start off with i_nlink == 2

* (for "." entry) */ inc_nlink(inode); break;

return inode;

Besides allocating a new instance of struct inode, the kernel needs to decide which file and inode operations are to be associated with the file depending on the information in the access mode. For device special files, the standard routine init_special_file (not connected with libfs) is used. The more interesting cases, however, are regular files and directories. Directories require the standard file and inode operations as discussed above; this ensures with no further effort that the new directory is correctly handled.

Regular files cannot be provided with boilerplate file operations. It is at least necessary to manually specify the read, write, and open methods. read is supposed to prepare data from kernel memory and copy them into userspace, while write can be used to read input from the user and apply it somehow. This is all that is required to implement custom files!

A filesystem also requires a superblock. Thankfully for lazy programmers, libfs provides the method simple_fill_super, that can be used to fill in a given superblock:

int simple_fill_super(struct super_block *s, int magic, struct tree_descr *files);

s is the superblock in question, and magic specifies a unique magic number which can be used to identify the filesystem. The files parameter provides a very convenient method to populate the virtual filesystem with what it is supposed to contain: files! Unfortunately, only files in a single directory can be specified with this method, but this is not a real limitation for virtual filesystems. More content can still be added later dynamically.

An array with struct tree_descr elements is used to describe the initial set of files. The structure is defined as follows:

struct tree_descr { char *name;

const struct file_operations *ops; int mode;

name denotes the filename, ops points to the associated file operations, and mode specifies the access bits.

+1 0

Post a comment