如何在内核中高效实现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数据:
将发送请求附加到某个内核内部异步 I/O 队列。
专用 "write-threads" 正在以充分利用目标硬件的方式接收这些发送请求。为此,可能会使用特殊的 I/O 调度程序。
根据目标硬件,写入请求最终会被分派,例如通过直接内存访问 (DMA)。
对于reading/receiving数据:
硬件引发一个 I/O 中断,该中断跳转到内核的 I/O 中断处理程序。
中断处理程序将通知附加到读取队列并 returns 迅速。
Dedicated "read-threads" 获取读取队列的通知并执行两个任务:1) 如有必要,将读取数据复制到目标缓冲区。 2.) 如有必要,以某种方式通知目标进程(e.g.epoll、信号、..)。
对于所有这些,写入线程或读取线程的数量无需多于 CPU 核心的数量。因此,并行请求可伸缩性问题将得到解决。
这是如何在真正的 OS 内核中实现的?以下哪些猜测是正确的?
那些 "asynchronous" I/O 东西是 KERNEL 和 Driver 服务的另一个幻觉。我以wifi为例driver。 (这是网络)。
- 接收
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。
- 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 内核中的套接字层,您可以做任何您想做的事情,因为下层已经按照您的意愿工作了。
这个问题是关于低级异步 I/O 系统调用的,比如 send + epoll/aio_read 等等。 我问的是网络 I/O 和磁盘 I/O.
实现这些异步调用的简单方法是为每个异步 I/O 请求创建一个线程,然后以同步方式执行请求。 显然,这种幼稚的解决方案很难适应大量并行请求。 即使使用线程池,我们仍然需要一个线程来处理每个并行请求。
因此,我推测这是通过以下更有效的方式完成的:
对于writing/sending数据:
将发送请求附加到某个内核内部异步 I/O 队列。
专用 "write-threads" 正在以充分利用目标硬件的方式接收这些发送请求。为此,可能会使用特殊的 I/O 调度程序。
根据目标硬件,写入请求最终会被分派,例如通过直接内存访问 (DMA)。
对于reading/receiving数据:
硬件引发一个 I/O 中断,该中断跳转到内核的 I/O 中断处理程序。
中断处理程序将通知附加到读取队列并 returns 迅速。
Dedicated "read-threads" 获取读取队列的通知并执行两个任务:1) 如有必要,将读取数据复制到目标缓冲区。 2.) 如有必要,以某种方式通知目标进程(e.g.epoll、信号、..)。
对于所有这些,写入线程或读取线程的数量无需多于 CPU 核心的数量。因此,并行请求可伸缩性问题将得到解决。
这是如何在真正的 OS 内核中实现的?以下哪些猜测是正确的?
那些 "asynchronous" I/O 东西是 KERNEL 和 Driver 服务的另一个幻觉。我以wifi为例driver。 (这是网络)。
- 接收
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。
- 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 内核中的套接字层,您可以做任何您想做的事情,因为下层已经按照您的意愿工作了。