A good example of a user-space driver is the vgalib library. The standard read() and write() calls are really inadequate for writing a really fast graphics driver, and so instead there is a library which acts conceptually like a device driver, but runs in user space. Any processes which use it must run setuid root, because it uses the ioperm() system call. It is possible for a process that is not setuid root to write to /dev/mem if you have a group mem or kmem which is allowed write permission to /dev/mem and the process is properly setgid, but only a process running as root can execute the ioperm() call.
There are several I/O ports associated with VGA graphics. vgalib creates symbolic names for this with #define statements, and then issues the ioperm() call like this to make it possible for the process to read and write directly from and to those ports:
It only needs to do error checking once, because the only reason for the ioperm() call to fail is that it is not being called by the superuser, and this status is not going to change.
After making this call, the process is allowed to use inb and outb machine instructions, but only on the specified ports. These instructions can be accessed without writing directly in assembly by including <linux/asm>, but will only work if you compile with optimization on, by giving the -O? to gcc. Read <linux/asm> for details.
After arranging for port I/O, vgalib arranges for writing directly to kernel memory with the following code:
It first opens /dev/mem, then allocates memory enough so that the mapping can be done on a page (4 KB) boundary, and then attempts the map. GRAPH_SIZE is the size of VGA memory, and GRAPH_BASE is the first address of VGA memory in /dev/mem. Then by writing to the address that is returned by mmap(), the process is actually writing to screen memory.