一、中文注释
int ipoib_send(struct net_device *dev, struct sk_buff *skb,
struct ib_ah *address, u32 dqpn)
{
struct ipoib_dev_priv *priv = ipoib_priv(dev); // 获取IPoIB设备的私有数据
struct ipoib_tx_buf *tx_req; // 发送请求结构体
int hlen, rc; // 分别为头部长度和返回码
void *phead; // 要发送数据包的头部指针
unsigned int usable_sge = priv->max_send_sge - !!skb_headlen(skb); // 计算可用的Scatter/Gather Element的数量
// 检查是否为GSO分段传输的skb
if (skb_is_gso(skb)) {
hlen = skb_transport_offset(skb) + tcp_hdrlen(skb); // 计算GSO的头部长度
phead = skb->data; // 获取skb数据的头部指针
// 如果skb_pull返回错误,则skb数据区太小
if (unlikely(!skb_pull(skb, hlen))) {
ipoib_warn(priv, "linear data too small\n");
++dev->stats.tx_dropped; // 统计丢弃的包
++dev->stats.tx_errors; // 统计错误数
dev_kfree_skb_any(skb); // 释放skb
return -1;
}
// 非GSO传输的处理
} else {
// 如果skb的长度过长,则丢弃
if (unlikely(skb->len > priv->mcast_mtu + IPOIB_ENCAP_LEN)) {
ipoib_warn(priv, "packet len %d (> %d) too long to send, dropping\n",
skb->len, priv->mcast_mtu + IPOIB_ENCAP_LEN);
++dev->stats.tx_dropped; // 统计丢弃的包
++dev->stats.tx_errors; // 统计错误数
ipoib_cm_skb_too_long(dev, skb, priv->mcast_mtu); // 处理过长的skb
return -1;
}
phead = NULL; // 无需处理头部
hlen = 0; // 头部长度为0
}
// 检查是否存在太多的skb碎片
if (skb_shinfo(skb)->nr_frags > usable_sge) {
// 尝试线性化skb,如果有错误则丢弃
if (skb_linearize(skb) < 0) {
ipoib_warn(priv, "skb could not be linearized\n");
++dev->stats.tx_dropped; // 统计丢弃的包
++dev->stats.tx_errors; // 统计错误数
dev_kfree_skb_any(skb); // 释放skb
return -1;
}
// 即使线性化后,碎片数仍然过多则丢弃
if (skb_shinfo(skb)->nr_frags > usable_sge) {
ipoib_warn(priv, "too many frags after skb linearize\n");
++dev->stats.tx_dropped; // 统计丢弃的包
++dev->stats.tx_errors; // 统计错误数
dev_kfree_skb_any(skb); // 释放skb
return -1;
}
}
ipoib_dbg_data(priv,
"sending packet, length=%d address=%p dqpn=0x%06x\n", // 调试信息:将要发送的数据包信息
skb->len, address, dqpn);
// 使用tx_ring队列缓存skb,在确保所有记录和状态一致后调用post_send()
tx_req = &priv->tx_ring[priv->tx_head & (priv->sendq_size - 1)];
tx_req->skb = skb;
// 如果skb长度小于内联阈值并且没有分段,则进行内联发送
if (skb->len < ipoib_inline_thold &&
!skb_shinfo(skb)->nr_frags) {
tx_req->is_inline = 1;
priv->tx_wr.wr.send_flags |= IB_SEND_INLINE;
} else {
// 处理DMA映射
if (unlikely(ipoib_dma_map_tx(priv->ca, tx_req))) {
++dev->stats.tx_errors; // 统计错误数
dev_kfree_skb_any(skb); // 释放skb
return -1;
}
tx_req->is_inline = 0;
priv->tx_wr.wr.send_flags &= ~IB_SEND_INLINE;
}
// 设置校验和
if (skb->ip_summed == CHECKSUM_PARTIAL)
priv->tx_wr.wr.send_flags |= IB_SEND_IP_CSUM;
else
priv->tx_wr.wr.send_flags &= ~IB_SEND_IP_CSUM;
// 如果发送队列满了,则暂停网络队列
if (atomic_read(&priv->tx_outstanding) == priv->sendq_size - 1) {
ipoib_dbg(priv, "TX ring full, stopping kernel net queue\n");
netif_stop_queue(dev); // 停止内核网络队列
}
skb_orphan(skb); // 取消与所有者的关联
skb_dst_drop(skb); // 丢弃路由缓存项
// 如果网络队列停止,则请求在发送完CQ后通知,以便我们可以唤醒它
if (netif_queue_stopped(dev))
if (ib_req_notify_cq(priv->send_cq, IB_CQ_NEXT_COMP |
IB_CQ_REPORT_MISSED_EVENTS) < 0)
ipoib_warn(priv, "request notify on send CQ failed\n");
rc = post_send(priv, priv->tx_head & (priv->sendq_size - 1), // 调用post_send函数发送数据包
address, dqpn, tx_req, phead, hlen);
if (unlikely(rc)) {
ipoib_warn(priv, "post_send failed, error %d\n", rc); // 如果post_send出现错误,记录错误信息
++dev->stats.tx_errors; // 统计错误数
if (!tx_req->is_inline)
ipoib_dma_unmap_tx(priv, tx_req); // 如果不是内联则释放DMA映射
dev_kfree_skb_any(skb); // 释放skb
if (netif_queue_stopped(dev)) // 如果网络队列停止,则唤醒
netif_wake_queue(dev);
rc = 0; // 将返回码设置为0
} else {
netif_trans_update(dev); // 更新最后一个传输的时间戳
rc = priv->tx_head; // 设置返回码为当前的tx_head
++priv->tx_head; // tx_head自增,为下一次发送做准备
atomic_inc(&priv->tx_outstanding); // 增加未决的发送操作计数
}
return rc; // 返回操作结果,成功发送则返回tx_head,失败返回0
}
这个函数是一个Linux内核的网络设备发送函数,用于IP over InfiniBand (IPoIB) 模块。它负责准备并发送用户数据包(skb),并操作网络设备统计信息和发送队列。当不能正确发送或者处理数据包时,该函数将适当地释放资源,并通过相应的返回码和内部统计来标识错误的发生。
二、中文讲解
这段代码是一个InfiniBand网络设备的发送函数,用于在IP over InfiniBand(IPoIB)网络中发送数据。以下是对该函数功能的中文讲解:
int ipoib_send(struct net_device *dev, struct sk_buff *skb, struct ib_ah *address, u32 dqpn)
函数`ipoib_send`有四个参数:
- dev表示要发送数据的网络设备。
- skb是一个指向socket缓冲区(数据包)的指针。
- address是远程端点的InfiniBand地址句柄。
- dqpn是目的端口号。
函数内部的变量说明:
- priv是设备的私有数据结构。
- tx_req表示用于发送的缓冲区。
- hlen是头部的长度,表示在何处开始线性化处理(如果是GSO(大尺寸传输优化)数据包)。
- rc是函数返回码。
- phead是指向数据头部的指针。
- usable_sge是扣除头部数据后剩余可用的分散/聚集元素(Scatter/Gather Elements)数量。
代码流程梳理:
1. 首先判断是不是GSO数据包,如果是,则记录下头部信息并处理skb缓冲区。如果skb太小无法处理,则丢弃并返回。
2. 如果不是GSO数据包,但skb长度超出最大传输单元(MTU),也会丢弃数据包并返回。
3. 检查数据包中的片段(frags)数量,如果超过可用的SGE数量,则尝试线性化skb。线性化失败或线性化后frags仍然太多,则丢弃数据包。
4. 设置传输请求结构`tx_req`并决定是否以内联方式发送数据。如果数据长度小于阈值且无frags,则设为内联。
5. 如果传输队列(TX ring)快满了,停止继续入队新的数据包。
6. 为数据包的发送作最后准备,包括释放相关资源,比如删除路由相关信息,并对发送队列和完成队列进行相关的操作。
7. 调用`post_send`来实际发送数据包。
8. 根据发送结果进行处理。如果失败,释放资源,并根据队列状态恢复传输队列或者记录错误。
这段代码的关键点在于管理数据包缓冲区,并且确保数据可以被有效的发送到InfiniBand网络。涉及到内存管理,异步通信,以及在发送之前后处理网络设备状态。