CPU包转发流程
从中断到路由系统
PKT Arrive INT表示报文到达CPU的中断产生了,某设备驱动的中断服务例程ISR于是处理这个中断
这副图告诉我们Linux下设备是如何处理接收报文的,其步骤如下:
1. 当中断到来后ISR响应,判断是否报文接收中断,如果是那么必定完成如下工作:
skb = dev_alloc_skb(…); skbàprotocol = eth_type_trans(skb, dev);
2. 触发软中断,ISR返回。
a) 如果设备采用backlog_dev设备作为自己的接收后台(如X driver),那么通过netif_rx函数触发软中断,它内部调用netif_rx_scheduleà__netif_rx_schedule
b) 如果设备没有采用backlog_dev设备(如Y driver),那么开发者必须调用__netif_rx_schedule触发软中断
3. 软中断会调用设备定义poll函数,此时也有2种路径:
a) 如果设备采用backlog_dev设备作为自己的接收后台(如X driver),那么poll就是process_backlog函数,它内部调用netif_receive_skb函数
b) 如果设备没有采用backlog_dev设备(如Y driver),那么开发者必须实现相应的poll函数,其中必须调用netif_receive_skb函数
4. netif_receive_skb会调用deliver_skb将报文传给正确的协议处理函数
分析netif_rx函数。为了CPU的效率,上层的处理函数的将采用软中断的方式激活,netif_rx的一个重要工作就是将传入的sk_buff放到等候队列中,并置软中断标志位,然后便可放心返回,等待下一次网络数据包的到来 过了一段时间后,一次CPU调度会由于某些原因会发生(如某进程的时间片用完)。在进程调度函数即schedule()中,会检查有没有软中断发生,若有则运行相应的处理函数 1. /** 2. * netif_rx - 传递报文数据到网络处理代码 3. * 此函数从设备驱动程序中收到一个报文,然后把它放入上层协议的队列中等待处理,它几乎总是成功的。 4. * 该报文可能由于拥塞控制而被丢弃,也可能被上层协议丢弃 5. * 6. * return values: 7. * NET_RX_SUCCESS (没有拥塞) 8. * NET_RX_CN_LOW (低拥塞) 9. * NET_RX_CN_MOD (中等程度的拥塞) 10. * NET_RX_CN_HIGH (严重拥塞) 11. * NET_RX_DROP (报文被丢弃) 12. */ 13. int netif_rx(struct sk_buff *skb) 14. { 15. int this_cpu; 16. struct softnet_data *queue; 17. unsigned long flags; 18. 19. ...... 20. 21. if (!skb->stamp.tv_sec) 22. net_timestamp(&skb->stamp); 23. 24. /* 25. * The code is rearranged so that the path is the most 26. * short when CPU is congested, but is still operating. 27. */ 28. local_irq_save(flags); 29. this_cpu = smp_processor_id(); 30. queue = &__get_cpu_var(softnet_data); 31. 32. __get_cpu_var(netdev_rx_stat).total++; 33. if (queue->input_pkt_queue.qlen <= netdev_max_backlog) 34. {
这个队列中的skb还不超长,所以可以进入此判断。下面几行代码的含义要分2种情况: 第一种:网口收到第一个报文时,先把接收软中断打开,然后把报文放在队列中,返回。当软中断可以执行时就去处理这些报文第二种:当网口收到报文后发现队列中还有报文,那么先把此次收到的报文挂到队列中,然后就返回,由于队列中还有报文,相关软中断可以在后台继续处理这些报文 35. if (queue->input_pkt_queue.qlen) 36. { 37. enqueue: 38. __skb_queue_tail(&queue->input_pkt_queue, skb); 39. local_irq_restore(flags); 40. return NET_RX_SUCCESS; 41. } 42. 43. netif_rx_schedule(&queue->backlog_dev);//在此函数中发起软中断 44. goto enqueue; 45. } 46. 47. drop: 48. ...... 49. 50. kfree_skb(skb);
|
net_rx_actiion函数调用树
通过packet_ptype_base[16]这个数组解决。这个数组包含了需要接收包的协议,以及它们的接收函数的入口。
ip协议接收数据是通过ip_rcv,arp协议是通过arp_rcv。
如果有协议想把自己添加到这个数组,是通过dev_add_pack实现。Ip层的注册是在ip_init中
netif_receive_skb函数调用树
下面是ip_rcv_finish的代码:
static inline int ip_rcv_finish(struct sk_buff *skb) 2. { 3. struct net_device *dev = skb->dev; 4. struct iphdr *iph = skb->nh.iph; 5. 6. /* 7. * Initialise the virtual path cache for the packet. It describes 8. * how the packet travels inside Linux networking.
|
INET域中有两个地方需要查询输入路由,一个是当收到一个IP数据报,ip_rcv将其交给ip_rcv_finish后,ip_rcv_finish判断skb->dst是否为NULL(因为对于环回接口上收到的数据报,其dst是存在的,不需要查询输入路由),如果为NULL,则需要查询得到输入路由。另一个地方是当收到一个ARP数据报,arp_rcv将其交给arp_process处理时,arp_process也需要查询得到该skb的输入路由。
skb->dst-input在ip_route_output_slow函数中定义。如果是发送到本机的包,往下的操作函数是ip_local_deliver,如果这条路由是用来转发的,对应操作函数是ip_forward,如果查找不到路由的处理情况则是ip_error。
ip_forward_finish最后还是调用了dst_output。我们知道dst_output实际上调用skb->dst->output,它指向了ip_output
ip_output函数其实很简单,只是做了skb->dev = dev; skb->protocol = ETH_P_IP;这么两个赋值操作后就调用ip_finish_output函数。后者几乎没有任何变化地调用ip_finish_output2函数。
ip_finish_output2函数:
1. static inline int ip_finish_output2(struct sk_buff *skb) 2. { 3. struct dst_entry *dst = skb->dst; 4. struct hh_cache *hh = dst->hh; 5. struct net_device *dev = dst->dev; 6. int hh_len = LL_RESERVED_SPACE(dev); 7.
...... 第一次进入到这里的时候,hh等于NULL,所以执行neighbouràoutput函数,它实际指向neigh_resolve_output,是在arp_constructor函数中初始化neighbour的, 8. if (hh) { 9. int hh_alen;
hh_alen等于16,而hhàhh_len等于14 10. hh_alen = HH_DATA_ALIGN(hh->hh_len);
把2层报文头一次性拷贝到skb中 11. memcpy(skb->data - hh_alen, hh->hh_data, hh_alen); 12. 13. skb_push(skb, hh->hh_len);
这里的hh_output它指向了dev_queue_xmit,3.10版本hh_cache结构体中没有output函数指针, 调用dst_neigh_output函数->neigh_hh_output->dev_queue_xmit 14. return hh->hh_output(skb); 15. } 16. else if (dst->neighbour) 17. return dst->neighbour->output(skb); 18. 19. return -EINVAL; 20. }
|
3.10版本hh_cache结构体中没有output函数指针,调用dst_neigh_output函数->neigh_hh_output->dev_queue_xmit->dev_hard_start_xmit发包
结论:
CPU只在收包时触发一次软中断调用rx_action处理函数,后续走ip_forward分支转发,走dst_out->ip_output->dev_queue_xmit->dev_hard_start_xmit发包不产生软中断处理