访问 I/O 端口的例程位于 /usr/include/asm/io.h
(或内核源代码发行版中的 linux/include/asm-i386/io.h
) 中。那里的例程是内联宏,因此只需 #include <asm/io.h>
即可;您不需要任何额外的库。
由于 gcc 的一个限制(在我所知道的所有版本中都存在,包括 egcs),您必须使用开启优化(gcc -O1
或更高)来编译任何使用这些例程的源代码,或者在 #include <asm/io.h>
之前使用 #define extern static
(之后记得 #undef extern
)。
对于调试,您可以使用 gcc -g -O
(至少对于现代版本的 gcc),尽管优化有时会使调试器的行为有点奇怪。如果这困扰您,请将使用 I/O 端口访问的例程放在单独的源文件中,并仅对该文件开启优化进行编译。
在您访问任何端口之前,您必须授予您的程序执行此操作的权限。这通过在程序开始附近(在任何 I/O 端口访问之前)调用 ioperm()
函数(在 unistd.h
中声明,并在内核中定义)来完成。语法是 ioperm(from, num, turn_on)
,其中 from
是要授予访问权限的第一个端口号,num
是要授予访问权限的连续端口数。例如,ioperm(0x300, 5, 1)
将授予对端口 0x300 到 0x304(总共 5 个端口)的访问权限。最后一个参数是一个布尔值,指定是授予程序对端口的访问权限(true (1))还是移除访问权限(false (0))。您可以多次调用 ioperm()
以启用多个非连续端口。有关语法的详细信息,请参阅 ioperm(2)
手册页。
ioperm()
调用要求您的程序具有 root 权限;因此,您需要以 root 用户身份运行它,或者使其 setuid root。在您调用 ioperm()
以启用您想要使用的端口之后,您可以放弃 root 权限。您不需要在程序结束时使用 ioperm(..., 0)
显式地放弃您的端口访问权限;这会在进程退出时自动完成。
setuid()
到非 root 用户不会禁用 ioperm()
授予的端口访问权限,但 fork()
会(子进程不会获得访问权限,但父进程会保留它)。
ioperm()
只能授予对端口 0x000 到 0x3ff 的访问权限;对于更高的端口,您需要使用 iopl()
(它允许您一次访问所有端口)。使用级别参数 3(即 iopl(3)
)来授予您的程序访问所有 I/O 端口的权限(所以要小心 --- 访问错误的端口可能会对您的计算机造成各种不良影响)。同样,您需要 root 权限才能调用 iopl()
。有关详细信息,请参阅 iopl(2)
手册页。
要从端口输入一个字节(8 位),请调用 inb(port)
,它会返回获取的字节。要输出一个字节,请调用 outb(value, port)
(请注意参数的顺序)。要从端口 x
和 x+1
输入一个字(16 位)(每个端口一个字节以形成字,使用汇编器指令 inw
),请调用 inw(x)
。要向这两个端口输出一个字,请使用 outw(value, x)
。如果您不确定要使用哪个端口指令(字节或字),您可能需要 inb()
和 outb()
--- 大多数设备都设计用于字节方式的端口访问。请注意,所有端口访问指令至少需要大约一微秒才能执行。
inb_p()
、outb_p()
、inw_p()
和 outw_p()
宏的工作方式与上面的宏相同,但它们在端口访问后会增加一个短暂的(大约一微秒)延迟;您可以在 #include <asm/io.h>
之前使用 #define REALLY_SLOW_IO
将延迟设置为大约四微秒。这些宏通常(除非您 #define SLOW_IO_BY_JUMPING
,这可能不太准确)使用端口输出到端口 0x80 来实现延迟,因此您需要首先使用 ioperm()
授予对端口 0x80 的访问权限(输出到端口 0x80 不应影响系统的任何部分)。有关更多通用的延迟方法,请继续阅读。
在 Linux 手册页集合的较新版本中,有关于 ioperm(2)
、iopl(2)
和上述宏的手册页。
/dev/port
访问 I/O 端口的另一种方法是 open()
/dev/port
(字符设备,主设备号 1,次设备号 4)以进行读取和/或写入(stdio f*()
函数具有内部缓冲,因此请避免使用它们)。然后 lseek()
到文件中的适当字节(文件位置 0 = 端口 0x00,文件位置 1 = 端口 0x01,依此类推),并从中 read()
或 write()
一个字节或字。
当然,为了使此方法有效,您的程序需要对 /dev/port
具有读/写访问权限。此方法可能比上面的常规方法慢,但不需要编译器优化或 ioperm()
。它也不需要 root 访问权限,如果您授予非 root 用户或组对 /dev/port
的访问权限 --- 但这在系统安全方面是非常糟糕的做法,因为有可能通过使用 /dev/port
直接访问硬盘、网卡等来损害系统,甚至获得 root 访问权限。
您不能使用 select(2)
或 poll(2)
来读取 /dev/port,因为硬件没有在输入端口中的值更改时通知 CPU 的机制。