用户空间 I/O HOWTO

作者:

Hans-Jürgen Koch Linux 开发者, Linutronix

日期:

2006-12-11

关于本文档

翻译

如果您知道本文档的任何翻译版本,或者您有兴趣翻译它,请发送电子邮件给我 hjk@hansjkoch.de

前言

对于许多类型的设备,创建 Linux 内核驱动程序有点过头了。真正需要的只是某种处理中断并提供对设备内存空间访问的方法。控制设备的逻辑不一定必须在内核中,因为设备不需要利用内核提供的任何其他资源。一种常见的此类设备是工业 I/O 卡。

为了解决这种情况,设计了用户空间 I/O 系统 (UIO)。对于典型的工业 I/O 卡,只需要一个非常小的内核模块。驱动程序的主要部分将在用户空间中运行。这简化了开发并降低了内核模块中出现严重错误的风险。

请注意,UIO 不是通用的驱动程序接口。那些已经被其他内核子系统(如网络、串口或 USB)很好处理的设备不是 UIO 驱动程序的候选者。最适合 UIO 驱动程序的硬件应满足以下所有条件

  • 设备具有可以映射的内存。通过写入此内存可以完全控制设备。

  • 设备通常会生成中断。

  • 设备不适合任何标准内核子系统。

致谢

我要感谢 Linutronix 的 Thomas Gleixner 和 Benedikt Spranger,他们不仅编写了大部分 UIO 代码,还通过向我提供各种背景信息,极大地帮助了我编写这个 HOWTO。

反馈

发现本文档有任何错误吗?(或者可能有什么正确的地方?)我很乐意收到您的来信。请发送电子邮件至 hjk@hansjkoch.de

关于 UIO

如果您使用 UIO 作为卡的驱动程序,您将获得以下好处

  • 只需编写和维护一个小内核模块。

  • 使用您习惯的所有工具和库,在用户空间中开发驱动程序的主要部分。

  • 驱动程序中的错误不会导致内核崩溃。

  • 无需重新编译内核即可更新驱动程序。

UIO 的工作原理

每个 UIO 设备都通过一个设备文件和几个 sysfs 属性文件进行访问。第一个设备的设备文件将被称为 /dev/uio0,后续设备的设备文件将被称为 /dev/uio1/dev/uio2,以此类推。

/dev/uioX 用于访问卡的地址空间。只需使用 mmap() 即可访问卡的寄存器或 RAM 位置。

中断通过从 /dev/uioX 读取来处理。一旦发生中断,从 /dev/uioX 进行阻塞的 read() 将立即返回。您也可以在 /dev/uioX 上使用 select() 来等待中断。从 /dev/uioX 读取的整数值表示中断总数。您可以使用此数字来确定是否错过了某些中断。

对于某些硬件,其内部有多个中断源,但没有单独的 IRQ 掩码和状态寄存器,在某些情况下,如果内核处理程序通过写入芯片的 IRQ 寄存器来禁用它们,则用户空间可能无法确定中断源是什么。在这种情况下,内核必须完全禁用 IRQ,以保持芯片的寄存器不动。现在,用户空间部分可以确定中断的原因,但无法重新启用中断。另一个极端情况是重新启用中断是对组合的 IRQ 状态/确认寄存器进行读-修改-写操作的芯片。如果同时发生新的中断,这将是竞争的。

为了解决这些问题,UIO 还实现了 write() 函数。它通常不使用,对于只有单个中断源或具有单独的 IRQ 掩码和状态寄存器的硬件可以忽略它。但是,如果您需要它,则写入 /dev/uioX 将调用驱动程序实现的 irqcontrol() 函数。您必须写入一个 32 位值,该值通常为 0 或 1,以禁用或启用中断。如果驱动程序未实现 irqcontrol(),则 write() 将返回 -ENOSYS

为了正确处理中断,您的自定义内核模块可以提供自己的中断处理程序。它将自动被内置处理程序调用。

对于不产生中断但需要轮询的卡,可以设置一个计时器,该计时器以可配置的时间间隔触发中断处理程序。此中断模拟是通过从计时器的事件处理程序调用 uio_event_notify() 来完成的。

每个驱动程序都提供用于读取或写入变量的属性。这些属性可以通过 sysfs 文件访问。自定义内核驱动程序模块可以将其自己的属性添加到 UIO 驱动程序拥有的设备,但此时未添加到 UIO 设备本身。如果发现有用,将来可能会更改。

UIO 框架提供了以下标准属性

  • name: 您的设备的名称。建议为此使用内核模块的名称。

  • version: 由您的驱动程序定义的版本字符串。这允许驱动程序的 user space 部分处理不同版本的内核模块。

  • event: 自上次读取设备节点以来,驱动程序处理的中断总数。

这些属性出现在 /sys/class/uio/uioX 目录下。请注意,此目录可能是一个符号链接,而不是一个真正的目录。任何访问它的用户空间代码都必须能够处理此问题。

每个 UIO 设备都可以使一个或多个内存区域可用于内存映射。这是必要的,因为某些工业 I/O 卡需要在驱动程序中访问多个 PCI 内存区域。

每个映射在 sysfs 中都有自己的目录,第一个映射显示为 /sys/class/uio/uioX/maps/map0/。后续映射创建目录 map1/map2/ 等。只有当映射的大小不为 0 时,这些目录才会出现。

每个 mapX/ 目录都包含四个只读文件,显示内存的属性

  • name: 此映射的字符串标识符。这是可选的,字符串可以为空。驱动程序可以设置它,以便用户空间更容易找到正确的映射。

  • addr: 可以映射的内存地址。

  • size: addr 指向的内存的大小,以字节为单位。

  • offset: 必须添加到 mmap() 返回的指针的偏移量,以字节为单位,才能到达实际的设备内存。如果设备的内存未页对齐,这一点很重要。请记住,mmap() 返回的指针始终是页对齐的,因此始终添加此偏移量是一种好的做法。

从用户空间,不同的映射通过调整 mmap() 调用的 offset 参数来区分。要映射映射 N 的内存,您必须使用 N 乘以页面大小作为偏移量

offset = N * getpagesize();

有时会有一些硬件具有类似内存的区域,无法使用此处描述的技术进行映射,但仍然可以通过用户空间访问它们。最常见的示例是 x86 ioport。在 x86 系统上,用户空间可以使用 ioperm()iopl()inb()outb() 和类似的函数访问这些 ioport。

由于这些 ioport 区域无法映射,因此它们不会像上面描述的普通内存一样出现在 /sys/class/uio/uioX/maps/ 下。如果没有关于硬件必须提供的端口区域的信息,驱动程序的 user space 部分很难找出哪些端口属于哪个 UIO 设备。

为了解决这种情况,添加了新的目录 /sys/class/uio/uioX/portio/。只有当驱动程序想要将关于一个或多个端口区域的信息传递给用户空间时,它才存在。如果是这种情况,则名为 port0port1 等的子目录将出现在 /sys/class/uio/uioX/portio/ 下。

每个 portX/ 目录都包含四个只读文件,显示端口区域的名称、起始位置、大小和类型

  • name: 此端口区域的字符串标识符。该字符串是可选的,可以为空。驱动程序可以设置它,以便用户空间更容易找到某个端口区域。

  • start: 此区域的第一个端口。

  • size: 此区域中的端口数。

  • porttype: 描述端口类型的字符串。

编写您自己的内核模块

请查看 uio_cif.c 作为示例。以下段落解释了此文件的不同部分。

struct uio_info

此结构告诉框架有关您的驱动程序的详细信息。某些成员是必需的,而另一些成员是可选的。

  • const char *name: 必需。您的驱动程序的名称,因为它将出现在 sysfs 中。我建议为此使用您的模块的名称。

  • const char *version: 必需。此字符串出现在 /sys/class/uio/uioX/version 中。

  • struct uio_mem mem[ MAX_UIO_MAPS ]: 如果您有可以使用 mmap() 映射的内存,则为必需。对于每个映射,您需要填写一个 uio_mem 结构。有关详细信息,请参见下面的描述。

  • struct uio_port port[ MAX_UIO_PORTS_REGIONS ]: 如果您想将关于 ioport 的信息传递给用户空间,则为必需。对于每个端口区域,您需要填写一个 uio_port 结构。有关详细信息,请参见下面的描述。

  • long irq: 必需。如果您的硬件产生中断,则您的模块的任务是在初始化期间确定 irq 编号。如果您没有硬件产生的中断,但想以某种其他方式触发中断处理程序,请将 irq 设置为 UIO_IRQ_CUSTOM。如果您根本没有中断,则可以将 irq 设置为 UIO_IRQ_NONE,但这很少有意义。

  • unsigned long irq_flags: 如果您已将 irq 设置为硬件中断编号,则为必需。此处给出的标志将用于调用 request_irq()

  • int (*mmap)(struct uio_info *info, struct vm_area_struct *vma): 可选。如果您需要特殊的 mmap() 函数,可以在此处设置它。如果此指针不为 NULL,则将调用您的 mmap() 来代替内置的 mmap()

  • int (*open)(struct uio_info *info, struct inode *inode): 可选。您可能想要拥有自己的 open(),例如,仅当实际使用您的设备时才启用中断。

  • int (*release)(struct uio_info *info, struct inode *inode): 可选。如果您定义了自己的 open(),您可能还想要一个自定义的 release() 函数。

  • int (*irqcontrol)(struct uio_info *info, s32 irq_on): 可选。如果您需要能够通过写入 /dev/uioX 来启用或禁用来自用户空间的中断,您可以实现此函数。参数 irq_on 将为 0 以禁用中断,为 1 以启用中断。

通常,您的设备将具有一个或多个可以映射到用户空间的内存区域。对于每个区域,您必须在 mem[] 数组中设置一个 struct uio_mem。以下是 struct uio_mem 字段的描述

  • const char *name: 可选。设置此值以帮助识别内存区域,它将显示在相应的 sysfs 节点中。

  • int memtype: 如果使用映射,则为必需。如果您有要映射的卡上的物理内存,则将其设置为 UIO_MEM_PHYS。对于逻辑内存(例如,使用 __get_free_pages() 分配但不是 kmalloc()),请使用 UIO_MEM_LOGICAL。还有 UIO_MEM_VIRTUAL 用于虚拟内存。

  • phys_addr_t addr: 如果使用映射,则为必需。填写您的内存块的地址。此地址是出现在 sysfs 中的地址。

  • resource_size_t size: 填写 addr 指向的内存块的大小。如果 size 为零,则该映射被认为是未使用的。请注意,您*必须*使用零初始化所有未使用的映射的 size

  • void *internal_addr: 如果您必须从您的内核模块中访问此内存区域,您将需要使用类似 ioremap() 的方法在内部对其进行映射。此函数返回的地址无法映射到用户空间,因此您不得将其存储在 addr 中。请使用 internal_addr 来记住这样的地址。

请不要触摸 struct uio_memmap 元素!它由 UIO 框架用于为此映射设置 sysfs 文件。只需保持原样即可。

有时,您的设备可能有一个或多个无法映射到用户空间的端口区域。但是,如果用户空间还有其他访问这些端口的可能性,那么在 sysfs 中提供关于端口的信息是有意义的。对于每个区域,您必须在 port[] 数组中设置一个 struct uio_port。以下是 struct uio_port 字段的描述

  • char *porttype: 必需。将其设置为预定义的常量之一。对于 x86 架构中发现的 ioport,请使用 UIO_PORT_X86

  • unsigned long start: 如果使用端口区域,则为必需。填写此区域的第一个端口的编号。

  • unsigned long size: 填写此区域中的端口数。如果 size 为零,则该区域被认为是未使用的。请注意,您*必须*使用零初始化所有未使用的区域的 size

请不要触摸 struct uio_portportio 元素!它由 UIO 框架在内部用于为此区域设置 sysfs 文件。只需保持原样即可。

添加中断处理程序

在您的中断处理程序中需要执行的操作取决于您的硬件以及您希望如何处理它。您应该尽量减少内核中断处理程序中的代码量。如果您的硬件在每次中断后不需要您执行任何操作,则您的处理程序可以为空。

另一方面,如果您的硬件*需要*在每次中断后执行某些操作,那么您*必须*在您的内核模块中执行它。请注意,您不能依赖驱动程序的 user space 部分。您的用户空间程序可以随时终止,可能会使您的硬件处于仍然需要正确处理中断的状态。

可能还会有一些应用程序,您希望在每次中断时从硬件读取数据并将其缓冲在您为此目的分配的一块内核内存中。使用此技术,如果您的用户空间程序错过中断,您可以避免数据丢失。

关于共享中断的说明:您的驱动程序应该尽可能支持中断共享。当且仅当您的驱动程序可以检测到您的硬件是否触发了中断时,才有可能进行中断共享。这通常通过查看中断状态寄存器来完成。如果您的驱动程序看到 IRQ 位实际上已设置,它将执行其操作,并且处理程序返回 IRQ_HANDLED。如果驱动程序检测到不是您的硬件导致了中断,它将不执行任何操作并返回 IRQ_NONE,允许内核调用下一个可能的中断处理程序。

如果您决定不支持共享中断,您的卡将无法在没有可用中断的计算机中工作。由于这经常发生在 PC 平台上,因此您可以通过支持中断共享来避免很多麻烦。

使用 uio_pdrv 作为平台设备

在许多情况下,平台设备的 UIO 驱动程序可以以通用方式处理。在您定义 struct platform_device 的相同位置,您只需也实现您的中断处理程序并填写您的 struct uio_info。然后,指向此 struct uio_info 的指针用作您的平台设备的 platform_data

您还需要设置一个 struct resource 数组,其中包含您的内存映射的地址和大小。此信息使用 struct platform_device.resource.num_resources 元素传递给驱动程序。

您现在必须将 struct platform_device.name 元素设置为 "uio_pdrv" 以使用通用的 UIO 平台设备驱动程序。此驱动程序将根据给定的资源填写 mem[] 数组,并注册设备。

此方法的优点是您只需要编辑一个您无论如何都需要编辑的文件。您无需创建额外的驱动程序。

使用 uio_pdrv_genirq 作为平台设备

特别是在嵌入式设备中,您经常会发现芯片的 IRQ 引脚连接到其自己的专用中断线上。在这种情况下,如果您能真正确定中断没有被共享,我们可以将 uio_pdrv 的概念更进一步,并使用通用的中断处理程序。这就是 uio_pdrv_genirq 所做的。

此驱动程序的设置与上述 uio_pdrv 的描述相同,只是您不实现中断处理程序。struct uio_info.handler 元素必须保持 NULL.irq_flags 元素不得包含 IRQF_SHARED

您需要将 struct platform_device.name 元素设置为 "uio_pdrv_genirq" 才能使用此驱动程序。

uio_pdrv_genirq 的通用中断处理程序将简单地使用 disable_irq_nosync() 禁用中断线。完成其工作后,用户空间可以通过向 UIO 设备文件写入 0x00000001 重新启用中断。该驱动程序已经实现了一个 irq_control() 使其成为可能,您不得实现自己的。

使用 uio_pdrv_genirq 不仅可以节省几行中断处理程序代码。您也不需要了解任何关于芯片内部寄存器的知识来创建驱动程序的内核部分。您只需要知道芯片连接的引脚的 IRQ 编号即可。

当在启用设备树的系统中使用时,驱动程序需要使用设置为该节点 "compatible" 字符串的 "of_id" 模块参数进行探测,该驱动程序应该处理该节点。默认情况下,节点的名称(没有单元地址)作为用户空间中 UIO 设备的名称公开。要设置自定义名称,可以在 DT 节点中指定名为 "linux,uio-name" 的属性。

为平台设备使用 uio_dmem_genirq

除了静态分配的内存范围之外,还可能希望在用户空间驱动程序中使用动态分配的区域。特别是,能够访问通过 dma-mapping API 提供的内存可能特别有用。uio_dmem_genirq 驱动程序提供了一种实现此目的的方法。

此驱动程序的使用方式与 "uio_pdrv_genirq" 驱动程序在中断配置和处理方面类似。

struct platform_device.name 元素设置为 "uio_dmem_genirq" 才能使用此驱动程序。

使用此驱动程序时,请填写 struct platform_device.platform_data 元素,该元素类型为 struct uio_dmem_genirq_pdata,其中包含以下元素

  • struct uio_info uioinfo:与 uio_pdrv_genirq 平台数据使用的结构相同

  • unsigned int *dynamic_region_sizes:指向要映射到用户空间的动态内存区域大小列表的指针。

  • unsigned int num_dynamic_regionsdynamic_region_sizes 数组中的元素数量。

平台数据中定义的动态区域将附加到平台设备资源之后的 `` mem[] `` 数组中,这意味着静态和动态内存区域的总数不能超过 MAX_UIO_MAPS

动态内存区域将在 UIO 设备文件 /dev/uioX 打开时分配。与静态内存资源类似,动态区域的内存区域信息随后通过 sysfs 在 /sys/class/uio/uioX/maps/mapY/* 中可见。动态内存区域将在 UIO 设备文件关闭时释放。当没有进程保持设备文件打开时,返回给用户空间的地址为 ~0。

用用户空间编写驱动程序

一旦您拥有适用于您的硬件的工作内核模块,您就可以编写驱动程序的用户空间部分。您不需要任何特殊的库,您的驱动程序可以用任何合理的语言编写,您可以使用浮点数等等。简而言之,您可以使用您通常用于编写用户空间应用程序的所有工具和库。

获取关于您的 UIO 设备的信息

关于所有 UIO 设备的信息都可以在 sysfs 中找到。您的驱动程序中应该做的第一件事是检查 nameversion 以确保您与正确的设备通信,并且其内核驱动程序具有您期望的版本。

您还应该确保您需要的内存映射存在并且具有您期望的大小。

有一个名为 lsuio 的工具,它列出 UIO 设备及其属性。它可以在这里找到

http://www.osadl.org/projects/downloads/UIO/user/

使用 lsuio,您可以快速检查您的内核模块是否已加载以及它导出了哪些属性。请查看手册页以获取详细信息。

lsuio 的源代码可以用作获取关于 UIO 设备信息的示例。文件 uio_helper.c 包含许多您可以在用户空间驱动程序代码中使用的函数。

mmap() 设备内存

在您确保您拥有正确的设备以及您需要的内存映射后,您所要做的就是调用 mmap() 以将设备的内存映射到用户空间。

mmap() 调用的参数 offset 对于 UIO 设备具有特殊的含义:它用于选择您要映射的设备的哪个映射。要映射映射 N 的内存,您必须使用 N 乘以页面大小作为您的偏移量

offset = N * getpagesize();

N 从零开始,因此如果您只有一个内存范围要映射,请设置 offset = 0。此技术的缺点是内存始终从其起始地址开始映射。

等待中断

在您成功映射了您的设备内存后,您可以像访问普通数组一样访问它。通常,您将执行一些初始化。之后,您的硬件开始工作,并在完成后立即生成一个中断,有可用数据,或者因为发生了错误需要您的注意。

/dev/uioX 是一个只读文件。read() 将始终阻塞直到发生中断。对于 read()count 参数只有一个合法值,即带符号的 32 位整数的大小 (4)。count 的任何其他值都会导致 read() 失败。读取的带符号的 32 位整数是您设备的中断计数。如果该值比您上次读取的值大 1,则一切正常。如果差值大于 1,则您错过了中断。

您也可以在 /dev/uioX 上使用 select()

通用 PCI UIO 驱动程序

通用驱动程序是一个名为 uio_pci_generic 的内核模块。它可以与任何符合 PCI 2.3 (大约 2002 年) 和任何符合 PCI Express 设备的设备一起工作。使用它,您只需要编写用户空间驱动程序,从而无需编写特定于硬件的内核模块。

使驱动程序识别设备

由于该驱动程序未声明任何设备 ID,因此它不会自动加载,也不会自动绑定到任何设备,您必须自己加载它并将 ID 分配给驱动程序。例如

modprobe uio_pci_generic
echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id

如果您的设备已经有一个特定于硬件的内核驱动程序,则通用驱动程序仍然不会绑定到它,在这种情况下,如果您想使用通用驱动程序(您为什么要这样做?),您必须手动解除绑定特定于硬件的驱动程序并绑定通用驱动程序,如下所示

echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind

您可以通过在 sysfs 中查找设备来验证该设备是否已绑定到驱动程序,例如以下所示

ls -l /sys/bus/pci/devices/0000:00:19.0/driver

如果成功,应该打印

.../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic

请注意,通用驱动程序不会绑定到旧的 PCI 2.2 设备。如果绑定设备失败,请运行以下命令

dmesg

并在输出中查找失败原因。

关于 uio_pci_generic 的须知

中断使用 PCI 命令寄存器中的中断禁用位和 PCI 状态寄存器中的中断状态位来处理。所有符合 PCI 2.3 (大约 2002 年) 的设备和所有符合 PCI Express 设备的设备都应该支持这些位。uio_pci_generic 检测到这种支持,并且不会绑定到不支持命令寄存器中的中断禁用位的设备。

在每次中断时,uio_pci_generic 设置中断禁用位。这会阻止设备生成进一步的中断,直到清除该位。用户空间驱动程序应该在阻塞和等待更多中断之前清除该位。

使用 uio_pci_generic 编写用户空间驱动程序

用户空间驱动程序可以使用 pci sysfs 接口,或者封装它的 libpci 库,来与设备通信并通过写入命令寄存器来重新启用中断。

使用 uio_pci_generic 的示例代码

以下是一些使用 uio_pci_generic 的示例用户空间驱动程序代码

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
    int uiofd;
    int configfd;
    int err;
    int i;
    unsigned icount;
    unsigned char command_high;

    uiofd = open("/dev/uio0", O_RDONLY);
    if (uiofd < 0) {
        perror("uio open:");
        return errno;
    }
    configfd = open("/sys/class/uio/uio0/device/config", O_RDWR);
    if (configfd < 0) {
        perror("config open:");
        return errno;
    }

    /* Read and cache command value */
    err = pread(configfd, &command_high, 1, 5);
    if (err != 1) {
        perror("command config read:");
        return errno;
    }
    command_high &= ~0x4;

    for(i = 0;; ++i) {
        /* Print out a message, for debugging. */
        if (i == 0)
            fprintf(stderr, "Started uio test driver.\n");
        else
            fprintf(stderr, "Interrupts: %d\n", icount);

        /****************************************/
        /* Here we got an interrupt from the
           device. Do something to it. */
        /****************************************/

        /* Re-enable interrupts. */
        err = pwrite(configfd, &command_high, 1, 5);
        if (err != 1) {
            perror("config write:");
            break;
        }

        /* Wait for next interrupt. */
        err = read(uiofd, &icount, 4);
        if (err != 4) {
            perror("uio read:");
            break;
        }

    }
    return errno;
}

通用 Hyper-V UIO 驱动程序

通用驱动程序是一个名为 uio_hv_generic 的内核模块。它支持 Hyper-V VMBus 上的设备,类似于 PCI 总线上的 uio_pci_generic。

使驱动程序识别设备

由于该驱动程序未声明任何设备 GUID,因此它不会自动加载,也不会自动绑定到任何设备,您必须自己加载它并将 ID 分配给驱动程序。例如,要使用网络设备类 GUID

modprobe uio_hv_generic
echo "f8615163-df3e-46c5-913f-f2d2f965ed0e" > /sys/bus/vmbus/drivers/uio_hv_generic/new_id

如果设备已经有一个特定于硬件的内核驱动程序,则通用驱动程序仍然不会绑定到它,在这种情况下,如果您想将通用驱动程序用于用户空间库,您必须手动解除绑定特定于硬件的驱动程序并绑定通用驱动程序,使用设备特定的 GUID,如下所示

echo -n ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/hv_netvsc/unbind
echo -n ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/uio_hv_generic/bind

您可以通过在 sysfs 中查找设备来验证该设备是否已绑定到驱动程序,例如以下所示

ls -l /sys/bus/vmbus/devices/ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver

如果成功,应该打印

.../ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver -> ../../../bus/vmbus/drivers/uio_hv_generic

关于 uio_hv_generic 的须知

在每次中断时,uio_hv_generic 设置中断禁用位。这会阻止设备生成进一步的中断,直到清除该位。用户空间驱动程序应该在阻塞和等待更多中断之前清除该位。

当主机撤销设备时,中断文件描述符将被标记为关闭,并且对中断文件描述符的任何读取都将返回 -EIO。类似于关闭的套接字或断开连接的串行设备。

vmbus 设备区域被映射到 uio 设备资源中
  1. 通道环形缓冲区:访客到主机和主机到访客

  2. 访客到主机中断信号页面

  3. 访客到主机监视页面

  4. 网络接收缓冲区区域

  5. 网络发送缓冲区区域

如果通过向主机请求创建了一个子通道,则 uio_hv_generic 设备驱动程序将为每个通道的环形缓冲区创建一个 sysfs 二进制文件。例如

/sys/bus/vmbus/devices/3811fe4d-0fa0-4b62-981a-74fc1084c757/channels/21/ring

更多信息