在开始今天的系列文章之前,我先来请大家思考几个小问题。
- 问1:我们在查看内核发送数据消耗的 CPU 时,是应该看 sy 还是 si ?
- 问2:为什么你服务器上的 /proc/softirqs 里 NET_RX 要比 NET_TX 大的多的多?
- 问3:发送网络数据的时候都涉及到哪些内存拷贝操作?
这些问题虽然在线上经常看到,但我们似乎很少去深究。如果真的能透彻地把这些问题理解到位,我们对性能的掌控能力将会变得更强。
带着这三个问题,我们开始今天对 Linux 内核网络发送过程的深度剖析。还是按照我们之前的传统,先从一段简单的代码作为切入。如下代码是一个典型服务器程序的典型的缩微代码:
代码语言:javascript复制int main(){
fd = socket(AF_INET, SOCK_STREAM, 0);
bind(fd, ...);
listen(fd, ...);
cfd = accept(fd, ...);
// 接收用户请求
read(cfd, ...);
// 用户请求处理
dosometing();
// 给用户返回结果
send(cfd, buf, sizeof(buf), 0);
}
今天我们来讨论上述代码中,调用 send 之后内核是怎么样把数据包发送出去的。本文基于Linux 3.10,网卡驱动采用Intel的igb网卡举例。
一、Linux 网络发送过程总览
我觉得看 Linux 源码最重要的是得有整体上的把握,而不是一开始就陷入各种细节。
我这里先给大家准备了一个总的流程图,简单阐述下 send 发送了的数据是如何一步一步被发送到网卡的。
在这幅图中,我们看到用户数据被拷贝到内核态,然后经过协议栈处理后进入到了 RingBuffer 中。随后网卡驱动真正将数据发送了出去。当发送完成的时候,是通过硬中断来通知 CPU,然后清理 RingBuffer。
因为文章后面要进入源码,所以我们再从源码的角度给出一个流程图。
虽然数据这时已经发送完毕,但是其实还有一件重要的事情没有做,那就是释放缓存队列等内存。
那内核是如何知道什么时候才能释放内存的呢,当然是等网络发送完毕之后。网卡在发送完毕的时候,会给 CPU 发送一个硬中断来通知 CPU。更完整的流程看图:
注意,我们今天的主题虽然是发送数据,但是硬中断最终触发的软中断却是 NET_RX_SOFTIRQ,而并不是 NET_TX_SOFTIRQ !!!(T 是 transmit 的缩写,R 表示 receive)