Descriptors vs. Direct Access
The technique presented last week for accessing I/O ports is portable
and safe but too slow for certain applications. Instead of using a
descriptor, Linux allows a process to write to an I/O port directly,
thereby avoiding the overhead associated with write() and read().
The outb() Syscall
A process can call the function outb() to write directly to an I/O
port. outb() takes two arguments: the data to be written to the I/O
port and the I/O port number. For example, to write the character 0x01
to our remote control, call outb() as follows:
outb(0x01, 0x378+2); /*turn the lights on*/
The use of symbolic constants can make the code more readable:
const char ON=0x01;
const char OFF=0x00;
const int PORT=0x378+2;
outb(OFF, PORT); /*turn the lights off*/
This is all well and good; however, by default Linux terminates a
process that attempts to access an I/O port directly (with good
reason!). A process that accesses I/O ports directly must get the
necessary permissions beforehand using one of two methods. The first
(and safer) way is to call ioperm(). ioperm() allows a process with
super-user privileges access to each I/O port in the range 0-0x3ff.
Thus, if you wish to access the ports 0x378, 0x379, and 0x380 (the
ports that access /dev/lp1), call ioperm() as follows:
ioperm(0x378, 3, 1);
The first argument is the first port in the range. The second argument
is the number of ports in the range. The last argument is either 1 or
0. The value 1 allows access to the ports in the range and 0 denies
access to them. ioperm() returns 0 on success and -1 on error.
Alternatively, a process with super-user privileges can enable access
to all the I/O ports at once by calling the iopl() function as follows:
The above call causes a process to switch to what is known as "I/O
protection level 3", which grants a process full access to the I/O bus.
As a rule, you should use ioperm() to obtain permission to access I/O
ports; use iopl() only if your process needs to access ports higher
A note on last week's tip:
The device names "dev/port" and "dev/lp1" should read "/dev/port"
and "/dev/lp1", respectively.