网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 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( )。