CPU包转发流程

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_rcvarp协议是通过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.

 

9. */

10.

11. if (skb->dst == NULL) {

12. /*路由为空的时候检查路由,因为要确定这个包是怎么处理的,是转发还是本地分发*/

13. if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))

14. goto drop;

15. }

16.

17. if (iph->ihl > 5) {

18. struct ip_options *opt;

19.

20. /* It looks as overkill, because not all

21. IP options require packet mangling.

22. But it is the easiest for now, especially taking

23. into account that combination of IP options

24. and running sniffer is extremely rare condition.

25. --ANK (980813)

26. */

27.

28. if (skb_cow(skb, skb_headroom(skb))) {

29. IP_INC_STATS_BH(IpInDiscards);

30. goto drop;

31. }

32. iph = skb->nh.iph;

33.

34. if (ip_options_compile(NULL, skb))

35. goto inhdr_error;

36.

37. opt = &(IPCB(skb)->opt);

38. if (opt->srr) {

39. struct in_device *in_dev = in_dev_get(dev);

40. if (in_dev) {

41. if (!IN_DEV_SOURCE_ROUTE(in_dev)) {

42. if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())

43. /*source route option */

44. in_dev_put(in_dev);

45. goto drop;

46. }

47. }

48. if (ip_options_rcv_srr(skb)) /*源路由选项处理*/

49. goto drop;

50. }

51. }

52.

53. return dst_input(skb);

54.

55. inhdr_error:

56. IP_INC_STATS_BH(IpInHdrErrors);

57. drop:

58. kfree_skb(skb);

59. return NET_RX_DROP;

60. }

 

 

 

     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-inputip_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发包不产生软中断处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值