【问题标题】:IO request queueingIO 请求排队
【发布时间】:2014-10-18 05:06:42
【问题描述】:

这是关于等待 IO 的调用,允许上下文切换,或非阻塞调用模型:

  1. 它们在操作系统中究竟是如何实现的?
  2. 底层设备如何在指令级工作 - 例如。存储和网络硬件的确切 CPU 指令?
  3. 一个设备一次可以挂起多少条 IO 指令?
  4. 操作系统可以排队多少个 IO 请求,例如同时使用 Linux 和 Windows?在哪里可以找到其他操作系统的此信息?

编辑:这是一个广泛而具体的问题。它关于 IO 设备和 CPU,操作系统用来完成工作 - 所以请回答你能回答的问题。它也与内存有关,因为每个 IO 操作都涉及数据的内存(如果不是寻址)。 x86 和/或广泛分类的答案就可以了。对于设备,您可以选择 x86 上的网络/存储。

【问题讨论】:

  • 这个问题太宽泛了。详细信息在设备类型、供应商、确切型号、操作系统类型、使用的总线等方面存在很大差异。请尽量缩小范围。
  • 这些实现因操作系统而异。对于 Linux,您可以查看内核源代码;对于 Windows,您根本不会得到任何可靠的信息。
  • 现代 CPU 上没有专门用于访问存储硬件或网络硬件的 CPU 指令。一些 CPU 可能有一些可用于访问设备的 I/O 指令,但许多设备可以在不使用 I/O 特定指令的情况下访问。 CPU 对挂起的 I/O 操作没有限制。操作系统通常对挂起的 I/O 操作没有固定限制。
  • 马丁:谢谢。你知道 Linux 内核源代码的任何简短文档吗?
  • Ross:对于块设备和字符设备,所有内容都是逐个字符进出吗?考虑到每条 CPU 指令本身都是阻塞的,进程等待 IO 意味着什么 - 是多路复用吗?

标签: assembly io operating-system


【解决方案1】:
  1. 一般来说,阻塞 I/O 调用会执行以下操作:1) 将进程/线程放在等待 I/O 完成的进程/线程列表中,2) 将其标记为不可运行,以及 3)调用上下文切换。由于进行阻塞调用的线程现在被标记为不可运行,操作系统的调度程序永远不会调度它在 CPU 上运行,直到 I/O 操作完成,并且线程被标记再次可运行。这就是“阻塞”的真正含义。

    Linux 有一个用于非阻塞 I/O 调用的接口,其工作原理大致如下:您的程序首先进行系统调用以创建“异步 I/O 上下文”。这是一个对象,其中包含一个 I/O 操作完成时生成的通知事件队列。内核传回一个 AIO 上下文标识符,您的程序在进行后续调用时会使用该标识符。然后,当您想要执行一些 I/O 时,您进行另一个系统调用以“提交”异步 I/O 操作以供执行。您可以提交任意数量的这些内容。当它们完成时,通知将累积在您的 AIO 上下文的事件队列中。稍后,您可以随时进行另一个系统调用以从该队列中检索通知。

    (有趣的是,虽然 Linux 已经支持这些系统调用有一段时间了,但 Glibc 仍然没有使用它们!Glibc 通过内部产生工作线程并在工作线程中进行阻塞 I/O 操作来实现非阻塞 I/O !)

  2. 基本上有3种情况。在 x86 上,有 INOUT 指令用于在硬件设备之间传输 1/2/4 字节。 x86 为这些指令访问的“I/O 端口”提供了一个特殊的 16 位地址空间。 (我不知道任何其他架构是否也有特殊的 I/O 地址空间。)即使在 x86 上,并非所有硬件设备都映射到该 I/O 地址空间。有些使用常规内存地址空间。在这种情况下,您可以使用普通的MOV 指令或任何其他访问内存的指令向/从它们传输数据。第三种情况是使用 DMA(直接内存访问)时。基本上,您使用前面提到的两种方法之一向硬件设备发送某种命令,告诉它:“从地址 Y 开始将 X 字节直接传输到 RAM”。然后设备直接通过系统总线发送请求的数据并将其存储在 RAM 中,而无需 CPU 的任何干预。完成后,一般会引发中断让你知道。

  3. 这完全取决于设备。没有一般的答案。

  4. 嗯,对于 Linux,这是我能给你的最接近答案的事情。 (不要问我其他操作系统。)在 Linux 中,每个存储设备都有自己的未完成 I/O 请求队列。这些队列中的每一个都有一个名为nr_requests 的变量,这是它将容纳的未完成I/O 请求的最大数量。您可以通过打印以下伪文件来找到硬盘驱动器的值:/sys/block/<DEVICE>/queue/nr_requests。默认好像是128,从看源码看来128也是最大值(?)。

后记:如果您正在考虑编写一些超高性能 I/O 密集型服务器应用程序之类的东西,请在您感兴趣的所有操作系统和硬件平台上进行 BENCHMARK!您可以编写几个简单的测试程序,它们产生一堆线程(使数量可配置)并以每秒(可配置的)速率启动(可配置的)数量的 I/O 操作。观察它们全部完成需要多长时间,在测试运行时服务器如何响应,以及有多少操作出错。

【讨论】:

    【解决方案2】:

    1。它们究竟是如何在操作系统中实现的?

    提出请求

    让我们看看当您在设备上调用写入时会发生什么。

    1. 您要求运行时执行文件写入。
    2. 运行时最终调用系统调用。
    3. 内核定位与您写入的设备关联的驱动程序/模块。
    4. 驱动程序/模块执行其操作并可能将请求转发给其他驱动程序/模块。
    5. 最后一个驱动程序/模块专门处理硬件设备,记录请求并发送写入命令。

    让任务进入休眠状态 最后一个驱动程序/模块返回一个状态码,以通知其调用者发生了什么。为简单起见,假设返回状态仅为:SuccessFailsPending
    通过返回 Pending 驱动程序/模块通知请求正在进行中。
    控制从 5. 返回到 2.,内核被赋予写入的返回状态。
    如果是 SuccessError 则返回用户空间指示成功或失败。如果它是 Pending 但它会将程序标记为 Waiting for IO(通过使用任何操作系统特定的方法)。
    这是关键,一个等待它的任务没有被安排,即使它也没有什么可做的。
    从 1. 到 5. 内核可以(在大多数操作系统中并且仅当驱动程序/模块或内核本身允许时)被抢占,即中断,但调用任务的状态将是 Running em> 所以稍后会重新安排,但是没有安排等待任务。

    等待任务 硬件完成后,它会通过中断请求 CPU 关注。 5. 中的模块/驱动程序设置回调,以便在其管理的硬件调用 CPU 注意力时执行其代码。
    在最终完成硬件操作后,模块/驱动程序会通知内核一个特定的请求(之前记录的)已经完成。
    内核知道哪个任务发出了请求并将其状态更改为正在运行,从而唤醒它。

    2。底层设备如何在指令级别上工作 - 例如存储和网络硬件的确切 CPU 指令?

    要处理网络设备,您主要使用移动指令。
    要处理存储设备,您主要使用移动指令。
    要处理 [在此处插入设备类型] 设备,您主要使用移动指令。

    在计算机中,一切都只是数据的移动。在乘法 ALU 中移动 09h 和 41h 得到 249h,在显存中移动 09h 和 41h 得到彩色 A。您在计时器寄存器中移动 09h 和 41h,您会听到哔声。

    系统可能非常不同,但原理是相同的。您将数据移入和移出由地址标识的位置。在大多数系统中,这与写入 RAM 相同,但是每个系统都可能具有特殊性,例如使用不同指令访问的不同类型的地址集(这些通常称为地址空间,例如:内存地址空间、IO 地址空间, 总线地址空间, PCI 配置地址空间, NUMA 地址空间, ...)。

    x86 CPU 本身知道两个地址空间:内存地址空间(现在最多 48 位)和 IO 地址空间(16 位)。
    第一个使用任何内存指令访问(例如,如果您正在访问全局变量),第二个使用具有以inout 开头的助记符的特定指令。
    但请注意,IO 地址空间是 IA32 早期的遗迹空间,现在的新设备不使用它。

    3。一个设备一次可以挂起多少条 IO 指令?

    4。操作系统可以排队多少个 IO 请求,例如同时使用 Linux 和 Windows?在哪里查找其他操作系统的此信息?

    如果您的意思是主动挂起,即由硬件同时执行,那么这通常是一个或多一点。
    大多数控制器(如 AHCI、XHCI、EHCI 等)处理的数据结构允许设备将多个请求排入队列,但它们一次只处理一个。通常没有限制(但描述请求的可用内存),但这是特定于硬件的。
    其他控制器,如 SCSI 控制器,一次可以处理多个命令。

    操作系统也处理请求的软件队列,因此同样可能是无限的,并且仅受可用内存的限制。 但是通常限制是一个合理的数字。
    Alex D. answer 对此有更多详细信息。

    对于 Windows,DDK 帮助引用

    除了文件系统驱动程序,I/O 管理器将设备队列对象(用于排队 IRP)与驱动程序创建的每个设备对象相关联。

    但是它没有指定是否有限制,并且在编程接口中我发现没有 API 来指定请求数量的限制。
    因此,最终由驱动程序编码器决定一个上限。
    我不知道 Windows 是否有每个 sé 的限制(我想它没有)。

    【讨论】:

    • 感谢您的回答。它确实消除了一些疑问。如果您能准确地解释当不同应用程序使用套接字在同一个网卡上发出多个 send() 时从上到下会发生什么,那就太好了。还请包括在 send() 完成后唤醒进程的底层通知的详细信息。 x86 可以。
    • @samofoz 抱歉迟到了。查看here 了解有关 TCP/IP 堆栈如何在 linux 上工作的详细信息。对于 NIC(网卡)类型的中断的具体情况,您可以查看this datasheet 的第 13 页。如您所见,当数据包成功传输或接收时,可以通知内核。这两个文件足以获得完整的图片。如果您需要对特定主题的解释,只需发布​​一个问题;)
    • @samofoz 图 9 中的操作 2 并不是针对每个数据包执行的,它仅在 RX 缓冲区已填满或第一次执行时执行。所以一般的顺序是:第一次创建 RX 缓冲区,告诉 NIC 缓冲区在哪里等等,CPU 被 NIC 中断每个打包接收,当缓冲区满时,重置它并告诉 NIC
    • @samofoz 数据表通常都有足够的文档记录,示例程序会出现问题,因为它依赖于操作系统。无论如何,RealTek 的数据表已经被剥离,我找不到完整的,最近该公司开始要求 NDA 签名。一个非常完整的是来自英特尔的one
    • @samofoz 我只能找到 Linux 源代码,here 包含模块实现的所有操作的 C 结构。如果没有考虑到数据表,要了解正在发生的事情并不容易,但它是我能找到的唯一 “程序”
    猜你喜欢
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多