The highest level file systems reside outside the kernel. They are implemented either as a process or as a run-time library. Most such file systems are accessed via the NFS protocol. That is, the process that implements them registers with the kernel as an NFS server, although the files it manages are not necessarily remote.
The primary benefits of user-level file systems are easier development, easier debugging, and portability. However, user level file systems suffer from inherently poor performance. Figure fig-level-user shows how many steps it takes the system to satisfy an access request through a user-level file server. Each crossing of the dashed line requires a context switch and, sometimes, a data copy.
Additionally, user level implementation raises the danger of deadlock, as the process implementing the file system must interact with the operating system, sometimes regarding the very file system it is implementing. Finally, user level implementation creates new failure modes. If there is a bug in a kernel-resident file system, the system will crash; though highly undesirable, this is the familiar ``fail-stop'' failure model. In contrast, when an out of kernel file system hangs or exits, processes that access the now-dead file system live on, possibly propagating erroneous results to other processes and machines.2
Examples of out-of-kernel file systems are the Amd [Pendry91,Stewart93] and Automountd [Callaghan89] automounters, Blaze's Cfsd encrypting file system [Blaze93], and Amd derivatives including Hlfsd [Zadok93b], AutoCacher [Minnich93], and Restore-o-Mounter [Moran93].
A few file systems at the user level have been implemented as a user-level library. One such example is Systas [Lord96], a file system for Linux that adds an extra measure of flexibility by allowing users to write Scheme code to implement the file system semantics. Another, also for Linux, is Userfs [Fitzhardinge94]. For example, to write a new file system using Userfs, the implementor fills in a set of C++ stub file system calls -- the file system's version of open, close, lookup, read, write, unlink, etc. Developers have all the flexibility of user level C++ programs. Then, they compile their code and link it with the provided Userfs run-time library. The library provides the file system driver engine and the necessary linkage to special kernel hooks. The result is a process that implements the file system. When run, the kernel will divert file system calls to the custom-linked user-level program they just linked with.
Such flexibility is very appealing. Unfortunately, the two examples just mentioned are limited to Linux and cannot be easily ported to other operating systems because they require special kernel support. Also, they still require the user to write a full implementation of each file system call.