网络IO时数据在网卡中的数据流动过程
网络IO,数据从网卡到应用进程过程解析
了解较为底层的原理可以培养我们分析问题的能力, 在本文中我会尽可能详细的说明数据在网络io过程中的流动过程(以Linux为例),相信大家耐心看完后一定会有所收获!
当我们要接收数据时
当外部的数据经历了重重险阻终于到达我们局域网的路由器,并通过mac地址经交换机定位到我们Linux机器的网卡
这时Linux会使用DMA技术, 将接收到的数据包写入一块指定的缓冲区域”Ring Buffer”, 这个缓冲区是环形的, 当我们处理数据速度跟不上接收速度, 就会出现丢弃数据的现象. (也就是需要暂存这部分数据, 等待处理后交给上层协议栈)
前面说了, 要是不及时处理数据可能会造成丢包, 所以我们需要以 “中断”的方式迅速通知CPU, 可是要是在高网络IO的情况下, CPU频繁被打断可能会造成其他任务的延迟,造成工作效率的降低 所以我们不能简单粗暴的”中断”CPU
仔细分析我们发现, 中断可以分为两部分,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,叫做软中断(softirq)。
所以Linux在2.6以后采用NAPI机制来避免频繁的中断调用, 可以理解为中断+轮询, 具体流程如下
- 系统启动时 NIC (network interface card) 进行初始化,系统分配内存空间给 Ring Buffer 。
- 初始状态下,Ring Buffer 队列每个槽中存放的 Packet Descriptor 指向 sk_buff ,状态均为 ready。
- DMA 将 NIC 接收的数据包逐个写入 sk_buff ,一个数据包可能占用多个 sk_buff ,sk_buff 读写顺序遵循FIFO(先入先出)原则。
- 被写入数据的 sk_buff 变为 used 状态。
- DMA 读完数据之后,NIC 会通过 NIC Interrupt Handler 触发 IRQ (中断请求)。(这里指的就是硬中断)
- NIC driver 注册 poll 函数。
- poll 函数对数据进行检查,例如将几个 sk_buff 合并,因为可能同一个数据可能被分散放在多个 sk_buff 中。(接下来pull函数就是在软中断所执行的内核线程内容)
- poll 函数将 sk_buff 交付上层网络协议栈处理。
- poll 函数清理 sk_buff,清理 Ring Buffer 上的 Descriptor 将其指向新分配的 sk_buff 并将状态设置为 ready。
一些疑问
Q: ring buffer 是什么?
A: 是内核分配给网卡的内存空间, 是一个大小有限的环形buffer
Q: sk_buff 是什么?
A: sk_buff是内核中用来存储网络包的结构体, 其中包含了指向每层的头部, 数据的指针等信息
当我们要发送数据时
发送数据就要把我们下个传递的数据进行层层包装, 加上所需要的头部, 到达数据链路层的网卡,经过处理后进入网络拓扑.
我们使用socket()函数进行系统调用, 内核会给对应进程分配sk_buff结构, 我们填充自己要发送的数据,然后再经过协议栈层层加上对应信息, 经网卡发送后释放.当然如果使用tcp, 则会对这个结构仅从拷贝, 网卡发送这个网络包的副本,直到收到ack后再释放, 发送的过程相对较为简单.
本文主要做学习总结之用, 如果能帮助到你是我的荣幸, 难免会有错误疏漏之处, 劳烦指出, 让我们共同进步!
参考文档 网卡的 Ring Buffer 详解