netpoll浅析

netpoll只是一种框架和一些接口,只有依赖这个框架和接口实现的netpoll实例,netpoll才能发挥它的功能。类似于kernel中的vfs,vfs本身并不会去做具体的文件操作,只是为不同的文件系统提供了一个框架。netpoll不依赖于网络协议栈,因此在内核网络及I/O子系统尚未可用时,也可以发送或接收数据包。当然netpoll能够处理的数据包类型也很有限,只有UDP和ARP数据包,并且只能是以太网报文。注意这里对UDP数据包的处理并不像四层的UDP协议那样复杂,并且netpoll可以发挥作用要依赖网络设备的支持。
  1、netpoll结构和netpoll_info结构
  netpoll结构用来描述接收和发送数据包的必要信息,每一个依赖netpoll的模块在使用这个框架前都必须实现并注册netpoll实例。

  netpoll结构定义如下:

[cpp] view plain copy
  1. struct netpoll {  
  2.     struct net_device *dev;  
  3.     char dev_name[IFNAMSIZ];  
  4.    
  5.     const char *name;  
  6.    
  7.     void (*rx_hook)(struct netpoll *, intchar *, int);  
  8.    
  9.     __be32 local_ip, remote_ip;  
  10.     u16 local_port, remote_port;  
  11.    
  12.     u8 remote_mac[ETH_ALEN];  
  13. };  
  dev成员存储的是绑定的网络设备实例,netpoll实例只能通过特定的网络设备接收和发送数据包。该设备在注册netpoll实例时设置。
  dev_name存储的是网络设备名,通过它调用dev_get_by_name()获取指定的网络设备实例,并保存在dev中。
  name是netpoll实例的名称。
  netpoll实例有两种:能接收数据包和不能接收数据包。如果要接收数据包的话,必须实现rx_hook接口。如果不接收数据包的话,则不用。
  local_ip和remote_ip分别存储的是远端和本地的IP,由netpoll实例指定。
  local_port和remote_port分别存储的是远端和本地的port。
  remote_mac存储的MAC地址。netpoll只支持以太网数据包,所以这里的MAC地址是以太网MAC地址。
  当支持netpoll时,网络设备的net_device实例必须实现npinfo成员,即网络设备的netpoll_info信息块,描述结构为netpoll_info,定义如下:
[cpp] view plain copy
  1. struct netpoll_info {  
  2.    
  3.     atomic_t refcnt;  
  4.    
  5.     int rx_flags;  
  6.    
  7.     spinlock_t rx_lock;  
  8.    
  9.     struct netpoll *rx_np; /* netpoll that registered an rx_hook */  
  10.    
  11.     struct sk_buff_head arp_tx; /* list of arp requests to reply to */  
  12.    
  13.     struct sk_buff_head txq;  
  14.     struct delayed_work tx_work;  
  15. };  
  refcnt是引用计数。每个netpoll_info实例被多个netpoll实例引用,每次引用时都对该成员加1.
  rx_flags是标识接收的特性,可取的值为NETPOLL_RX_ENABLED和NETPOLL_RX_DROP(尚未使用)。如果所属的netpoll实例允许接收数据包,则会设置为NETPOLL_RX_ENABLED,否则为0.
  rx_lock用来保证同一时刻只有一个CPU在进行相关的netpoll的输入操作。除此之外,在清理netpoll实例操作与netpoll的输入操作互斥,参见netpoll_cleanup().
  如果注册的netpoll实例可以接收数据包,则将实例存储在rx_np成员中,不过该成员在发送数据包时也会使用,参见arp_reply().
  arp_tx存储的是接收到的ARP报文。这里存储的ARP报文是在service_arp_queue()中处理的,而调用该函数的是netpoll_poll(),后面再讨论netpoll_poll()函数。
  如果netpoll没有能成功发送数据包或者设备繁忙,则将待输出报文缓存到txq队列中,重新调度tx_work工作队列,等待再次尝试发送。
  2、netpoll的输入
  netpoll_rx()函数是netpoll接收数据包的入口函数,在netif_rx()和netif_receive_skb()中都会调用到。如果该函数返回0,则表示当前数据包不是netpoll想要的,继续传递到上层协议栈继续处理;如果返回1,则表示由netpoll来处理,不再向上层传递。
  如果是ARP包,是否接收还要看静态变量trapped。trapped默认状态下是0,只有在poll_one_api()中调用网络设备的poll接口接收数据包前,才会加1,接收完后又会减1,如下所示:
[cpp] view plain copy
  1. static int poll_one_napi(struct netpoll_info *npinfo,  
  2.              struct napi_struct *napi, int budget)  
  3. {  
  4.     int work;  
  5.    
  6.     /* net_rx_action's ->poll() invocations and our's are 
  7.      * synchronized by this test which is only made while 
  8.      * holding the napi->poll_lock. 
  9.      */  
  10.     if (!test_bit(NAPI_STATE_SCHED, &napi->state))  
  11.         return budget;  
  12.    
  13.     npinfo->rx_flags |= NETPOLL_RX_DROP;  
  14.     atomic_inc(&trapped);  
  15.     set_bit(NAPI_STATE_NPSVC, &napi->state);  
  16.    
  17.     work = napi->poll(napi, budget);  
  18.     trace_napi_poll(napi);  
  19.    
  20.     clear_bit(NAPI_STATE_NPSVC, &napi->state);  
  21.     atomic_dec(&trapped);  
  22.     npinfo->rx_flags &= ~NETPOLL_RX_DROP;  
  23.    
  24.     return budget - work;  
  25. }  
  poll_one_api()是由poll_napi()调用的,如果当前CPU和接收数据包的CPU不是一个CPU,并且此时网卡被放置到轮询列表,即设置了NAPI_STATE_SCHED,才会去执行接收操作。所以netpoll在调度接收网卡的数据包过程中会trap数据包(trapped不为0),这种情况下ARP包会被接收。如果trapped为0,即不trap数据包,并且是ARP数据包,则会传递到上层协议栈。不过,在__netpoll_rx()中返回之前,trapped此时不为0,会丢弃ARP包。
  如果是UDP数据包,则主要是检查校验和和IP地址、端口号等信息,确定是否是netpoll想要的数据包,如果不是,则根据trapped决定是丢弃数据包还是传递到上层协议栈。如果是netpoll实例感兴趣的报文,则会调用其注册的rx_hook接口来接收,然后释放掉SKB包(注意,是在调用rx_hook之后立即释放)。
  还有一点需要注意,如果netpoll在调度接收网卡的数据包,即trapped不为0,这个过程中会直接释放掉所有不是netpoll想要的数据包(只是netpoll实例绑定的网卡上的数据包)。个人理解是,netpoll只有在发送数据包没有成功或者分配skb失败(尝试10次)时(都是在向外输出数据包的时候)才会调度网卡接收数据包,如果出现这种情况,则说明网卡非常繁忙,并且很多数据包没来得及处理,此时丢掉数据包也是合理的。
3、netpoll的输出
  如果接收到ARP报文,会调用arp_reply()来发送ARP响应;如果是UDP报文,则由netpoll实例处理,发送数据包调用的是netpoll_send_udp()。不过这两个接口最终都是在封装好要发送的数据包后,交给netpoll_send_skb()来发送,如下所示:
[cpp] view plain copy
  1. static void netpoll_send_skb(struct netpoll *np, struct sk_buff *skb)  
  2. {  
  3.     ......  
  4.    
  5.     /* don't get messages out of order, and no recursion */  
  6.     if (skb_queue_len(&npinfo->txq) == 0 && !netpoll_owner_active(dev)) {  
  7.         struct netdev_queue *txq;  
  8.         unsigned long flags;  
  9.    
  10.         txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));  
  11.    
  12.         local_irq_save(flags);  
  13.         /* try until next clock tick */  
  14.         for (tries = jiffies_to_usecs(1)/USEC_PER_POLL;  
  15.              tries > 0; --tries) {  
  16.             if (__netif_tx_trylock(txq)) {  
  17.                 if (!netif_tx_queue_stopped(txq)) {  
  18.                     status = ops->ndo_start_xmit(skb, dev);  
  19.                     if (status == NETDEV_TX_OK)  
  20.                         txq_trans_update(txq);  
  21.                 }  
  22.                 __netif_tx_unlock(txq);  
  23.    
  24.                 if (status == NETDEV_TX_OK)  
  25.                     break;  
  26.    
  27.             }  
  28.    
  29.             /* tickle device maybe there is some cleanup */  
  30.             netpoll_poll(np);  
  31.    
  32.             udelay(USEC_PER_POLL);  
  33.         }  
  34.    
  35.         WARN_ONCE(!irqs_disabled(),  
  36.             "netpoll_send_skb(): %s enabled interrupts in poll (%pF)\n",  
  37.             dev->name, ops->ndo_start_xmit);  
  38.    
  39.         local_irq_restore(flags);  
  40.     }  
  41.    
  42.     if (status != NETDEV_TX_OK) {  
  43.         skb_queue_tail(&npinfo->txq, skb);  
  44.         schedule_delayed_work(&npinfo->tx_work,0);  
  45.     }  
  46. }  
  从上面的代码我们可以看到,只有在(skb_queue_len(&npinfo->txq) == 0 && !netpoll_owner_active(dev))为真时才会尝试,发送数据包,否则直接缓存到txq队列中。
  如果npinfo->txq队列不为空,说明tx_work工作队列已经被调度执行,此时直接将数据包缓存到txq队列中,通过tx_work工作队列来输出。注意,这里调用schedule_delayed_work()的时候,延迟时间设置的是0,所以如果重新调度的话,tx_work工作队列会立即开始执行。
  如果npinfo->txq队列为空,是否将数据包直接缓存到txq队列,取决于netpoll_owner_active()的返回值。netpoll_owner_active()源码如下:
[cpp] view plain copy
  1. static int netpoll_owner_active(struct net_device *dev)  
  2. {  
  3.     struct napi_struct *napi;  
  4.    
  5.     list_for_each_entry(napi, &dev->napi_list, dev_list) {  
  6.         if (napi->poll_owner == smp_processor_id())  
  7.             return 1;  
  8.     }  
  9.     return 0;  
  10. }  
  poll_owner是在接收数据包的软中断处理函数net_rx_action()中设置的,保存的是当前处理软中断的CPU的ID。如果netpoll实例绑定的网卡没有在接收数据包,也就是网卡没有放到设备轮询列表上,此时会直接返回0.如果此时网卡被放到轮询列表上,但是接收数据包的CPU不是当前的CPU,也会返回0。如果此时绑定的网卡正在接收数据包,并且是当前CPU,才会返回1,这时netpoll在发送SKB包时,会直接将数据包放到txq队列中,等待tx_work工作队列发送。  
  如果不是上述情况,netpoll_send_skb()会立即调用网络设备的ndo_start_xmit接口发送数据包。如果发送失败,则会尝试多次,直到下一次时钟节拍。如果仍然没有发送成功,则会将数据包缓存到txq队列中。
  在尝试重新发送的过程中,netpoll对调用netpoll_poll()接口来模拟网络设备接收到数据包的中断,然后借助其他CPU来接收数据包,源码如下:
[cpp] view plain copy
  1. void netpoll_poll(struct netpoll *np)  
  2. {  
  3.     struct net_device *dev = np->dev;  
  4.     const struct net_device_ops *ops;  
  5.    
  6.     if (!dev || !netif_running(dev))  
  7.         return;  
  8.    
  9.     ops = dev->netdev_ops;  
  10.     if (!ops->ndo_poll_controller)  
  11.         return;  
  12.    
  13.     /* Process pending work on NIC */  
  14.     ops->ndo_poll_controller(dev);  
  15.    
  16.     poll_napi(dev);  
  17.    
  18.     /* 
  19.       * 处理arp_tx队列中的ARP报文 
  20.       */  
  21.     service_arp_queue(dev->npinfo);  
  22.    
  23.     zap_completion_queue();  
  24. }  
  模拟中断的接口是ndo_poll_controller,如果网卡不支持,则直接返回。模拟中断后,网卡设备会被放到轮询列表上,在poll_api()中会检查接收数据包的CPU和当前CPU是否是同一个CPU,如果不是,则会调用poll_one_napi()去使用网络设备的poll接口来接收数据包,否则直接返回,避免在UP上出现递归的情况。如果可以接收数据包,则trapped会加1,此时netpoll会trap数据包,该网卡上不是netpoll想要的数据包都会被直接丢掉,也只有在这段时间netpoll才可以接收ARP报文。所以我们看到,处理netpoll接收到的ARP包的接口,只在netpoll_poll()中调用,也只有在此时才有必要去处理接收到的ARP包。
  综上所述,netpoll_poll()会加速网卡对数据包的处理,这样下次发送数据包时就更容易成功。
4、netpoll应用
  netconsole是依赖netpoll实现的,可以将本机的dmesg系统信息,通过网络的方式输出到另一台主机上。这样就可以实现远程监控某些主机的dmesg信息,给开发人员调试内核提供了非常方便的途径。netconolse的使用方法参见内核文档netconsole.txt,里面介绍的非常详细

你可能感兴趣的:(netpoll浅析)