03.网络

网络IO

虚拟存储一章中我们讨论了内核空间与用户空间的划分,即针对32Linux操作系统而言,将最高的1G字节(从虚拟地址0xC00000000xFFFFFFFF,供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x000000000xBFFFFFFF,供各个进程使用,称为用户空间。

读取网卡数据

有了用户空间和内核空间,整个Linux内部结构可以分为三部分,从最底层到最上层依次是:硬件–>内核空间–>用户空间。我们都知道,为了OS的安全性等的考虑,进程是无法直接操作IO设备的,其必须通过系统调用请求内核来协助完成IO动作,而内核会为每个IO设备维护一个Buffer

内核包处理(Kernel Packet Processing)

回到最简单的传统实现,网卡接收到一个数据包,并向Linux内核发送一个中断,指出存在应该处理的数据包。内核停止其他工作,将上下文切换到中断处理程序,处理数据包,然后切换回其正在执行的操作。

传统的中断驱动

这种上下文切换很慢,这在90年代对10Mbit NIC来说可能还不错,但是在NIC10G且最大线路速率的现代服务器上,每秒可以带来大约1500万个数据包,而在具有8个核心的小型服务器上 这可能意味着内核每个内核每秒中断数百万次。

多年前,Linux不再不断处理中断,而是添加了NAPI,这是现代驱动程序用来提高高数据包速率性能的网络API。在低速率下,内核仍然按照我们提到的方法接受来自NIC的中断。一旦有足够的数据包到达并超过阈值,它将禁用中断,而是开始轮询NIC并分批提取数据包。该处理在 “softirq” 或软件中断上下文中完成。这发生在系统调用和硬件中断的末尾,这是内核(而不是用户空间)已经在运行的时候。

NAPI

这快得多,但是带来了另一个问题。如果要处理的数据包如此之多,以至于我们花了所有的时间来处理来自NIC的数据包,但又没有时间让用户空间进程实际上耗尽那些队列(从TCP连接等读取,会发生什么?最终,队列将满,我们将开始丢弃数据包。为了使公平起见,内核将在给定softirq上下文中处理的数据包数量限制为一定的预算。超出预算后,它会唤醒一个称为ksoftirqd的单独线程(您将在每个内核的ps中看到其中一个,该线程将在正常的系统调用/中断路径之外处理这些softirq。使用标准流程调度程序调度该线程,该进程已经尝试公平了。

NAPI polling crossing threshold to schedule ksoftirqd

纵观内核处理数据包的方式,我们可以肯定地有机会停止处理。如果两次softirq处理调用之间的时间增加,则在处理数据包之前,数据包可能会在NIC RX队列中停留一段时间。这可能是导致CPU内核死锁的原因,也可能是导致内核无法运行softirqs的缓慢原因。

Links

  • NGTE扩展阅读:关于IO模型、IO多路复用参阅《Concurrent-Notes;关于网络协议参阅《Network-Notes

  • 2017-调整Linux I/O调度器优化系统性能: Linux I/O调度器是Linux内核中的一个组成部分,用户可以通过调整这个调度器来优化系统性能。本文首先介绍Linux I/O调度器的结构,然后介绍如何根据不同的存储器来设置Linux I/O调度器从而达到优化系统性能。