linux 网络设备驱动之报文接收

发布时间:2024年01月10日

从网络上接收报文比发送它要难一些, 因为必须分配一个 sk_buff 并从一个原子性上下
文中递交给上层. 网络驱动可以实现 2 种报文接收的模式: 中断驱动和查询. 大部分驱
动采用中断驱动技术, 这是我们首先要涉及的. 有些高带宽适配卡的驱动也可能采用查询
技术; 我们在"接收中断缓解"一节中了解这个方法.
snull 的实现将"硬件"细节从设备独立的常规事务中分离. 因此, 函数 snull_rx 在硬件
收到报文后从 snull 的"中断"处理中调用, 并且报文现在已经在计算机的内存中.
snull_rx 收到一个数据指针和报文长度; 它唯一的责任是发走这个报文和运行附加信息
给上层的网络代码. 这个代码独立于获得数据指针和长度的方式.
void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
struct sk_buff *skb;
struct snull_priv *priv = netdev_priv(dev);

/*
*
The packet has been retrieved from the transmission
*
medium. Build an skb around it, so upper layers can handle it
*/
skb = dev_alloc_skb(pkt->datalen + 2);
if (!skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n"); priv-
>stats.rx_dropped++; goto out;
}
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
/* Write metadata, and then pass to the receive level */
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
netif_rx(skb);
out:
return;
}
这个函数足够普通以作为任何网络驱动的一个模板, 但是在你有信心重用这个代码段前需
要一些解释.
第一步是分配一个缓存区来保存报文. 注意缓存分配函数 (dev_alloc_skb) 需要知道数
据长度. 函数用这些信息来给缓存区分配空间. dev_alloc_skb 使用 atomic 优先级调用
kmalloc , 因此它可以在中断时间安全使用. 内核提供了其他接口给 socket 缓存分配,
但是它们不值得在此介绍; socket 缓存在"socket 缓存"一节中详细介绍.
当然, dev_alloc_skb 的返回值必须检查, snull 这样做了. 我们调用
printk_ratelimit 在抱怨失败之前, 但是. 每秒钟产生成百上千的控制台消息是完全陷
死系统和隐藏问题的真正源头的好方法; printk_ratelimit 帮助阻止这个问题, 通过在
有太多输出到了控制台时返回 0, 事情需要慢下来一点.
一旦有一个有效的 skb 指针, 通过调用 memcpy, 报文数据被拷贝到缓存区; skb_put 函
数更新缓存中的数据末尾指针并返回指向新建空间的指针.
如果你在编写一个高性能驱动, 为一个可以进行完全总线占据 I/O 的接口, 一个可能的
优化值得在此考虑下. 一些驱动在报文接收前分配 sokcet 缓存, 接着使接口将报文数据
直接放入 socket 缓存空间. 网络层通过在可 DMA 的空间( 如果你的设备设置了
NETIF_F_HIGHDMA 标志, 这个空间有可能在高端内存)中分配所有 socket 缓存来配合这
个策略. 这样避免了单独的填充 socket 缓存的拷贝操作, 但是需要小心缓存区的大小,
因为你无法提前知道进来的报文大小. change_mtu 方法的实现在这种情况下也重要, 因
为它允许驱动对最大报文大小改变作出响应.

网络层在搞懂报文的意思前需要清楚一些事情. 为此, dev 和 protocol 成员必须在缓存
向上传递前赋值. 以太网支持代码输出一个帮助函数( eth_type_trans ), 它发现一个合
适值来赋给 protocol. 接着我们需要指出校验和要如何进行或者已经在报文上完成
( snull 不需要做任何校验和 ). 对于 skb->ip_summed 可能的策略有:
CHECKSUM_HW
设备已经在硬件里做了校验. 一个硬件校验的例子使 APARC HME 接口.
CHECKSUM_NONE
校验和还没被验证, 必须由系统软件来完成这个任务. 这个是缺省的, 在新分配的
缓存中.
CHECKSUM_UNNECESSARY
不要做任何校验. 这是 snull 和 环回接口的策略.
你可能奇怪为什么校验和状态必须在这里指定, 当我们已经在我们的 net_device 结构的
特性成员中设置了标志. 答案是特性标志告诉内核我们的设备如何对待外出的报文. 它不
用于进入的报文, 相反, 进入报文必须单独标记.
最后, 驱动更新它的统计计数来记录收到一个报文。 统计结构由几个成员组成; 最重要
的是 rx_packet, rx_bytes, 和 tx_bytes, 分别含有收到的报文数目, 发送的数目, 和
发送的字节总数. 所有的成员在"统计信息"一节中完全描述.
报文接收的最后一步由 netif_rx 进行, 它递交 socket 缓存给上层. 实际上 netif_rx
返回一个整数; NET_RX_SUCCESS(0) 意思是报文成功接收; 任何其他值指示错误. 有 3
个返回值 (NET_RX_CN_LOW, NET_RX_CN_MOD, 和 NET_RX_CN_HIGH )指出网络子系统的递
增的拥塞级别; NET_RX_DROP 意思是报文被丢弃. 一个驱动在拥塞变高时可能使用这些值
来停止输送报文给内核, 但是, 实际上, 大部分驱动忽略从 netif_rx 的返回值. 如果你
在编写一个高带宽设备的驱动, 并且希望正确处理拥塞, 最好的办法是实现 NAPI, 我们
在快速讨论中断处理后讨论它.

文章来源:https://blog.csdn.net/liu1250836704/article/details/135510873
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。