Page 1 of 1

VFS: How d you guys handle mountpoints?

Posted: Wed Apr 22, 2015 6:49 am
by mariuszp
Glidix currently has a VFS with all the typical UNIX features (except symlinks, which are to be implemented soon). However, I'm starting to think that my implementation is not the best.

The main problem with it is that it may not necessarily be the most efficient, and this stems mainly from how it stores mountpoints. When you mount a filesystem, it is assigned a "prefix"; for example, if I might my CD-ROM at /media/cdrom, the kernel will add the following structure to a linked list:

Code: Select all

typedef struct _MountPoint
{
	char			prefix[512];
	FileSystem		*fs;
	struct _MountPoint	*prev;
	struct _MountPoint	*next;
} MountPoint;
Where "prefix" will be "/media/cdrom/" (i.e. all paths starting with "/media/cdrom/" are on this filesystem). When trying to resolve the path "/media/cdrom/my_files/hello.txt", it will see that it begins with "/media/cdrom/" and therefore successfully detect that it is on that filesystem; then, the rest of the path, "my_files/hello.txt" is followed, by calling fs->openroot(), which returns a "Dir" structure, and the system will repeadetly call dir->next() until "my_files" is found, then it will call dir->opendir() which returns another Dir, and eventually it finds the file (if it exists of course).

But this means that the only way processes can have working directories is if they just store their paths, and that path must be strcat()ed with every relative path used, and a lot of pointless directory-traversing will then happen... but otherwise, of course, it wouldn't know which filesystem to go on, if a subdirectory of the working directory is a mountpoint.

How does your OS store mountpoints? I can think of another way: to just store the device (st_dev) and inode (st_inode) of each mountpoint, and redirect all operations on such inodes to the root of the filesystem. But the problem would be that /initrd, /dev, etc must be mounted before the root filesystem, but they are on the root filesystem. I could mount some kind of "ramfs" on the root, then unmount everything and mount the proper filesystems.

But before doing this I wanted to see how other OSes do this. I can't find any information on how linux stores this either.

Re: VFS: How d you guys handle mountpoints?

Posted: Wed Apr 22, 2015 10:26 am
by AbstractYouShudNow
Hi,

Lookup the JamesM tutorial on filesystems, it has a quite elegant way of doing that AFAIK

Re: VFS: How d you guys handle mountpoints?

Posted: Wed Apr 22, 2015 10:55 am
by mariuszp
JamesM's tutorial marks some inodes with a flag (FS_MOUNTPOINT), this is clearly not a good idea; either you'd have to save that flag to disk and each filesystem driver would have to support it, or you'd have to have a list of mounted dev-inode pairs anyway.

Re: VFS: How d you guys handle mountpoints?

Posted: Wed Apr 22, 2015 12:27 pm
by max
In Ghost, a node contains
- type
- virtual id
- physical fs id
- name
- delegate
- parent
- list of children

When the type of the node is "mountpoint", the node additionally has a "delegate" set. Whenever anyone tries to open a file, list a directory, read from a file etc. the delegate is used.

For example, a process wants to open "/examplefs/documents/test.txt":
1. find the node "examplefs" by searching the list of children in the root; if its not found, ask the root's delegate to discover the node
2. find the node "documents" by searching the list of children in the found node; if its not found, the delegate for "examplefs" is asked to discover the node
3. find the node "test.txt" ... using the delegate of "examplefs" as before

"Discovering" here means, searching on the actual physical filesystem/whatever to search for a specific node.
You probably also want to have processes to be filesystem drivers: for this, there is the "task delegate". Whenever this delegate is asked to do anything, it sends a message to the process that registered itself to be the driver for that mountpoint, sets the requesting task asleep, and waits until the action is finished. The filesystem driver process receives this message, does whatever is necessary to do the requested action (like using system calls to create the virtual filesystem nodes etc.) and then tells the kernel that the transaction is finished. The requesting process is the woken up and can continue as if nothing happened.

The root node is just a node with no name, that has a special delegate (the root delegate) that just lists all mounpoints created in root.

Thats how I do it. In short, mounpoints have a delegate that have an implementation to access a specific filesystem.

Re: VFS: How d you guys handle mountpoints?

Posted: Wed Apr 22, 2015 10:43 pm
by KemyLand
I'll share my VFS's design on what mountpoints respect.

First of all, some important data structures and functions (Spoiler, C++11 is involved!):

Code: Select all

namespace VFS {
	enum class InodeType {
		File,
		Directory,
		SymbolicLink,
		ProtocolFile, // i.e: socket
		NamedPipe,
		TrapFile, // i.e: "special" files
		Gateway // Let's leave this for another forum thread...
	};
	
	struct Inode {
		Size		id; // As far as the underlying filesystem knows
		UID		owner;
		Size		refs; // (Hard-links)
		Size		openRefs; // (Open file descriptors)
		UInt16	access;
		Size		size;
		InodeType	type;
		Bool		mayBeUncached;
	};
	
	class Directory;
	
	struct File {
		Byte		name[256];
		Size		mount;
		Size		inode;
		Directory*	parent;
		File*		next;
		InodeType	type;
	};
	
	class Directory : public File {
		public:
			File*	children;
			
			ErrorCode	getChild	(String name, File **file);
	};
	
	ErrorCode	Mount	(String source, String target, Bool bindMount);
	ErrorCode	Unmount	(String root);
}
Some BTWs:
  • typedef unsigned long Size; // For x86
  • class ErrorCode; // ...
  • typedef bool Bool; // This *has* its reasons
  • typedef const char* String; // Did you though it was something like std::string?
The job of VFS::Mount() is very simple:

If bindMount is true, then source shall be a path pointing to a valid, present directory. That directory and its children shall be copied into a new, fresh tree. The address of that new directory structure is stored in a example pointer named sourceDir. The bind-mounted directory structure itself may not be copied, it's innecessary.

Else, the trap file pointed to by source is loaded into kernel memory and its internal filesystem is parsed into the common tree structure in kernel memory. The directory representation of the root of the loaded filesystem is stored in the previously mentioned sourceDir pointer.

Whatever *sourceDir contains, the target directory, target, shall be empty for everything to work correctly. There will be a single change in the original filesystem tree, that is, all first-level nodes from the root (i.e: /usr counts but /usr/local doesn't) shall have their parent changed to the directory pointed to by target, as well as target shall change its childs pointer member changed to the address of the first element of the mounted filesystem.

The reverse process, VFS::Unmount() is even simpler. It just flushes all data in the mounted filesystem, removes it from kernel memory, and sets the host root's childs pointer member to nullptr.

This model is simple, yet it addresses bind mounting, nested mounts, and multiple mounts of a single filesystem. BTW, obviously, there is a hidden mountpoint list that avoids VFS::Unmount()'ing of non-mountpoints, and there are several minimal, but important details that I didn't said here, specially with the "multiple mounts of a single filesystem" feature.