下一页 上一页 目录

2. 在 C 程序中使用 I/O 端口

2.1 常规方法

访问 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)(请注意参数的顺序)。要从端口 xx+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) 和上述宏的手册页。

2.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 的机制。


下一页 上一页 目录