网卡驱动数据包接收流程——基于RTL8139网卡

 1 中断函数

static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)

所有有网卡产生的中断都会引起该中断函数的调用,这是在

static int rtl8139_open (struct net_device *dev)
{
 struct rtl8139_private *tp = netdev_priv(dev);
 int retval;
 void __iomem *ioaddr = tp->mmio_addr;
        /* 注册中断处理函数 ,中断号为共享 */
 retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);

完成的。

中断函数处理的事件可以大致分为几类:

A 数据包到达产生的中断(RxAckBits = RxFIFOOver | RxOverflow | RxOK);

B 异常事件,通常都是出错的情况(RxAckBits = RxFIFOOver | RxOverflow | RxOK)

C发送完成事件(TxOK | TxErr)

下面我们看看具体的代码

static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
{
        /* 参数dev_instance是在上面注册中断处理函数的时候传入的 */
 struct net_device *dev = (struct net_device *) dev_instance;
        /* tp 为网卡驱动自定义的驱动特有的数据,和dev一起分配的 */
 struct rtl8139_private *tp = netdev_priv(dev);
 void __iomem *ioaddr = tp->mmio_addr;
 u16 status, ackstat;
 int link_changed = 0; /* avoid bogus "uninit" warning */
 int handled = 0;
        /* 对驱动数据加锁*/
 spin_lock (&tp->lock);
        /*读中断状态寄存器,获取中断状态*/
 status = RTL_R16 (IntrStatus);

 /* 这时由共享此中断号的其它设备产生的中断 */
 if (unlikely((status & rtl8139_intr_mask) == 0))
  goto out;

 handled = 1;

 /* 硬件错误 */
 if (unlikely(status == 0xFFFF))
  goto out;

 /* 设备已关闭*/
 if (unlikely(!netif_running(dev))) {
                /* 屏蔽所有中断*/
  RTL_W16 (IntrMask, 0);
  goto out;
 }

 /* Acknowledge all of the current interrupt sources ASAP, but
    an first get an additional status bit from CSCR. */
 if (unlikely(status & RxUnderrun))
  link_changed = RTL_R16 (CSCR) & CSCR_LinkChangeBit;

 ackstat = status & ~(RxAckBits | TxErr);
 if (ackstat)
  RTL_W16 (IntrStatus, ackstat);

下一步处理数据包到达事件

/* Receive packets are processed by poll routine.
    If not running start it now. */
 if (status & RxAckBits){
  if (netif_rx_schedule_prep(dev, &tp->napi)) {
   RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
   __netif_rx_schedule(dev, &tp->napi);
  }
 }

先强调下8139网卡驱动的数据接收方式采用的是Linux内核的NAPI新机制。

NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。随着网络的接收速度的增加,NIC 触发的中断能做到不断减少。

使用 NAPI 先决条件:
驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的加入并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证:
1.         要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。
2.         在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。 (这就是锁NAPI机制并不是所有的网卡都能支持的,只有在关闭中断的情况下,任然能通过DMA接收数据包的网卡才可以)
NAPI 对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI 就会强制执行dev->poll 方法。而和不像以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。

--------------------------------------------------------------------------------

 如果定义了采用NAPI模式接收数据包,则进入这个调用点。首先调用netif_rx_schedule_prep(dev),确定设备处于运行,而且设备还没有被添加到网络层的 POLL 处理队列中,在调用 netif_rx_schedule之前会调用这个函数。接下来调用__netif_rx_schedule(dev),将设备的 POLL 方法添加到网络层次的 POLL 处理队列中去,排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,并且触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。处理完成。

下面介绍一下__netif_rx_schedule(netdev)函数的作用:

static inline void __netif_rx_schedule(struct net_device *dev)
{
       unsigned long flags;
       /*    获取当前CPU。   */
int cpu = smp_processor_id();
 
       local_irq_save(flags);
       dev_hold(dev);
       /*将当前设备加入CPU相关全局队列softnet_data的轮询设备列表中,不过值得注意的是,这个列表中的设备不一定都执行轮询接收数据包,这里的poll_list只是表示当前设备需要接收数据,具体采用中断还是轮询的方式,取决于设备提供的poll方法。*/
       list_add_tail(&dev->poll_list, &softnet_data[cpu].poll_list);
      
       /*
       调用函数产生网络接收软中断。也就是系统将运行net_rx_action()处理网络数据。
       */
       __cpu_raise_softirq(cpu, NET_RX_SOFTIRQ);
       local_irq_restore(flags);
}
 

接下来是多出错事件以及数据包发送完成事件的处理

/* Check uncommon events with one test. */
 if (unlikely(status & (PCIErr | PCSTimeout | RxUnderrun | RxErr)))
  rtl8139_weird_interrupt (dev, tp, ioaddr,
      status, link_changed);

 if (status & (TxOK | TxErr)) {
  rtl8139_tx_interrupt (dev, tp, ioaddr);
  if (status & TxErr)
   RTL_W16 (IntrStatus, TxErr);
 }

 

出错事件的处理较为简单,只是对一些出错计数的处理。下面我们看看发送完成事件的处理

 

2 发送完成事件处理

static void rtl8139_tx_interrupt (struct net_device *dev,
      struct rtl8139_private *tp,
      void __iomem *ioaddr)
{
 unsigned long dirty_tx, tx_left;

 assert (dev != NULL);
 assert (ioaddr != NULL);
        /*dirty_tx是最近发送数据包时,没有经中断处理的最早数据包所对应的
            发送描述符*/
 dirty_tx = tp->dirty_tx;
        /* cur_tx是最近发送完成的最后一个数据包对应的发送描述符,所以在
           此次中断中要处理的就是和dirty_tx之间的发送描述符*/
 tx_left = tp->cur_tx - dirty_tx;
 while (tx_left > 0) {
                 /* 环形缓冲区,最大为NUM_TX_DESC,取模得到真实值*/
  int entry = dirty_tx % NUM_TX_DESC;
  int txstatus;
                /*当前发送描述符的发送状态(一个寄存器为32bit)*/
  txstatus = RTL_R32 (TxStatus0 + (entry * sizeof (u32)));
                /*还没有发送*/
  if (!(txstatus & (TxStatOK | TxUnderrun | TxAborted)))
   break; /* It still hasn't been Txed */

  /* Note: TxCarrierLost is always asserted at 100mbps. */
  if (txstatus & (TxOutOfWindow | TxAborted)) {
   /* There was an major error, log it. */
   if (netif_msg_tx_err(tp))
    printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x./n",
     dev->name, txstatus);
   dev->stats.tx_errors++;
   if (txstatus & TxAborted) {
    dev->stats.tx_aborted_errors++;
    RTL_W32 (TxConfig, TxClearAbt);
    RTL_W16 (IntrStatus, TxErr);
    wmb();
   }
   if (txstatus & TxCarrierLost)
    dev->stats.tx_carrier_errors++;
   if (txstatus & TxOutOfWindow)
    dev->stats.tx_window_errors++;
  } else {
   if (txstatus & TxUnderrun) {
    /* Add 64 to the Tx FIFO threshold. */
    if (tp->tx_flag < 0x00300000)
     tp->tx_flag += 0x00020000;
    dev->stats.tx_fifo_errors++;
   }
   dev->stats.collisions += (txstatus >> 24) & 15;
   dev->stats.tx_bytes += txstatus & 0x7ff;
   dev->stats.tx_packets++;
  }

  dirty_tx++;
  tx_left--;
 }

#ifndef RTL8139_NDEBUG
 if (tp->cur_tx - dirty_tx > NUM_TX_DESC) {
  printk (KERN_ERR "%s: Out-of-sync dirty pointer, %ld vs. %ld./n",
          dev->name, dirty_tx, tp->cur_tx);
  dirty_tx += NUM_TX_DESC;
 }
#endif /* RTL8139_NDEBUG */

 /* only wake the queue if we did work, and the queue is stopped */
 if (tp->dirty_tx != dirty_tx) {
  tp->dirty_tx = dirty_tx;
  mb();
  netif_wake_queue (dev);
 }
}
 

 

3 软中断处理函数

由于在前面的中断处理程序中调用了__cpu_raise_softirq(cpu, NET_RX_SOFTIRQ),所以CPU会在中断处理完成后的适当的时候调用软中断处理函数,也就是我们在系统初始化的过程中注册的net_rx_action函数。

 static void net_rx_action(struct softirq_action *h)
{
        /*获取每个CPU的softnet_data结构,然后取得其poll_list */
 struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
 unsigned long start_time = jiffies;
 int budget = netdev_budget;
 void *have;

 local_irq_disable();
        /* 处理poll_list上关联的每一个设备*/
 while (!list_empty(list)) {
  struct napi_struct *n;
  int work, weight;

  /* If softirq window is exhuasted then punt.
   *
   * Note that this is a slight policy change from the
   * previous NAPI code, which would allow up to 2
   * jiffies to pass before breaking out.  The test
   * used to be "jiffies - start_time > 1".
   */
  if (unlikely(budget <= 0 || jiffies != start_time))
   goto softnet_break;

  local_irq_enable();

  /* Even though interrupts have been re-enabled, this
   * access is safe because interrupts can only add new
   * entries to the tail of this list, and only ->poll()
   * calls can remove this head entry from the list.
   */
  n = list_entry(list->next, struct napi_struct, poll_list);

  have = netpoll_poll_lock(n);

  weight = n->weight;

  /* This NAPI_STATE_SCHED test is for avoiding a race
   * with netpoll's poll_napi().  Only the entity which
   * obtains the lock and sees NAPI_STATE_SCHED set will
   * actually make the ->poll() call.  Therefore we avoid
   * accidently calling ->poll() when NAPI is not scheduled.
   */
  work = 0;
                /* 调用每个设备的pool方法接收数据*/
  if (test_bit(NAPI_STATE_SCHED, &n->state))
   work = n->poll(n, weight);

  WARN_ON_ONCE(work > weight);

  budget -= work;

  local_irq_disable();

  /* Drivers must not modify the NAPI state if they
   * consume the entire weight.  In such cases this code
   * still "owns" the NAPI instance and therefore can
   * move the instance around on the list at-will.
   */
  if (unlikely(work == weight)) {
                         /* 设备运行出错,或自己退出poll_list,就删除它*/
   if (unlikely(napi_disable_pending(n)))
    __napi_complete(n);
   else
                                 /* 该设备还有要接收的数据没被处理,因为轮询算法
                                         被移动到poll_llst尾部等待处理
                                      */
    list_move_tail(&n->poll_list, list);
  }

  netpoll_poll_unlock(have);
 }
out:
 local_irq_enable();

#ifdef CONFIG_NET_DMA
 /*
  * There may not be any more sk_buffs coming right now, so push
  * any pending DMA copies to hardware
  */
 if (!cpus_empty(net_dma.channel_mask)) {
  int chan_idx;
  for_each_cpu_mask_nr(chan_idx, net_dma.channel_mask) {
   struct dma_chan *chan = net_dma.channels[chan_idx];
   if (chan)
    dma_async_memcpy_issue_pending(chan);
  }
 }
#endif

 return;

softnet_break:
 __get_cpu_var(netdev_rx_stat).time_squeeze++;
 __raise_softirq_irqoff(NET_RX_SOFTIRQ);
 goto out;
}


通常,在网卡收发数据的时候,需要维护一个缓冲区队列,来缓存可能存在的突发数据,类似于前面的DMA环形缓冲区。
队列层中,包含了一个叫做struct softnet_data:
struct softnet_data
{
/*throttle 用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将被丢弃*/
int throttle;
/*netif_rx函数返回的拥塞级别*/
int cng_level;
int avg_blog;
/*softnet_data 结构包含一个指向接收和传输队列的指针,input_pkt_queue成员指向准备传送
给网络层的sk_buffs包链表的首部的指针,这个队列中的包是由netif_rx函数递交的*/
struct sk_buff_head input_pkt_queue;

struct list_head poll_list;
struct net_device *output_queue;
struct sk_buff *completion_queue;

struct net_device backlog_dev; /* Sorry. 8) */
};

内核使用了一个同名的变量softnet_data,它是一个Per-CPU变量,每个CPU都有一个。

 


--------------------------------------------------------------------------------


下一步进入设备的poll函数。需要注意的是,如果是NAPI的网卡驱动的话,poll函数是在驱动中注册的,驱动实现的;如果是非NAPI的话,就是内核定义的process_backlog函数,至于process_backlog是如何添加到poll_list中的,这里暂时不管,先看看8139驱动的poll 函数是如何实现的。

 

4    8139 poll函数实现

static int rtl8139_poll(struct napi_struct *napi, int budget)
{
 struct rtl8139_private *tp = container_of(napi, struct rtl8139_private, napi);
 struct net_device *dev = tp->dev;
 void __iomem *ioaddr = tp->mmio_addr;
 int work_done;

 spin_lock(&tp->rx_lock);
 work_done = 0;
        /* 在 rtl8139_rx中将接送到的数据拷贝出来并传递给上层协议驱动。*/
 if (likely(RTL_R16(IntrStatus) & RxAckBits))
  work_done += rtl8139_rx(dev, tp, budget);
        /*说明没有多余的数据到达,则恢复接收中断,并把此设备从poll_list中清除*/
 if (work_done < budget) {
  unsigned long flags;
  /*
   * Order is important since data can get interrupted
   * again when we think we are done.先关中断,在写中断屏蔽位
   */
  spin_lock_irqsave(&tp->lock, flags);
  RTL_W16_F(IntrMask, rtl8139_intr_mask);
  __netif_rx_complete(dev, napi);
  spin_unlock_irqrestore(&tp->lock, flags);
 }
 spin_unlock(&tp->rx_lock);

 return work_done;
}
 

从rtl8139_rx的代码也可以看出,当数据包接收出错或者是没有更多的数据包可以接收时,work_done才不会达到budget,这时,应该让网卡重新回到

中断的状态,以等待数据包的到来。另外一种情况就是work_done等于budget,很可能是因为还有数据包要接收,所以在net_rx_action函数中,只是

把该网卡设备移到队列的尾部,以期待在下次循环中再次调用其poll函数。

 

5 看看rtl8139_rx的实现

static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp,
        int budget)
{
 void __iomem *ioaddr = tp->mmio_addr;
 int received = 0;
        /* 网卡不断的把数据放进环形接收缓冲区, CPU 读出来的时候,读到哪里的顺序需要   自    己维护,tp->cur_rx记录上次读到哪里,这里将接着从上次的地方拷贝。*/
 unsigned char *rx_ring = tp->rx_ring;
 unsigned int cur_rx = tp->cur_rx;
 unsigned int rx_size = 0;

 DPRINTK ("%s: In rtl8139_rx(), current %4.4x BufAddr %4.4x,"
   " free to %4.4x, Cmd %2.2x./n", dev->name, (u16)cur_rx,
   RTL_R16 (RxBufAddr),
   RTL_R16 (RxBufPtr), RTL_R8 (ChipCmd));
        /*轮询寄存器,当ChipCmd RxBufEmpty 位没被网卡设置的时
               候,则说明环形缓冲区中有接收到的数据等待处理*/
 while (netif_running(dev) && received < budget
        && (RTL_R8 (ChipCmd) & RxBufEmpty) == 0) {
  u32 ring_offset = cur_rx % RX_BUF_LEN;
  u32 rx_status;
  unsigned int pkt_size;
  struct sk_buff *skb;

  rmb();
                /* 获取接收状态以及接收数据的长度*/
  /* read size+status of next frame from DMA ring buffer */
  rx_status = le32_to_cpu (*(__le32 *) (rx_ring + ring_offset));
  rx_size = rx_status >> 16;
                /* 实际数据包的长度,减去4个字节的CRC*/
  pkt_size = rx_size - 4;

  if (netif_msg_rx_status(tp))
   printk(KERN_DEBUG "%s:  rtl8139_rx() status %4.4x, size %4.4x,"
    " cur %4.4x./n", dev->name, rx_status,
    rx_size, cur_rx);
#if RTL8139_DEBUG > 2
  {
   int i;
   DPRINTK ("%s: Frame contents ", dev->name);
   for (i = 0; i < 70; i++)
    printk (" %2.2x",
     rx_ring[ring_offset + i]);
   printk ("./n");
  }
#endif
                /*当EarlyRX 允许的时候,可能会发生这种情况,一个完整的
                     数据包的一部分已经通过DMA 传送到了内存中,而另外一部
                   分还在网卡内部FIFO 中,网卡的DMA 操作还在进行中*/
  /* Packet copy from FIFO still in progress.
   * Theoretically, this should never happen
   * since EarlyRx is disabled.
   */
  if (unlikely(rx_size == 0xfff0)) {
   if (!tp->fifo_copy_timeout)
    tp->fifo_copy_timeout = jiffies + 2;
   else if (time_after(jiffies, tp->fifo_copy_timeout)) {
    DPRINTK ("%s: hung FIFO. Reset.", dev->name);
    rx_size = 0;
    goto no_early_rx;
   }
   if (netif_msg_intr(tp)) {
    printk(KERN_DEBUG "%s: fifo copy in progress.",
           dev->name);
   }
   tp->xstats.early_rx++;
   break;
  }

no_early_rx:
  tp->fifo_copy_timeout = 0;

  /* If Rx err or invalid rx_size/rx_status received
   * (which happens if we get lost in the ring),
   * Rx process gets reset, so we abort any further
   * Rx processing.
   */
  if (unlikely((rx_size > (MAX_ETH_FRAME_SIZE+4)) ||
        (rx_size < 8) ||
        (!(rx_status & RxStatusOK)))) {
   rtl8139_rx_err (rx_status, dev, tp, ioaddr);
   received = -1;
   goto out;
  }

  /* Malloc up new buffer, compatible with net-2e. */
  /* Omit the four octet CRC from the length. */

  skb = netdev_alloc_skb(dev, pkt_size + NET_IP_ALIGN);
  if (likely(skb)) {
   skb_reserve (skb, NET_IP_ALIGN); /* 16 byte align the IP fields. */
#if RX_BUF_IDX == 3
   wrap_copy(skb, rx_ring, ring_offset+4, pkt_size);
#else
   skb_copy_to_linear_data (skb, &rx_ring[ring_offset + 4], pkt_size);
#endif
   skb_put (skb, pkt_size);

   skb->protocol = eth_type_trans (skb, dev);

   dev->last_rx = jiffies;
   dev->stats.rx_bytes += pkt_size;
   dev->stats.rx_packets++;
                        数据包从这里进入上层
   netif_receive_skb (skb);
  } else {
   if (net_ratelimit())
    printk (KERN_WARNING
     "%s: Memory squeeze, dropping packet./n",
     dev->name);
   dev->stats.rx_dropped++;
  }
  received++;
                /* 前一个4是头部的状态和长度的4个字节,后面的3是为了对齐*/
  cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
  RTL_W16 (RxBufPtr, (u16) (cur_rx - 16));
                /*清除中断状态位*/
  rtl8139_isr_ack(tp);
 }

 if (unlikely(!received || rx_size == 0xfff0))
  rtl8139_isr_ack(tp);

#if RTL8139_DEBUG > 1
 DPRINTK ("%s: Done rtl8139_rx(), current %4.4x BufAddr %4.4x,"
   " free to %4.4x, Cmd %2.2x./n", dev->name, cur_rx,
   RTL_R16 (RxBufAddr),
   RTL_R16 (RxBufPtr), RTL_R8 (ChipCmd));
#endif

 tp->cur_rx = cur_rx;

 /*
  * The receive buffer should be mostly empty.
  * Tell NAPI to reenable the Rx irq.
  */
 if (tp->fifo_copy_timeout)
  received = budget;

out:
 return received;
}

 

 rtl8139_rx把数据从网卡接收缓存中拷贝出来。数据在环形缓冲区的存放格式如下:| 长度| 状态位| 内容| 长度| 状态位| 内容| ...

 长度和状态位一共4个字节

 

6 最后就是netif_receive_skb了,数据包从此离开链路层,向上层遨游

/**
 * netif_receive_skb - process receive buffer from network
 * @skb: buffer to process
 *
 * netif_receive_skb() is the main receive data processing function.
 * It always succeeds. The buffer may be dropped during processing
 * for congestion control or by the protocol layers.
 *
 * This function may only be called from softirq context and interrupts
 * should be enabled.
 *
 * Return values (usually ignored):
 * NET_RX_SUCCESS: no congestion
 * NET_RX_DROP: packet was dropped
 */
int netif_receive_skb(struct sk_buff *skb)
{
 struct packet_type *ptype, *pt_prev;
 struct net_device *orig_dev;
 struct net_device *null_or_orig;
 int ret = NET_RX_DROP;
 __be16 type;

 if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
  return NET_RX_SUCCESS;

 /* if we've gotten here through NAPI, check netpoll */
 if (netpoll_receive_skb(skb))
  return NET_RX_DROP;

 if (!skb->tstamp.tv64)
  net_timestamp(skb);

 if (!skb->iif)
  skb->iif = skb->dev->ifindex;

 null_or_orig = NULL;
 orig_dev = skb->dev;
 if (orig_dev->master) {
  if (skb_bond_should_drop(skb))
   null_or_orig = orig_dev; /* deliver only exact match */
  else
   skb->dev = orig_dev->master;
 }

 __get_cpu_var(netdev_rx_stat).total++;

 skb_reset_network_header(skb);
 skb_reset_transport_header(skb);
 skb->mac_len = skb->network_header - skb->mac_header;

 pt_prev = NULL;

 rcu_read_lock();

 /* Don't receive packets in an exiting network namespace */
 if (!net_alive(dev_net(skb->dev)))
  goto out;

#ifdef CONFIG_NET_CLS_ACT
 if (skb->tc_verd & TC_NCLS) {
  skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
  goto ncls;
 }
#endif

 list_for_each_entry_rcu(ptype, &ptype_all, list) {
  if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
      ptype->dev == orig_dev) {
   if (pt_prev)
    ret = deliver_skb(skb, pt_prev, orig_dev);
   pt_prev = ptype;
  }
 }

#ifdef CONFIG_NET_CLS_ACT
 skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
 if (!skb)
  goto out;
ncls:
#endif

 skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
 if (!skb)
  goto out;
 skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
 if (!skb)
  goto out;

 type = skb->protocol;
 list_for_each_entry_rcu(ptype,
   &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
  if (ptype->type == type &&
      (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
       ptype->dev == orig_dev)) {
   if (pt_prev)
    ret = deliver_skb(skb, pt_prev, orig_dev);
   pt_prev = ptype;
  }
 }

 if (pt_prev) {
  ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
 } else {
  kfree_skb(skb);
  /* Jamal, now you will not able to escape explaining
   * me how you were going to use this. :-)
   */
  ret = NET_RX_DROP;
 }

out:
 rcu_read_unlock();
 return ret;
 

 

 

 5),netif_receive_skb(skb)
这是一个辅助函数,用于在poll中处理接收到的帧。它主要是向各个已注册的协议处理例程发送一个SKB。
每个协议的类型由一个packet_type结构表示:
struct packet_type {
__be16 type;/* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
struct sk_buff *(*gso_segment)(struct sk_buff *skb,
int features);
int (*gso_send_check)(struct sk_buff *skb);
void *af_packet_priv;
struct list_head list;
};
它的主要域为:type, 为要处理的协议
func, 为处理这个协议的例程
所用到的协议在系统或模块加载的时候初始化,如IP协议:
static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
};
static int __init inet_init(void)
{
……
dev_add_pack(&ip_packet_type);
……
}
void dev_add_pack(struct packet_type *pt)
{
int hash;
spin_lock_bh(&ptype_lock);
if (pt->type == htons(ETH_P_ALL)) {
netdev_nit++;
list_add_rcu(&pt->list, &ptype_all);
} else {
hash = ntohs(pt->type) & 15;
list_add_rcu(&pt->list, &ptype_base[hash]);
}
spin_unlock_bh(&ptype_lock);
}
可以看到,dev_add_pack()是将一个协议类型结构链入某一个链表, 当协议类型为
ETH_P_ALL时,它将被链入ptype_all链表,这个链表是用于sniffer这样一些程序的,它接收所有NIC收到的包。还有一个是HASH链表ptype_base,用于各种协议,它是一个16个元素的数组,dev_add_pack()会根据协议类型将这个packet_type链入相应的HASH链表中。
而ptype_base与ptype_all的组织结构如下,一个为HASH链表,一个为双向链表:
int netif_receive_skb(struct sk_buff *skb)
{
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
return ret;
}
netif_receive_skb()的主要作用体现在两个遍历链表的操作中,其中之一为遍历ptype_all链,这些为注册到内核的一些sniffer,将上传给这些sniffer,另一个就是遍历ptype_base,这个就是具体的协议类型。假高如上图如示,当eth1接收到一个IP数据包时,它首先分别发送一份副本给两个ptype_all链表中的packet_type,它们都由package_rcv处理,然后再根据HASH值,在遍历另一个HASH表时,发送一份给类型为ETH_P_IP的类型,它由ip_rcv处理。如果这个链中还注册有其它IP层的协议,它也会同时发送一个副本给它。
其中,这个是由deliver_skb(skb, pt_prev, orig_dev)去完成的:
static __inline__ int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
可以看到,它只是一个包装函数,它只去执行相应packet_type里的func处理函数,如对于ETH_P_IP类型,由上面可以看到,它执行的就是ip_rcv了。
至此,一个以太网帧的链路层接收过程就全部完成,再下去就是网络层的处理了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(网卡驱动数据包接收流程——基于RTL8139网卡)