One basic question about FUSD that one might ask is: what is it good for? Why use it? In this section, we describe some of the situations in which FUSD has been the solution for us.
A problem that comes up frequently in modern operating systems is contention for a single resource by multiple competing processes. In UNIX, it's the job of a device driver to coordinate access to such resources. By accepting requests from user processes and (for example) queuing and serializing them, it becomes safe for processes that know nothing about each other to make requests in parallel to the same resource. Of course, kernel drivers do this job already, but they typically operate on top of hardware directly. However, kernel drivers can't easily be layered on top of other device drivers.
For example, consider a device such as a modem that is connected to a host via a serial port. Let's say we want to implement a device driver that allows multiple users to dial the telephone (e.g., echo 1-310-555-1212 > /dev/phone-dialer). Such a driver should be layered on top of the serial port driver--that is, it most likely wants to write to /dev/ttyS0, not directly to the UART hardware itself.
While it is possible to write to a logical file from within a kernel device driver, it is both tricky and considered bad practice. In the words of kernel hacker Dick Johnson, ``You should never write a [kernel] module that requires reading or writing to any logical device. The kernel is the thing that translates physical I/O to logical I/O. Attempting to perform logical I/O in the kernel is effectively going backwards.''
With FUSD, it's possible to layer device drivers because the driver is a user-space process, not a kernel module. A FUSD implementation of our hypothetical /dev/phone-dialer can open /dev/ttyS0 just as any other process would.
Typically, such layering is accomplished by system daemons. For example, the lpd daemon manages printers at a high level. Since it is a user-space process, it can access the physical printer devices using kernel device drivers (for example, using printer or network drivers). There a number of advantages to using FUSD instead:
Since a FUSD driver is just a regular user-space program, it can naturally use any of the enormous body of existing libraries that exist for almost any task. FUSD drivers can easily incorporate user interfaces, encryption, network protocols, threads, and almost anything else. In contrast, porting arbitrary C code into the kernel is difficult and usually a bad idea.
Since FUSD drivers run in their own process space, the rest of the system is protected from them. A buggy or malicious FUSD driver, at the very worst, can only corrupt itself. It's not possible for it to corrupt the kernel, other FUSD drivers, or even the processes that are using its devices. In contrast, a buggy kernel module can bring down any process in the system, or the entire kernel itself.
One particularly interesting application of FUSD that we've found very useful is as a way to let regular user-space libraries export device file APIs. For example, imagine you had a library which factored large composite numbers. Typically, it might have a C interface--say, a function called int *factorize(int bignum). With FUSD, it's possible to create a device file interface--say, a device called /dev/factorize to which clients can write(2) a big number, then read(2) back its factors.
This may sound strange, but device file APIs have at least three advantages over a typical library API. First, it becomes much more language independent--any language that can make system calls can access the factorization library. Second, the factorization code is running in a different address space; if it crashes, it won't crash or corrupt the caller. Third, and most interestingly, it is possible to use select(2) to wait for the factorization to complete. select(2) would make it easy for a client to factor a large number while remaining responsive to other events that might happen in the meantime. In other words, FUSD allows normal user-space libraries to integrate seamlessly with UNIX's existing, POSIX-standard event notification interface: select(2).
FUSD processes can be developed and debugged with all the normal user-space tools. Buggy drivers won't crash the system, but instead dump cores that can be analyzed. All of your favorite visual debuggers, memory bounds checkers, leak detectors, profilers, and other tools can be applied to FUSD drivers as they would to any other program.