并非总是需要为设备编写设备驱动程序,尤其是在没有两个应用程序会竞争使用设备的情况下。 最有用的例子是内存映射设备,但您也可以对 I/O 空间中的设备执行此操作(使用以下方法访问的设备:inb()和outb()等)。 如果您的进程以超级用户(root)身份运行,则可以使用mmap()调用将进程内存的某些部分映射到实际内存位置,通过mmap()映射 /dev/mem 的一部分。 完成此映射后,就可以像读写任何变量一样轻松地读写实际内存地址。
如果您的驱动程序需要响应中断,那么您确实需要在内核空间中工作,并且需要编写真正的设备驱动程序,因为目前没有好的方法将中断传递给用户进程。 虽然 DOSEMU 项目创建了一个名为 SIG(Silly Interrupt Generator,愚蠢中断生成器)的东西,允许将中断发布到用户进程(我相信是通过使用信号),但 SIG 速度不是特别快,应该被认为是像 DOSEMU 这样的情况下的最后手段。
中断是硬件发布的异步通知,用于警告设备驱动程序某种情况。 您可能在设置硬件时处理过 `IRQ's`; IRQ 是“中断请求线 (Interrupt ReQuest line)”,当设备想要与驱动程序通信时触发。 这可能是因为它有数据要提供给驱动器,或者因为它现在已准备好接收数据,或者因为驱动程序需要知道的某些其他“异常情况”。 它类似于用户级进程接收信号,非常相似,以至于相同的sigaction结构在内核中用于处理中断,就像在用户级程序中用于处理信号一样。 用户级进程的信号由内核传递,而内核的中断由硬件传递。
如果您的驱动程序必须同时可供多个进程访问,和/或管理资源的争用,那么您还需要在内核级别编写真正的设备驱动程序,而用户空间设备驱动程序将不足够甚至不可能。
用户空间驱动程序的一个很好的例子是vgalib库。 标准的read()和write()调用对于编写真正快速的图形驱动程序来说确实是不够的,因此,取而代之的是一个在概念上类似于设备驱动程序但在用户空间中运行的库。 任何使用它的进程必须以 setuid root 身份运行,因为它使用了ioperm()系统调用。 如果您有一个组,则非 setuid root 进程也可以写入 /dev/memmem或kmem被允许对 /dev/mem 具有写入权限,并且该进程已正确设置 setgid,但只有以 root 身份运行的进程才能执行ioperm()调用。
有几个与 VGA 图形相关的 I/O 端口。 vgalib 使用以下内容为其创建符号名称#define语句,然后发出ioperm()这样的调用,使进程可以直接从这些端口读取和写入
if (ioperm(CRT_IC, 1, 1)) { printf("VGAlib: can't get I/O permissions \n"); exit (-1); } ioperm(CRT_IM, 1, 1); ioperm(ATT_IW, 1, 1); [...]它只需要执行一次错误检查,因为ioperm()调用失败的唯一原因是它不是由超级用户调用的,并且此状态不会改变。
在发出此调用后,进程被允许使用inb和outb机器指令,但仅限于指定的端口。 可以通过包含以下内容访问这些指令,而无需直接用汇编编写
在安排端口 I/O 之后,vgalib安排使用以下代码直接写入内核内存
/* open /dev/mem */ if ((mem_fd = open("/dev/mem", O_RDWR) ) < 0) { printf("VGAlib: can't open /dev/mem \n"); exit (-1); } /* mmap graphics memory */ if ((graph_mem = malloc(GRAPH_SIZE + (PAGE_SIZE-1))) == NULL) { printf("VGAlib: allocation error \n"); exit (-1); } if ((unsigned long)graph_mem % PAGE_SIZE) graph_mem += PAGE_SIZE - ((unsigned long)graph_mem % PAGE_SIZE); graph_mem = (unsigned char *)mmap( (caddr_t)graph_mem, GRAPH_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, GRAPH_BASE ); if ((long)graph_mem < 0) { printf("VGAlib: mmap error \n"); exit (-1); }它首先打开 /dev/mem,然后分配足够的内存,以便可以在页面(4 KB)边界上完成映射,然后尝试映射。GRAPH_SIZE是 VGA 内存的大小,并且GRAPH_BASE是 /dev/mem 中 VGA 内存的起始地址。 然后通过写入返回的地址mmap(),进程实际上是在写入屏幕内存。
如果您想要一个更像内核级驱动程序但不在内核空间中运行的驱动程序,您也可以创建一个 fifo 或命名管道。 这通常位于 /dev/ 目录中(尽管不是必须的),并且一旦设置好,其作用基本上就像一个设备。 但是,fifo 是单向的——它们只有一个读取器和一个写入器。
例如,过去如果您有一个 PS/2 样式的鼠标,并且想要运行 XFree86,则必须创建一个名为 /dev/mouse 的 fifo,并运行一个名为 mconv 的程序,该程序从 /dev/psaux 读取 PS/2 鼠标“数据”,并将等效的 microsoft 样式“数据”写入 /dev/mouse。 然后 XFree86 将从 /dev/mouse 读取“数据”,就好像有一个 microsoft 鼠标连接到 /dev/mouse 一样。 即使 XFree86 现在能够读取 PS/2 样式的“数据”,此示例中的概念仍然成立。(如果您有更好的例子,我很乐意看到它。)
版权所有 (C) 1992, 1993, 1994, 1995, 1996 Michael K. Johnson, johnsonm@redhat.com。