当前位置:   article > 正文

【C语言】linux内核ipoib模块 - ipoib_start_xmit

【C语言】linux内核ipoib模块 - ipoib_start_xmit

一、ipoib_start_xmit函数定义

  1. static netdev_tx_t ipoib_start_xmit(struct sk_buff *skb, struct net_device *dev)
  2. {
  3. struct ipoib_dev_priv *priv = ipoib_priv(dev);
  4. struct rdma_netdev *rn = netdev_priv(dev);
  5. struct ipoib_neigh *neigh;
  6. struct ipoib_pseudo_header *phdr;
  7. struct ipoib_header *header;
  8. unsigned long flags;
  9. phdr = (struct ipoib_pseudo_header *) skb->data;
  10. skb_pull(skb, sizeof(*phdr));
  11. header = (struct ipoib_header *) skb->data;
  12. if (unlikely(phdr->hwaddr[4] == 0xff)) {
  13. /* multicast, arrange "if" according to probability */
  14. if ((header->proto != htons(ETH_P_IP)) &&
  15. (header->proto != htons(ETH_P_IPV6)) &&
  16. (header->proto != htons(ETH_P_ARP)) &&
  17. (header->proto != htons(ETH_P_RARP)) &&
  18. (header->proto != htons(ETH_P_TIPC))) {
  19. /* ethertype not supported by IPoIB */
  20. ++dev->stats.tx_dropped;
  21. dev_kfree_skb_any(skb);
  22. return NETDEV_TX_OK;
  23. }
  24. /* Add in the P_Key for multicast*/
  25. phdr->hwaddr[8] = (priv->pkey >> 8) & 0xff;
  26. phdr->hwaddr[9] = priv->pkey & 0xff;
  27. neigh = ipoib_neigh_get(dev, phdr->hwaddr);
  28. if (likely(neigh))
  29. goto send_using_neigh;
  30. ipoib_mcast_send(dev, phdr->hwaddr, skb);
  31. return NETDEV_TX_OK;
  32. }
  33. /* unicast, arrange "switch" according to probability */
  34. switch (header->proto) {
  35. case htons(ETH_P_IP):
  36. case htons(ETH_P_IPV6):
  37. case htons(ETH_P_TIPC):
  38. neigh = ipoib_neigh_get(dev, phdr->hwaddr);
  39. if (unlikely(!neigh)) {
  40. neigh = neigh_add_path(skb, phdr->hwaddr, dev);
  41. if (likely(!neigh))
  42. return NETDEV_TX_OK;
  43. }
  44. break;
  45. case htons(ETH_P_ARP):
  46. case htons(ETH_P_RARP):
  47. /* for unicast ARP and RARP should always perform path find */
  48. unicast_arp_send(skb, dev, phdr);
  49. return NETDEV_TX_OK;
  50. default:
  51. /* ethertype not supported by IPoIB */
  52. ++dev->stats.tx_dropped;
  53. dev_kfree_skb_any(skb);
  54. return NETDEV_TX_OK;
  55. }
  56. send_using_neigh:
  57. /* note we now hold a ref to neigh */
  58. if (ipoib_cm_get(neigh)) {
  59. if (ipoib_cm_up(neigh)) {
  60. priv->fp.ipoib_cm_send(dev, skb, ipoib_cm_get(neigh));
  61. goto unref;
  62. }
  63. } else if (neigh->ah && neigh->ah->valid) {
  64. neigh->ah->last_send = rn->send(dev, skb, neigh->ah->ah,
  65. IPOIB_QPN(phdr->hwaddr));
  66. goto unref;
  67. } else if (neigh->ah) {
  68. neigh_refresh_path(neigh, phdr->hwaddr, dev);
  69. }
  70. if (skb_queue_len(&neigh->queue) < IPOIB_MAX_PATH_REC_QUEUE) {
  71. spin_lock_irqsave(&priv->lock, flags);
  72. /*
  73. * to avoid race with path_rec_completion check if it already
  74. * done, if yes re-send the packet, otherwise push the skb into
  75. * the queue.
  76. * it is safe to check it here while priv->lock around.
  77. */
  78. if (neigh->ah && neigh->ah->valid)
  79. if (!ipoib_cm_get(neigh) ||
  80. (ipoib_cm_get(neigh) && ipoib_cm_up(neigh))) {
  81. spin_unlock_irqrestore(&priv->lock, flags);
  82. goto send_using_neigh;
  83. }
  84. push_pseudo_header(skb, phdr->hwaddr);
  85. __skb_queue_tail(&neigh->queue, skb);
  86. spin_unlock_irqrestore(&priv->lock, flags);
  87. } else {
  88. ++dev->stats.tx_dropped;
  89. dev_kfree_skb_any(skb);
  90. }
  91. unref:
  92. ipoib_neigh_put(neigh);
  93. return NETDEV_TX_OK;
  94. }

二、函数解读

该函数 ipoib_start_xmit 是用于 IP over InfiniBand (IPoIB) 模式的 InfiniBand 内核网络栈处理发送网络数据包的标准入口点。该函数的职责是准备并发送一个 socket buffer(`skb`),该 buffer 包含了要发送的网络数据。以下是该函数的详细中文解读:
1. ipoib_start_xmit 函数接收两个参数:
   - skb:一个指向数据包(socket buffer)的指针,这个数据包即将被发送。
   - dev:一个指向相关网络设备 (net_device) 的指针。
2. 函数首先通过调用 ipoib_priv(dev) 来获取这个网络设备的 IPoIB 私有数据结构 ipoib_dev_priv。
3. 函数通过 skb->data 获取 IPoIB 伪头部 (ipoib_pseudo_header),然后通过 skb_pull 函数将数据包指针向前移动,跳过伪头部,指向实际的 IPoIB 头部 (ipoib_header)。
4. 通过分析伪头部和 IPoIB 头部的内容,函数会检测数据包是单播还是多播。如果是多播,并且协议类型不被 IPoIB 支持,则丢弃此数据包并返回。
5. 对于多播数据包,函数添加 P_Key,并获取或创建一个邻居(neighbor)条目。如果获取邻居条目成功,则跳转到 send_using_neigh 标签来发送数据包;否则,通过 ipoib_mcast_send 函数发送多播数据包。
6. 对于单播数据包,根据协议头部中的协议类型,函数确定如何处理数据包。如果是 IP、IPv6 或 TIPC 协议,函数尝试获取一个邻居条目。如果没有找到,则通过 neigh_add_path 函数添加一个路径,并发送数据包。对于 ARP 或 RARP 协议,函数使用 unicast_arp_send 发送单播ARP请求。
7. 来到 send_using_neigh 标签,如果获取到邻居并确定了有效的通信路径,根据邻居条目的状态(是否启用了连接管理 CM 或者是否有有效的地址句柄 AH),发送数据包或者将数据包加入发送队列,等待有效通信路径建立。
8. 如果邻居的发送队列已满,那么数据包会被丢弃,并更新统计量 dev->stats.tx_dropped。
9. 在引用了一个邻居条目,并处理完后,通过调用 ipoib_neigh_put 函数减少邻居条目的引用计数。
10. 最后,函数返回 NETDEV_TX_OK,表示数据包的发送过程已经被正确处理,不管是直接发送、入队列等待,还是被丢弃。
总的来说,`ipoib_start_xmit` 函数处理从 IPoIB 网络设备发出的网络数据包的发送。函数会根据目标地址是单播还是多播以及数据包的类型,采取适当的发送策略,并处理数据路径查找和邻居管理等任务。

 三、中文注释

  1. static netdev_tx_t ipoib_start_xmit(struct sk_buff *skb, struct net_device *dev)
  2. {
  3. struct ipoib_dev_priv *priv = ipoib_priv(dev); // 获取IPoIB设备私有数据结构
  4. struct rdma_netdev *rn = netdev_priv(dev); // 获取RDMA网络设备私有数据结构
  5. struct ipoib_neigh *neigh;
  6. struct ipoib_pseudo_header *phdr; // 声明一个伪头部变量
  7. struct ipoib_header *header; // IPoIB协议头部结构
  8. unsigned long flags;
  9. phdr = (struct ipoib_pseudo_header *) skb->data; // 获取skb中的伪头部指针
  10. skb_pull(skb, sizeof(*phdr)); // 移除skb中的伪头部
  11. header = (struct ipoib_header *) skb->data; // 获取去掉伪头部后的具体协议头部指针
  12. if (unlikely(phdr->hwaddr[4] == 0xff)) { // 如果是组播地址
  13. /* 组播:根据概率调整下列 "if" 语句 */
  14. // 判断数据包协议是否为IPoIB支持,不支持的话,则drop
  15. if ((header->proto != htons(ETH_P_IP)) &&
  16. (header->proto != htons(ETH_P_IPV6)) &&
  17. (header->proto != htons(ETH_P_ARP)) &&
  18. (header->proto != htons(ETH_P_RARP)) &&
  19. (header->proto != htons(ETH_P_TIPC))) {
  20. /* IPoIB不支持的以太类型 */
  21. ++dev->stats.tx_dropped; // 统计丢包数
  22. dev_kfree_skb_any(skb); // 释放skb
  23. return NETDEV_TX_OK;
  24. }
  25. /* 组播时加入P_Key */
  26. phdr->hwaddr[8] = (priv->pkey >> 8) & 0xff;
  27. phdr->hwaddr[9] = priv->pkey & 0xff;
  28. neigh = ipoib_neigh_get(dev, phdr->hwaddr); // 获取组播邻居
  29. if (likely(neigh))
  30. goto send_using_neigh; // 如果邻居存在,跳转到使用邻居发送的代码部分
  31. ipoib_mcast_send(dev, phdr->hwaddr, skb);
  32. return NETDEV_TX_OK;
  33. }
  34. /* 单播:根据概率调整下列 "switch" 语句 */
  35. // 判断skb中的具体协议,执行相应操作
  36. switch (header->proto) {
  37. case htons(ETH_P_IP):
  38. case htons(ETH_P_IPV6):
  39. case htons(ETH_P_TIPC):
  40. neigh = ipoib_neigh_get(dev, phdr->hwaddr); // 获取单播邻居
  41. if (unlikely(!neigh)) { // 如果邻居未知,则尝试添加路径
  42. neigh = neigh_add_path(skb, phdr->hwaddr, dev);
  43. if (likely(!neigh))
  44. return NETDEV_TX_OK;
  45. }
  46. break;
  47. case htons(ETH_P_ARP):
  48. case htons(ETH_P_RARP):
  49. /* 对于单播ARP和RARP,应始终执行路径查找 */
  50. unicast_arp_send(skb, dev, phdr);
  51. return NETDEV_TX_OK;
  52. default:
  53. /* IPoIB不支持的以太类型 */
  54. ++dev->stats.tx_dropped;
  55. dev_kfree_skb_any(skb);
  56. return NETDEV_TX_OK;
  57. }
  58. send_using_neigh:
  59. /* 注意此时我们持有一个邻居的引用 */
  60. if (ipoib_cm_get(neigh)) { // 检查连接管理是否激活
  61. if (ipoib_cm_up(neigh)) { // 检查连接管理是否已经建立
  62. priv->fp.ipoib_cm_send(dev, skb, ipoib_cm_get(neigh)); // 通过连接管理发送
  63. goto unref; // 跳过后续步骤
  64. }
  65. } else if (neigh->ah && neigh->ah->valid) { // 如果地址句柄有效
  66. neigh->ah->last_send = rn->send(dev, skb, neigh->ah->ah,
  67. IPOIB_QPN(phdr->hwaddr)); // 通过RDMA发送
  68. goto unref; // 跳过后续步骤
  69. } else if (neigh->ah) { // 如果地址句柄无效,刷新路径
  70. neigh_refresh_path(neigh, phdr->hwaddr, dev);
  71. }
  72. if (skb_queue_len(&neigh->queue) < IPOIB_MAX_PATH_REC_QUEUE) { // 检查队列深度
  73. spin_lock_irqsave(&priv->lock, flags); // 加锁
  74. /* 防止与path_rec_completion竞争,首先检查邻居相关操作是否完成,安全地检查 */
  75. if (neigh->ah && neigh->ah->valid)
  76. if (!ipoib_cm_get(neigh) ||(ipoib_cm_get(neigh) && ipoib_cm_up(neigh))) { // 如果地址句柄有效或者已经上线
  77. spin_unlock_irqrestore(&priv->lock, flags); // 解锁
  78. goto send_using_neigh; // 再次尝试发送
  79. }
  80. push_pseudo_header(skb, phdr->hwaddr); // 重新添加伪头部
  81. __skb_queue_tail(&neigh->queue, skb); // 将skb加入到邻居的队列尾部
  82. spin_unlock_irqrestore(&priv->lock, flags); // 解锁
  83. } else {
  84. ++dev->stats.tx_dropped; // 队列过长则丢弃数据包
  85. dev_kfree_skb_any(skb); // 释放skb
  86. }
  87. unref:
  88. ipoib_neigh_put(neigh); // 释放邻居的引用
  89. return NETDEV_TX_OK; // 返回发送成功状态
  90. }

四、伪头部 (pseudo header)

在网络通信中,特别是在针对特定网络协议的实现时,伪头部 (pseudo header) 通常是一种临时构造的数据结构,它包含在数据包的开始部分以方便包处理。伪头部不是数据包实际的一部分,而是一种为网络协议处理(如校验和的计算)附加的数据结构。伪头部通常模拟了数据包可能具有的实际头部结构,为软件处理提供了上下文信息。
在IP over InfiniBand (IPoIB) 的上下文中,伪头部可能是指添加到socket缓冲区(sk_buff)的开始部分的特定信息。这通常是为了存储处理IPoIB时需要的附加信息,例如宛地址的硬件地址(也称为MAC地址或链路层地址)。一旦完成了这些附加信息的处理,伪头部就会被从sk_buff中移除,数据包可以继续在协议栈中向下传递。
在`ipoib_start_xmit()`函数中,伪头部用于在skb数据包结构体中存储有关目标地址硬件信息的一些元数据。在实际发送之前,这个伪头部会从skb中移除:

  1. phdr = (struct ipoib_pseudo_header *) skb->data; // 获取skb中的伪头部指针
  2. skb_pull(skb, sizeof(*phdr)); // 移除skb中的伪头部

这里的`skb_pull()`函数用于调整sk_buff的data指针,移除伪头部结构,使得data指针指向实际的网络数据。
在计算网络数据的校验和或者处理其他需要源地址和目的地址信息的协议操作时,伪头部特别有用。对于需要此类信息而在原始封装中可能不包含的协议(如UDP和TCP的校验和计算中需要使用的IP头部信息),伪头部提供了必要的上下文。 

五、单播邻居

在InfiniBand (IB)网络和IP over InfiniBand (IPoIB)通信中,单播邻居表示目标节点的IP地址与其对应的InfiniBand唯一标识符(如全局唯一标识符GID)的映射关系。
当需要向特定的目标节点发送数据时,必须知道目的地的地址信息。这在IPoIB通信中尤其重要,因为数据包需要在InfiniBand层准确地被路由到正确的目的节点。为此,需要一个地址句柄(Address Handle, AH),它包含了转换后的IB目的地址和必要的路径信息来发送数据包。
因此,当要发送单播数据时,首先需要解析目标IP地址对应的IB硬件地址(比如GID),建立或更新地址句柄,这通常是通过对应的子网管理员(Subnet Manager, SM)解析或通过交换路径信息(Path Resolution Protocol, PRP)来完成的。这个解析出来的硬件地址和地址句柄的映射就是"单播邻居"关系。
如果找到了单播邻居,就可以通过之前建立好的地址句柄发送单播数据。如果找不到,可能需要发起一个解析过程来获得或更新地址句柄。在IPoIB实现中,通常会有缓存机制来回忆(记住)最近使用的地址句柄,以避免每次发送时都需要解析。
在ipib_start_xmit()函数中,如果是单播发送,首先尝试从已有的邻居信息中获取地址句柄(如果存在的话),如果没有找到,则可能触发一个路径解析过程来寻找并添加新的单播邻居。一旦邻居和它的地址句柄被确认或建立,就可以进行单播数据发送。

六、回调函数指针rn->send

在 InfiniBand的IP over IB (IPoIB) 实现中,`rn->send`是一个函数指针,它是`rdma_netdev`里的一个回调函数,用于实际发送数据。这个回调函数的具体行为会依赖于使用的传输模式,它可以是连接模式(Connected Mode, CM)或无连接模式(Datagram mode)。
在代码中,`rn->send`被设置为指向一个函数,实际发送数据包的具体实现依赖于RDMA驱动。这是一种常见的设计模式,允许不同的后端实现在不修改上层代码的情况下被插入。在IPoIB的上下文中,`rn->send`可能被设置为`ipoib_send`函数,或者如果启用了IPoIB的连接模式,可能是`ipoib_cm_send`。
当实际注册`rn->send`函数时,这通常发生在Network Device的初始化阶段,也就是IPoIB接口初始化时。不同的低层RDMA硬件和驱动可能会提供自己特定的实现来处理具体硬件上的包发送。如:

rn->send = &ipoib_send;

或者在连接模式下:

rn->send = &ipoib_cm_send;

真正的函数注册可能会依赖于所使用的支持RDMA的网卡和其相应的驱动,和上层代码可能完全不同。用户空间配置的变化以及所使用的内核版本都可能影响最终注册的函数。
在`ipoib_cm_send`函数中,该函数会确保连接已建立并且可用,然后会使用底层InfiniBand的verbs API(如`ib_post_send`)来在硬件上排队和传输数据。这样,数据传输过程就对上层透明,由具体后端的实现完成。当然,情况可能因不同的操作系统内核版本、RDMA驱动版本或IPoIB配置而异。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/169829
推荐阅读
相关标签
  

闽ICP备14008679号