Linux网络设备驱动之数据接收流程(六)

  网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx() 函数将 sk_buffer 传递给上层协议。下面是完成这个过程的函数模板。

/*
* 网络设备驱动中的中断处理函数模板
*/
1
static void xxx_interrupt(int irq, void *dev_id) 2 { 3 struct net_device *dev = (struct net_device *)dev_id; 4 ··· 5 switch (status & ISQ_EVENT_MASK) { 6 case ISQ_RECEIVER_EVENT: 7 /* 获取数据包 */ 8 xxx_rx(dev); 9 break; 10 /* 其他类型的中断 */ 11 } 12 } 13 14 15 static void xxx_rx(struct xxx_device *dev) 16 { 17 ··· 18 length = get_rev_len(···); 19 /* 分配新的套接字缓冲区 */ 20 skb = dev_alloc_skb(length + 2); 21 22 skb_reserve(skb, 2); /* 对齐 */ 23 skb->dev = dev; 24 25 /* 读取硬件上接收到的数据 */ 26 insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1); 27 if (length & 1) 28 skb->data[length - 1] = inw(ioaddr, + RX_FRAME_PORT); 29 30 /* 获取上层协议类型 */ 31 skb->protocol= eth_type_trans(skb, dev); 32 33 /* 把数据包交给上层 */ 34 netif_rx(skb); 35 36 /* 记录接收时间戳 */ 37 dev->last_rx = jiffies; 38 ··· 39 }

  从上述代码的5 ~ 8行可以看出,当设备的中断处理程序判断中断类型为数据包接收中断时,它调用15 ~ 39行定义的 xxx_rx( ) 函数完成更深入的数据包接收工作。 xxx_rx( ) 函数代码中的第 18 行从硬件读取到接收数据包有效数据的长度,第19 ~ 22行分配 sk_buff 和数据缓冲区,第 25 ~ 28行读取硬件上接收到的数据并放入数据缓冲区, 第30 ~ 31行解析接收数据包上层协议的类型,最后,第33 ~ 34行代码将数据包上交给上层协议。

  如果是 NAPI 兼容的设备驱动,则可以通过 poll 方式接收数据包。在这种情况下,我们需要为该设备驱动提供作为 netif_napi_add( ) 参数的 xxx_poll( ) 函数,代码如下所示:

 

 1 /*
 2  *  网络设备驱动的 xxx_poll() 函数模板
 3  */
 4 
 5 static int xxx_poll(struct napi_struct *napi, int budget)
 6 {
 7     int npackets = 0;
 8     struct sk_buff *skb;
 9     struct xxx_priv *priv = container_of(napi, struct xxx_priv, napi);
10     struct xxx_packet *pkt;
11 
12     while (npackets < budget && priv->rx_queue) {
13         /* 从队列中取出数据包 */
14         pkt = xxx_dequeue_buf(dev);
15 
16         /* 接下来的处理和中断触发的数据包接收一致 */
17         skb = dev_alloc_skb(pkt->datalen + 2);
18         ···
19         skb_reserve(skb, 2);
20         memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
21         skb->dev = dev;
22         skb->protocol = eth_type_trans(skb, dev);
23         /* 调用 netif_receive_skb,而不是 net_rx,将数据包交给上层协议 */
24         netif_receive_skb(skb);
25 
26         /* 更改统计数据 */
27         priv->stats.rx_packets++;
28         priv->stats.rx_bytes += pkt->datalen;
29         xxx_release_buffer(pkt);
30         npackets++;  
31     }   
32     if (npackets < budget) {
33         napi_complete(napi);
34         xxx_enable_rx_int(···);  /* 再次启动网络设备的接收中断 */
35     }
36     return npackets;
37 }

  上述代码中的 budget 是在初始化阶段分配给接口的 weight 值,从 xxx_poll() 函数每次只能接收最多 budget 个数据包。第 12 行的 while() 循环读取设备的接收缓冲区,同时读取数据包并提交给上层。这个过程和中断触发的数据包接收过程一致,但是最后使用的是 netif_receive_skb( ) 函数而不是 netif_rx( ) 函数将数据包提交给上层。这里体现出了 中断处理机制 和 轮询机制之间的差别。

  当一个轮询过程结束时,第 33 行代码调用 napi_complete( ) 宣布这一消息,而第 34 行代码则再次启动网络设备的接收中断。

  虽然 NAPI 兼容的设备驱动以 xxx_poll( ) 方式接收数据包,但是仍然需要首次数据包接收中断来触发这个过程。与数据包的中断接收方式不同的是,以轮询方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断并调度 NAPI,如以下代码所示:

 

 1 /*
 2  *  网络设备驱动的 poll 中断处理
 3  */
 4 
 5 static void xxx_interrupt(int irq, void *dev_id)
 6 {
 7     switch (status & ISQ_EVENT_MASK) {
 8     case ISQ_RECEIVER_EVENT:
 9         ···  /* 获取数据包 */
10         xxx_disable_rx_int(···);   /* 禁止接收中断 */
11         napi_schedule(&priv->napi);
12         break;
13     ···  /* 其他类型的中断 */
14     }
15 }    

 

  上述代码第 11 行的 napi_schedule( ) 函数被轮询方式驱动的中断程序调用,将设备的 poll 方法添加到网络层的 poll 处理队列中,排队并且准备接收数据包,最终触发一个 NET_RX_SOFTIRQ 软中断,从而通知网络层接收数据包。下图为 NAPI 驱动程序各部分的调用关系。

    

  在支持 NAPI 的网络设备驱动中,通常还会进行如下与 NAPI 相关的工作。

  1. 在私有数据结构体 (如 xxx_priv)中增加一个成员:

struct napi_struct napi;

  在代码中就可以方便地使用 container_of( ) 通过 NAPI 成员反向获得对应的 xxx_priv 指针。

  2. 通常会在设备驱动初始化时调用:

netif_napi_add(dev, napi, xxx_poll, XXX_NET_NAPI_WEIGHT);

  3. 通常会在 net_device 结构体的 open( ) 和 stop( ) 成员函数中分别调用 napi_enable( ) 和 napi_disable( )。


posted @ 2020-07-26 23:36  闹闹爸爸  阅读(818)  评论(0编辑  收藏  举报