内核收包路径
- 系统初始化化时,网卡驱动程序向系统内核在内存中申请一块Ring Buffer,用于存储从外部网络接受到的数据包;
- 网卡驱动程序将申请到的Ring Buffer地址告知网卡;
- 当数据帧从外部网络到达网卡时,网卡把帧通过DMA拷贝存放到Ring Buffer中;
- 网卡产生硬中断(通过给CPU物理引脚施加电压变化实现),告知CPU有数据帧到达;
- 当CPU接收到中断请求后,调用网络设备驱动注册的中断处理函数;
- 中断处理函数发出软中断请求(给内存中的一个变量赋予二进制值以标识有软中断发生),然后释放CPU资源;
- ksoftirqd检测到有软中断请求到达,调用网卡驱动注册的poll函数开始轮询收包,并且封装为sk_buff,然后递交给网络协议栈进行处理;
- 协议栈处理完成后数据就进入用户态的对应进程,进程就可以操作数据了。
DMA
- 系统启动时将网卡初始化,在内存中分配空间给Ring Buffer;
- 初始状态下,Ring Buffer中存放的每个元素Packet Descriptor指向一个DMA Buffer(用于存储从外部网络接受到的数据包),状态均为ready;
- 当网卡接收到数据时,DMA负责在Ring Buffer上按顺序找到下一个状态为ready的Packet Descriptor,将数据存入该Packet Descriptor指向的DMA Buffer中;
- 修改Packet Descriptor,标记被写入数据的DMA Buffer为used状态;
- 当DMA处理完数据之后,网卡会触发一个硬中断让CPU去处理接收到的数据。
因此当Ring Buffer满的时候,新接收到的数据包将被丢弃。
中断处理
硬中断在整个操作系统中拥有最高优先级,当一个硬中断到来后,CPU必须马上停止当前正在执行的程序,转而执行中断处理程序。如果该中断处理程序是一段耗时长的逻辑,就会导致CPU无法释放。为此引入了软中断:网卡发出硬中断信号,CPU收到后调用对应的中断处理程序,该中断处理程序仅简单的发起软中断请求,而软中断对应的处理函数就可以让CPU按照自己的调度策略去执行。
ksoftirqd负责处理软中断,其不断的循环检测中断标记,当检测到软中断时调用驱动注册的poll函数。poll函数会遍历Ring Buffer中的Packet Descriptor,查看哪些Packet Descriptor指示有新数据包。如果发现有新接收到的数据包,就在内核空间内存分配一个新的sk_buff,将DMA Buffer中的数据拷贝(或映射,传统驱动大多采用复制方式,XDP、DPDK或者某些支持零拷贝的驱动会采用映射方式)到该sk_buff的数据区域,随后将sk_buff传递给协议栈进行进一步的处理。
内核发包路径
当协议栈准备好数据包时,驱动的发送函数被调用,驱动检查Ring Buffer(根据发送与接收分为RX Ring和TX Ring),并将sk_buff中的数据映射到DMA Buffer中、更新Packet Descriptor,随后通过写寄存器或触发中断以告知网卡有数据包要发送,网卡开始从Packet Descriptor指向的内存读取数据并发送出去。
网卡发送完数据后,会通过硬中断通知驱动程序(同样是先硬中断,随后是软中断)回收Ring Buffer中的Packet Descriptor、解除DMA映射、释放sk_buff资源。