图解Linux网络包接收过程( 九 )


当用户执行完recvfrom调用后,用户进程就通过系统调用进行到内核态工作了 。如果接收队列没有数据,进程就进入睡眠状态被操作系统挂起 。这块相对比较简单,剩下大部分的戏份都是由Linux内核其它模块来表演了 。
首先在开始收包之前,Linux要做许多的准备工作:

  • 1. 创建ksoftirqd线程,为它设置好它自己的线程函数,后面指望着它来处理软中断呢
  • 2. 协议栈注册,linux要实现许多协议,比如arp,icmp,ip,udp,tcp,每一个协议都会将自己的处理函数注册一下,方便包来了迅速找到对应的处理函数
  • 3. 网卡驱动初始化,每个驱动都有一个初始化函数,内核会让驱动也初始化一下 。在这个初始化过程中,把自己的DMA准备好,把NAPI的poll函数地址告诉内核
  • 4. 启动网卡,分配RX,TX队列,注册中断对应的处理函数
以上是内核准备收包之前的重要工作,当上面都ready之后,就可以打开硬中断,等待数据包的到来了 。
当数据到来了以后,第一个迎接它的是网卡(我去,这不是废话么):
  • 1. 网卡将数据帧DMA到内存的RingBuffer中,然后向CPU发起中断通知
  • 2. CPU响应中断请求,调用网卡启动时注册的中断处理函数
  • 3. 中断处理函数几乎没干啥,就发起了软中断请求
  • 4. 内核线程ksoftirqd线程发现有软中断请求到来,先关闭硬中断
  • 5. ksoftirqd线程开始调用驱动的poll函数收包
  • 6. poll函数将收到的包送到协议栈注册的ip_rcv函数中
  • 7. ip_rcv函数再将包送到udp_rcv函数中(对于tcp包就送到tcp_rcv)
现在我们可以回到开篇的问题了,我们在用户层看到的简单一行recvfrom,Linux内核要替我们做如此之多的工作,才能让我们顺利收到数据 。这还是简简单单的UDP,如果是TCP,内核要做的工作更多,不由得感叹内核的开发者们真的是用心良苦 。
理解了整个收包过程以后,我们就能明确知道Linux收一个包的CPU开销了 。首先第一块是用户进程调用系统调用陷入内核态的开销 。第二块是CPU响应包的硬中断的CPU开销 。第三块是ksoftirqd内核线程的软中断上下文花费的 。后面我们再专门发一篇文章实际观察一下这些开销 。
另外网络收发中有很多末支细节咱们并没有展开了说,比如说no NAPI,GRO,RPS等 。因为我觉得说得太对了反而会影响大家对整个流程的把握,所以尽量只保留主框架了,少即是多!
 
来自公众号:开发内功修炼




推荐阅读