-
Notifications
You must be signed in to change notification settings - Fork 7
Filesystems
A filesystem stores files and directories persistently. Files and directories are represented in memory by Inode instances. An inode has to be acquired by invoking one of the Filesystem_AcquireNode functions before it can be used. Once an inode is no longer needed, it should be relinquished which will cause the filesystem to write inode metadata changes back to the underlying storage device.
Every filesystem sits on top of some medium and it is responsible for managing the data on this medium. A medium can be anything as far as the abstract Filesystem base class is concerned: it may be a physical disk, a tape or maybe some kind of object that only exists in memory and does not even persist across reboot.
The ContainerFilesystem subclass is a specialization of Filesystem which builds on top of a FSContainer. A FSContainer represents a logical disk which may map 1:1 to a single physical disk or an array of physical disks. Concrete filesystem implementations which are meant to implement a traditional disk-based filesystem should derive from ContainerFilesystem instead of Filesystem.
The lifetime of a filesystem instance is always >= the lifetime of all acquired inodes. This is guaranteed by ensuring that a filesystem can not be stopped and destroyed as long as there is at least one acquired inode outstanding. Thus it is sufficient to either hold a strong reference to a filesystem object (use Object_Retain to get it) or a strong reference to an inode from the filesystem in question (use Filesystem_AcquireNode to get one) to ensure that the filesystem stays alive.
A filesystem must be started before it can be used and the root inode can be acquired. An active/running filesystem instance can be stopped at any time. A stop may be forced or unforced: a forced means that the filesystem is stopped even if there are still inode instances outstanding or an open channel to the filesystem instance itself exists. An unforced stop will fail the stop if any of these conditions is true and the filesystem remains in active state. A stopped filesystem (whether unforced or forced stop) can not be destroyed as long as there are still inodes outstanding or an open channel to the filesystem exists. The filesystem will only be destroyed after all the outstanding inodes have been relinquished and all open filesystem channels have been closed. This is taken care of by the FilesystemManager reaper. Note that stopping a filesystem is a two step process: first the filesystem is stopped and then it is disconnected from its underlying storage. This separation exists to allow the file hierarchy to stop a filesystem while holding its lock and then to disconnect it after it has dropped its lock. This is desired because disconnecting a filesystem from its storage first triggers a flush of all still cached data to the storage and thus this process can take a while.
Disconnecting a filesystem from its underlying storage guarantees that the filesystem will no longer be able to read/write the underlying storage. A forced stopped filesystem that hasn't been destructed yet is known as a "zombie" filesystem because it no longer allows you to acquire its root node and all I/O operations will fail with a ENODEV since the filesystem is no longer connected to its underlying storage.
A filesystem goes through the following lifetime states:
- Idle: filesystem was just created and hasn't been started yet.
- Started: filesystem was started and is connected to its underlying medium or storage. Filesystem is free to read/write its storage as needed. The root node may be acquired and a filesystem channel may be opened.
- Stopped: filesystem was stopped unforced and not isn't yet destroyed. No inodes are outstanding and no filesystem channels are open. The filesystem is disconnected from its underlying storage. Root node can not be acquired anymore and no filesystem channel can be opened anymore.
- Force Stopped (Zombie): filesystem was force stopped and is not yet destroyed. There are still inodes outstanding and/or open filesystem channels active. Filesystem is disconnected from its underlying storage. Root node can not be acquired anymore. Filesystem channel can not be acquired anymore.
- Destroyed: filesystem is gone for good.
This is handled by the kernel. Files and directories serialize read/write/seek operations to ensure that a read will return all the original data found in the region that it accesses and not a mix of original data and data that a concurrent write wants to place there. Additionally this serialization ensures that the current file position moves in a meaningful way and not in a way where it appears to erratically jump forward and backward between concurrently scheduled operations.
This is handled by the Filesystem base class. Inode acquisition and relinquishing is protected by an inode management lock and these operations are atomic. Furthermore writing the changed (meta-)data of an inode back to disk and deleting the the (meta-)data of an inode on disk are done atomically in the sense that no other thread of execution can acquire an inode that is currently in the process of a write-back or on-disk removal. Reacquiring an inode is also atomic.
Starting, stopping a filesystem and acquiring its root node (and any other node for that matter) are protected by the same inode management lock and are atomic with respect to each other. A filesystem must be started and not stopped in order to be able to acquire its root node or any other node. This mechanism is enforced by the Filesystem class and subclasses do not need to implement anything special to make this semantic work. Subclasses simply override the onStart and onStop methods to implement the filesystem specific portions of starting and stopping the filesystem.
Remember that a filesystem can not be stopped and neither deallocated as long as there is at least one acquired inode outstanding. Because of this and the fact that all filesystem operations expect at least one inode as input, non of the filesystem operation functions need to be protected with a filesystem specific lock.
The inode that is passed to an operation acts in a sense as a lock and its existence guarantees that the filesystem can not be stopped and deallocated while the operation is executing.