A Unix programmer who’s addressing kernel issues for the first time might well be nervous about writing a module. Writing a user program that reads and writes directly to the device ports is much easier.
Indeed, there are some arguments in favor of user-space programming, and sometimes writing a so-called user-space device driver is a wise alternative to kernel hacking.
The advantages of user-space drivers can be summarized as follows:
The full C library can be linked in. The driver can perform many exotic tasks without resorting to external programs (the utility programs implementing usage policies that are usually distributed along with the driver itself).
The programmer can run a conventional debugger on the driver code without having to go through contortions to debug a running kernel.
If a user-space driver hangs, you can simply kill it. Problems with the driver are unlikely to hang the entire system, unless the hardware being controlled is really misbehaving.
User memory is swappable, unlike kernel memory. An infrequently used device with a huge driver won’t occupy RAM that other programs could be using, except when it is actually in use.
A well-designed driver program can still allow concurrent access to a device.
An example of a user-space driver is the X server: it knows exactly what the hardware can do and what it can’t, and it offers the graphic resources to all X clients. Note, however, that there is a slow but steady drift toward frame-buffer-based graphics environments, where the X server acts only as a server based on a real kernel-space device driver for actual graphic manipulation.
Usually, the writer of a user-space driver implements a server process, taking over from the kernel the task of being the single agent in charge of hardware control. Client applications can then connect to the server to perform actual communication with the device; a smart driver process can thus allow concurrent access to the device. This is exactly how the X server works.
Another example of a user-space driver is the gpm mouse server: it performs arbitration of the mouse device between clients, so that several mouse-sensitive applications can run on different virtual consoles.
Sometimes, though, the user-space driver grants device access to a
single program. This is how
libsvga works. The
library, which turns a TTY into a graphics display, gets linked to the
application, thus supplementing the application’s capabilities without
resorting to a central authority (e.g., a server). This approach
usually gives you better performance because it skips the
communication overhead, but it requires the application to run as a
privileged user (this is one of the problems being solved by the frame
buffer device driver running in kernel space).
But the user-space approach to device driving has a number of drawbacks. The most important are as follows:
Interrupts are not available in user space. The only way around this (on the x86) is to use the vm86 system call, which imposes a performance penalty.
Direct access to memory is possible only by mmapping
/dev/mem, and only a privileged user can do that.
Access to I/O ports is available only after calling ioperm or iopl. Moreover, not all platforms support these system calls, and access to
/dev/portcan be too slow to be effective. Both the system calls and the device file are reserved to a privileged user.
Response time is slower, because a context switch is required to transfer information or actions between the client and the hardware.
Worse yet, if the driver has been swapped to disk, response time is unacceptably long. Using the mlock system call might help, but usually you’ll need to lock several memory pages, because a user-space program depends on a lot of library code. mlock, too, is limited to privileged users.
The most important devices can’t be handled in user space, including, but not limited to, network interfaces and block devices.
As you see, user-space drivers can’t do that much after all. Interesting applications nonetheless exist: for example, support for SCSI scanner devices (implemented by the SANE package) and CD writers (implemented by cdrecord and other tools). In both cases, user-level device drivers rely on the “SCSI generic” kernel driver, which exports low-level SCSI functionality to user-space programs so they can drive their own hardware.
In order to write a user-space driver, some hardware knowledge is sufficient, and there’s no need to understand the subtleties of kernel software. We won’t discuss user-space drivers any further in this book, but will concentrate on kernel code instead.
One case in which working in user space might make sense is when you are beginning to deal with new and unusual hardware. This way you can learn to manage your hardware without the risk of hanging the whole system. Once you’ve done that, encapsulating the software in a kernel module should be a painless operation.
 The system call is not discussed in this book because the subject matter of the text is kernel drivers; moreover, vm86 is too platform specific to be really interesting.