系统负责处理与物理网络接口相关的操作,包括数据包的封装和发送,以及从物理接口接收数据包并进行解析
网络设备子系统不但处理数据包的格式转换,如在以太网中添加帧头和帧尾,以及从帧中提取数据
还负责处理硬件相关的操作,如发送和接收数据包的时钟同步、物理层错误检测等
接着网络设备子系统会选择一个合适的网卡发送队列并把 skb 添加到队列中(绕过软中断处理程序)
然后,内核会调用网卡驱动的入口函数 dev_hard_start_xmit
来触发数据包的发送
在一些情况下,邻居子系统还会将 skb 数据包添加到软中断队列(softnet_data)上,并触发软中断(NET_TX_SOFTIRQ)
这个过程是为了将 skb 数据包交给软中断处理程序进行进一步处理和发送。软中断处理程序会负责实际的数据包发送
这就是为什么一般服务器上查看 /proc/softirqs
,一般 NET_RX 都要比 NET_TX 大的多的原因之一
即对于收包来说,都是要经过 NET_RX 软中断;而对于发包来说,只有某些情况下才触发 NET_TX 软中断
网卡驱动发送
驱动程序从发送队列中读取 skb 的描述信息,将其挂到 RingBuffer 上(前面提到的igb_tx_buffer
数组)
接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中(前面提到的e1000_adv_tx_desc
数组)
网卡会直接从 e1000_adv_tx_desc
数组中根据描述信息读取实际数据并将数据发送到网络。这样就完成了数据包的发送过程
收尾工作
当数据发送完成后,网卡设备会触发一个硬件中断(NET_RX_SOFTIRQ),这个硬中断通常称为“发送完成中断”或者“发送队列清理中断”
这个硬中断的主要作用是执行发送完成的清理工作,包括释放之前为数据包分配的内存,即释放 skb 内存和 RingBuffer 内存
最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 skb(前面有讲到发送的其实是 skb 的拷贝版)
可以看到,当数据发送完成以后,通过硬中断的方式来通知驱动发送完毕,而这个中断类型是 NET_RX_SOFTIRQ
前面我们讲到过网卡收到一个网络包的时候,会触发 NET_RX_SOFTIRQ
中断去告诉 CPU 有数据要处理
也就是说,无论是网卡接收一个网络包还是发送网络包结束之后,触发的都是 NET_RX_SOFTIRQ
总结
最后总结一下在 Linux 系统中发送网络数据包的流程:
- 应用程序通过 socket 提供的接口进行系统调用,将数据从用户态拷贝到内核态的 socket 缓冲区中
- 网络协议栈从 socket 缓冲区中拿取数据,并按照 TCP/IP 协议栈从上到下逐层处理
- 传输层处理:以 TCP 为例,在传输层中会复制一份数据(为了丢失重传),然后为数据封装 TCP 头
- 网络层处理:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片等操作
- 邻居子系统和网络设备子系统处理:在这里数据会被进一步处理和封装,然后被添加到网卡的发送队列中
- 驱动程序从发送队列中读取 skb 的描述信息然后挂在 RingBuffer 上,接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中
- 网卡将数据发送到网络
- 当数据发送完成后触发硬中断,释放 skb 内存和 RingBuffer 内存