L2数据链路层的数据包处理详细流程

标签: 网卡驱动  Linux  数据包

原文出处:http://lvzg2005.blog.51cto.com/2015450/1240892

 

在Linux kernel中,由网卡驱动完成L1物理层和L2数据链路层的工作。


首先看函数net_dev_init
  1. static int __init net_dev_init(void)

  2. {

  3. int i, rc =-ENOMEM;


  4. BUG_ON(!dev_boot_phase);

/* 
创建对应的/proc文件,如/proc/net/dev, /proc/net/softnet_stat等
*/
  1. if(dev_proc_init())

  2. goto out;

/* 初始化netdev对应的kobject*/
  1. if(netdev_kobject_init())

  2. goto out;

/* 
初始化数据链路层的handle上层数据类型表。
回忆前文《TCP/IP学习(28)——数据包完整接受流程》中,在inet_init中注册了IP包类型到这个表中。
*/
  1. INIT_LIST_HEAD(&ptype_all);

  2. for(= 0; i < PTYPE_HASH_SIZE; i++)

  3. INIT_LIST_HEAD(&ptype_base[i]);

/*
注册neddev_net_ops subsystem
*/
  1. if(register_pernet_subsys(&netdev_net_ops))

  2. goto out;


  3. /*

  4. * Initialise the packet receive queues.

  5. */

  6. /*

  7. 为每个CPU初始化PERCPU的全局变量softnet_data,作为该CPU的接收缓存 

  8. */

  9. for_each_possible_cpu(i){

  10. struct softnet_data *sd =&per_cpu(softnet_data, i);


  11. ...... ......

  12. }


  13. dev_boot_phase = 0;


  14. /* The loopback device is special if any other network devices

  15. *is present in a network namespace the loopback device must

  16. * be present. Since we now dynamically allocate and free the

  17. * loopback device ensure this invariant is maintained by

  18. * keeping the loopback device as the first device on the

  19. * list of network devices. Ensuring the loopback devices

  20. *is the first device that appears and the last network device

  21. * that disappears.

  22. */

  23. if(register_pernet_device(&loopback_net_ops))

  24. goto out;


  25. if(register_pernet_device(&default_device_ops))

  26. goto out;

/*
enable软中断
*/
  1. open_softirq(NET_TX_SOFTIRQ, net_tx_action);

  2. open_softirq(NET_RX_SOFTIRQ, net_rx_action);


  3. hotcpu_notifier(dev_cpu_callback, 0);

  4. dst_init();

  5. dev_mcast_init();

  6. rc = 0;

  7. out:

  8. return rc;

  9. }

net_dev_init在系统启动时,在注册网卡之前调用,主要就是初始化net device所需要的一些环境。

下面仍然以Intel PRO/1000的网卡驱动为例,e1000_init_module为该驱动的入口。通过e1000_init_module->pci_register_driver->e1000_probe进入初始化函数。
在e1000_probe中,通过下面这条语句绑定了操作函数。
netdev->netdev_ops = &e1000_netdev_ops;
  1. static const struct net_device_ops e1000_netdev_ops ={

  2. .ndo_open = e1000_open,

  3. ...... ......

  4. };

对于今天的主题来说,只需关心e1000_open即可。因为该函数是在激活该网卡时被调用,完成资源的申请,中断的注册,即e1000_intr。
  1. static irqreturn_t e1000_intr(int irq, void *data)

  2. {

  3. ...... ...... 

  4. /*

  5. 检测是否可以调度NAPI:

  6. 当没有disable NAPI且没有该网卡对应的NAPI在运行时(保证对应一个网卡的NAPI只有一个实例在运行),即可调度一个新的NAPI。

  7. NAPI是一种新的网卡数据检查处理方式。基本上是interrupt+poll。详细信息问google

  8. */

  9. if(likely(napi_schedule_prep(&adapter->napi))){

  10. /* 

  11. 清楚单次的统计信息。

  12. 刚看到这里时,我也奇怪,为什么total的统计信息要被清零。

  13. 实际上这些统计信息只是一次NAPI运行的统计信息,并不是网卡总的统计信息。

  14. 网卡的统计信息为netdev->stats。NAPI运行完会将下面的值加到网卡的统计信息上的。

  15. */

  16. adapter->total_tx_bytes = 0;

  17. adapter->total_tx_packets = 0;

  18. adapter->total_rx_bytes = 0;

  19. adapter->total_rx_packets = 0;

  20. /* 要求调度对应的NAPI实例 */

  21. __napi_schedule(&adapter->napi);

  22. }else{

  23. /* this really should notif it does it is basically a

  24. * bug, but not a hard error, so enable ints and continue */

  25. if(!test_bit(__E1000_DOWN,&adapter->flags))

  26. e1000_irq_enable(adapter);

  27. }


  28. return IRQ_HANDLED;

  29. }

上面为中断的关键流程,其中要求调度对应的NAPI实例时,实际上是引发一个软中断。
__raise_softirq_irqoff(NET_RX_SOFTIRQ)。这个中断函数的主要功能就是要求调度一个NAPI——这里跟以前理解的中断函数不太一样。按照教科书式的概念,网卡的中断函数,应该将数据包从网卡的缓冲中取出放到一个系统缓冲中,然后在引发软中断去做剩下的工作。

下面看 NET_RX_SOFTIRQ软中断对应的处理函数net_rx_action。
  1. static void net_rx_action(struct softirq_action *h)

  2. {

  3. struct softnet_data *sd =&__get_cpu_var(softnet_data);

  4. unsigned long time_limit = jiffies + 2;

  5. int budget = netdev_budget;

  6. void *have;


  7. local_irq_disable();

/* 开始顺序poll所有需要poll的网卡 */
  1. while(!list_empty(&sd->poll_list)){

  2. struct napi_struct *n;

  3. int work, weight;


  4. /*If softirq windowis exhuasted then punt.

  5. * Allow this to run for 2 jiffies since which will allow

  6. * an average latency of 1.5/HZ.

  7. */

  8. if(unlikely(budget <= 0 || time_after(jiffies, time_limit)))

  9. goto softnet_break;


  10. local_irq_enable();


  11. /* Even though interrupts have been re-enabled, this

  12. * access is safe because interrupts can only add new

  13. * entries to the tail of this list,and only ->poll()

  14. * calls can remove this head entry from the list.

  15. */

  16. /* 取得一个网卡的NAPI实例 */

  17. = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

/* 给这个实例上锁 */
  1. have = netpoll_poll_lock(n);


  2. weight = n->weight;


  3. /* This NAPI_STATE_SCHED test isfor avoiding a race

  4. * with netpoll's poll_napi(). Only the entity which

  5. * obtains the lock and sees NAPI_STATE_SCHED set will

  6. * actually make the ->poll()call. Therefore we avoid

  7. * accidently calling ->poll() when NAPI isnot scheduled.

  8. */

  9. work = 0;

  10. if(test_bit(NAPI_STATE_SCHED,&n->state)){

  11. /* poll这个网卡 */

  12. work = n->poll(n, weight);

  13. trace_napi_poll(n);

  14. }


  15. WARN_ON_ONCE(work > weight);


  16. budget -= work;


  17. local_irq_disable();


  18. /* Drivers must not modify the NAPI state if they

  19. * consume the entire weight.In such cases this code

  20. * still "owns" the NAPI instance and therefore can

  21. * move the instance around on the list at-will.

  22. */

  23. if(unlikely(work == weight)){

  24. /* 该NAPI的weight消耗完毕,需要处理下一个 */

  25. if(unlikely(napi_disable_pending(n))){

  26. local_irq_enable();

  27. napi_complete(n);

  28. local_irq_disable();

  29. }else

  30. list_move_tail(&n->poll_list,&sd->poll_list);

  31. }


  32. netpoll_poll_unlock(have);

  33. }

  34. out:

  35. net_rps_action_and_irq_enable(sd);


  36. #ifdef CONFIG_NET_DMA

  37. /*

  38. * There may not be any more sk_buffs coming rightnow, so push

  39. * any pending DMA copies to hardware

  40. */

  41. dma_issue_pending_all();

  42. #endif


  43. return;


  44. softnet_break:

  45. sd->time_squeeze++;

  46. __raise_softirq_irqoff(NET_RX_SOFTIRQ);

  47. goto out;

  48. }

通过上面这个软中断处理函数,对应每个网卡来说,又需要跳回驱动,去学习对应的poll函数。对于本文的这个驱动来说,poll函数就是e1000_clean->e1000_clean_rx_irq。这个函数是真正用于处理网卡接收数据包的工作。
  1. static bool e1000_clean_rx_irq(struct e1000_adapter *adapter,

  2. struct e1000_rx_ring *rx_ring,

  3. int*work_done,int work_to_do)

  4. {

  5. ...... ......

/* 得到当前需要处理buffer*/
  1. = rx_ring->next_to_clean;

  2. rx_desc = E1000_RX_DESC(*rx_ring, i);

  3. buffer_info =&rx_ring->buffer_info[i];


  4. while(rx_desc->status & E1000_RXD_STAT_DD){

  5. struct sk_buff *skb;

  6. u8 status;


  7. if(*work_done >= work_to_do//如果已经poll到足够的包,可以跳出返回

  8. break;

  9. (*work_done)++;

  10. rmb();/* read descriptor and rx_buffer_info after status DD */

/* 得到数据包buffer对应的skb buffer结构地址 */
  1. status = rx_desc->status;

  2. skb = buffer_info->skb;

  3. buffer_info->skb =NULL;

/* 
然后做一些网卡硬件相关,及一些sanity check
*/
...... ......
  1. /* 

  2. 设置skb->pkt_type:PACKET_BROADCAST等;

  3. 即数据链路层协议类型

  4. */

  5. skb->protocol = eth_type_trans(skb, netdev);

/* 将数据包传递给上层,并做一些通用数据链路层的处理 */
  1. e1000_receive_skb(adapter, status, rx_desc->special, skb);


  2. next_desc:

  3. /* 处理下一个数据包 */

  4. ...... ......

  5. }


/* 更新统计信息等*/
  1. ...... ...... 


  2. return cleaned;

  3. }

在这个函数中,真正的从网卡buffer中取出数据包,然后根据硬件的特性做一些特定处理,并简单的设置了数据包的一些field,完成L1的操作,设置好L2的报头。这时,数据包已经为TCP/IP协议栈所需要的skb_buff结构。
然后调用e1000_receive_skb->netif_receive_skb->__netif_receive_skb
  1. static int __netif_receive_skb(struct sk_buff *skb)

  2. {

  3. struct packet_type *ptype,*pt_prev;

  4. rx_handler_func_t *rx_handler;

  5. struct net_device *orig_dev;

  6. struct net_device *master;

  7. struct net_device *null_or_orig;

  8. struct net_device *orig_or_bond;

  9. int ret = NET_RX_DROP;

  10. __be16 type;

/* 为skb打时间戳 */
  1. if(!netdev_tstamp_prequeue)

  2. net_timestamp_check(skb);

/* vlan下硬件加速处理 */
  1. if(vlan_tx_tag_present(skb)&& vlan_hwaccel_do_receive(skb))

  2. return NET_RX_SUCCESS;


  3. /*if we've gotten here through NAPI, check netpoll */

  4. if(netpoll_receive_skb(skb))

  5. return NET_RX_DROP;

/* 设置skb的iif为接收网卡的索引 */
  1. if(!skb->skb_iif)

  2. skb->skb_iif = skb->dev->ifindex;

  1. /*

  2. * bonding note: skbs received on inactive slaves should only

  3. * be delivered to pkt handlers that are exact matches. Also

  4. * the deliver_no_wcard flag will be set.If packet handlers

  5. * are sensitive to duplicate packets these skbs will need to

  6. * be dropped at the handler. The vlan accel path may have

  7. * already set the deliver_no_wcard flag.

  8. */

  9. /*关于网卡的bond的处理, 这个feature我只是了解,所以略过 */

  10. null_or_orig =NULL;

  11. orig_dev = skb->dev;

  12. master = ACCESS_ONCE(orig_dev->master);

  13. if(skb->deliver_no_wcard)

  14. null_or_orig = orig_dev;

  15. elseif(master){

  16. if(skb_bond_should_drop(skb, master)){

  17. skb->deliver_no_wcard = 1;

  18. null_or_orig = orig_dev;/* deliver only exact match */

  19. }else

  20. skb->dev = master;

  21. }


  22. __this_cpu_inc(softnet_data.processed);

  23. /* 初始化l3 header 和 l4 header 的地址*/

  24. skb_reset_network_header(skb);

  25. skb_reset_transport_header(skb);

  26. /* 得到mac地址长度,准确来说是2层地址的长度 */

  27. skb->mac_len = skb->network_header - skb->mac_header;


  28. pt_prev =NULL;


  29. rcu_read_lock();


  30. /*

  31. 省略一些不太相关的代码 

  32. */

  33. ...... ......

  34.  

  35. /*

  36. 通过2层协议类型作为key,得到相应链表。

  37. */

  1. type = skb->protocol;

  2. list_for_each_entry_rcu(ptype,

  3. &ptype_base[ntohs(type)& PTYPE_HASH_MASK], list){

  4. if(ptype->type == type &&(ptype->dev == null_or_orig ||

  5. ptype->dev == skb->dev || ptype->dev == orig_dev ||

  6. ptype->dev == orig_or_bond)){

  7. if(pt_prev//找到匹配的协议类型,上传给L3层

  8. ret = deliver_skb(skb, pt_prev, orig_dev);

  9. pt_prev = ptype;

  10. }

  11. }


  12. if(pt_prev){

  13. ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

  14. }else{

  15. kfree_skb(skb);

  16. /* Jamal,now you will not able to escape explaining

  17. * me how you were going to use this.:-)

  18. */

  19. ret = NET_RX_DROP;

  20. }


  21. out:

  22. rcu_read_unlock();

  23. return ret;

  24. }

现在基本上已经比较详细的学习了L2层的数据包处理流程。当然,还有很多很多的细节没有涉及,道路还很漫长啊。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值