如何在内核中高效实现AsyncI/O

How to implement Async I/O efficiently in the kernel

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

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

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

对于writing/sending数据:

对于reading/receiving数据:

对于所有这些,写入线程或读取线程的数量无需多于 CPU 核心的数量。因此,并行请求可伸缩性问题将得到解决。

这是如何在真正的 OS 内核中实现的?以下哪些猜测是正确的?

那些 "asynchronous" I/O 东西是 KERNEL 和 Driver 服务的另一个幻觉。我以wifi为例driver。 (这是网络)。

  1. 接收

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

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

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

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

5) 据我了解,这个"Q"是queue给用户space提供数据包。您可以在此处执行 "async" 或 "sync" I/O。

  1. TX

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

2) 在此阶段,您的数据包已经转换为 "skb"

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

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

在这里,我的意思是,您的网络 I/O 已经在 DMA 和 Driver 级别上作为 "Asynchronous" 工作。大多数现代 driver 可能对此都有单独的上下文。对于 TX,它将使用线程、tasklet 或 NET_TX_SOFTIRQ。对于 RX,如果我们使用 "NAPI",它将使用 NET_RX_SOFTIRQ。或者它也可以使用thread和tasklet。

所有这些都是基于 "interrupt" 或其他一些触发器独立发生的。

"Synchronous I/O"多在上层应用层模拟。因此,如果您 re-write 内核中的套接字层,您可以做任何您想做的事情,因为下层已经按照您的意愿工作了。