2

这个问题是关于低级异步 I/O 系统调用,如 send + epoll/aio_read 等。我问的是网络 I/O 和磁盘 I/O。

实现这些异步调用的简单方法是为每个异步 I/O 请求创建一个线程,然后以同步方式执行请求。显然,这种幼稚的解决方案无法应对大量并行请求。即使使用了线程池,我们仍然需要为每个并行请求设置一个线程。

因此,我推测这是通过以下更有效的方式完成的:

对于写入/发送数据:

  • 将发送请求附加到一些内核内部的异步 I/O 队列。

  • 专用的“写入线程”以这样一种方式接收这些发送请求,从而充分利用目标硬件。为此,可能会使用特殊的 I/O 调度程序。

  • 根据目标硬件,写入请求最终会被分派,例如通过直接内存访问 (DMA)。

对于读取/接收数据:

  • 硬件引发一个 I/O 中断,该中断跳转到内核的 I/O 中断处理程序中。

  • 中断处理程序将通知附加到读取队列并快速返回。

  • 专用的“读取线程”接收读取队列的通知并执行两个任务:1) 如有必要,将读取的数据复制到目标缓冲区。2.) 必要时以某种方式通知目标进程(egepoll、signals、..)。

对于所有这一切,没有必要拥有比 CPU 内核数量更多的写入线程或读取线程。因此,并行请求可伸缩性问题将得到解决。

这是如何在真实的操作系统内核中实现的?这些猜测中哪些是正确的?

4

1 回答 1

3

那些“异步”I/O 的东西是 KERNEL 和 Driver 服务的另一个错觉。我将以wifi驱动程序为例。(这是网络)。

  1. 接收

1)如果数据包进来,wifi H/W 将产生中断并将 dot11 帧或 dot3 帧 DMA 到 DRAM(这取决于 wifi H/W。如今,大多数现代 wifi hw 将转换 HW 中的数据包 - 实际上是 FW在硬件上)。

2) Wifi Driver(在 KERNEL 中运行)应该处理多个 wifi 相关的事情,但最有可能的是,它会形成套接字缓冲区(skb),然后将 skbs 发送到 Linux KERNEL。通常,它发生在 NET_RX_SOFTIRQ 中,或者您可以创建自己的线程。

3) 数据包进入 Linux 堆栈。您可以将其发送到用户空间。它发生在“__netif_receive_skb_core”中,如果数据包是“IP”数据包,第一个 rx_handler 将是“ip_rcv()”。

4) ip 数据包向上移动到传输层处理程序,即 udp_rcv() / tcp_rcv()。要将数据包发送到传输层,您必须经过套接字层,最终您将在特定套接字上形成数据包链表(可以说是Q)。

5)据我了解,这个“Q”是向用户空间提供数据包的队列。您可以在此处执行“异步”或“同步”I/O。

  1. 德克萨斯州

1) 数据包通过 KERNEL 的传输层和 IP 层,最终,您的 netdev TX 处理程序被调用(hard_start_xmit 或 ndo_xmit_start)。基本上,如果您的 netdev(例如 eth0 或 wifi0)是以太网设备,它连接到您的以太网驱动程序“TX”功能或 wifi 驱动程序“TX”功能。这是回调,通常在驱动程序启动时设置。

2)在这个阶段,您的数据包已经转换为“skb”

3)在回调中,它将准备所有的头和描述符并进行DMA。

4) 一旦 TX 在 HW 上打开,HW 将产生中断,您需要释放数据包。

在这里,我的观点是,您的网络 I/O 已经在 DMA 和驱动程序级别作为“异步”工作。大多数现代驱动程序可能对此有单独的上下文。对于 TX,它将使用线程、tasklet 或 NET_TX_SOFTIRQ。对于 RX,如果我们使用“NAPI”,它将使用 NET_RX_SOFTIRQ。或者它也可以使用线程和tasklet。

所有这些都是基于“中断”或其他一些触发器独立发生的。

“同步I/O”主要是在上层应用层模拟的。所以,如果你在内核中重写你的套接字层,你可以做任何你想做的事情,因为下层已经按照你的意愿工作了。

于 2019-12-25T18:27:02.627 回答