接口设备发包

//网络接口发送数据包(转发或由本地输出都会调用此接口函数)
dev_queue_xmit
  //检测当前报文是GSO数据包,同时物理设备不支此种GSO的分片聚合,或者当前报
  //文已经不需要物理设备进行校验和,则直接跳到gso标签,后面在dev_hard_start_xmit
  //中会进行软件实现GSO处理。
  rv = netif_needs_gso(dev, skb)
  //skb_is_gso 判断skb的shinfo中gso_size字段是否有值来确定当前是GSO包
  //skb_gso_ok 检测设备是否支持当前gso包类型(gso可以有UDP、TCP等几种)
  //skb->ip_summed != CHECKSUM_PARTIAL	表明该包软件实现校验和
  return skb_is_gso(skb) && (!skb_gso_ok(skb, dev->features) ||
  unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
  if (rv == true)
  goto gso;

  //如果当前报文有多个frag_list组成,并且当前设备不支持多段处理,则需要使用
  //__skb_linearize进行线性化,也就是需要将多个段数据和入到一个单独的skb中
  //如果__skb_linearize处理失败,该包需要丢弃,这里失败原因比如说创建一个大的
  //skb时没有足够内存资源等。
  if (skb_shinfo(skb)->frag_list &&!(dev->features & NETIF_F_FRAGLIST) &&
  __skb_linearize(skb))
  goto out_kfree_skb;

  //如果当前报文由多个段组成,如果当前设备不支持分散聚合,或者当前段使用的内存
  //页在高端内存区但当前设备不支持高端内存区的DMZ处理,则需要进行线性化,如
  //果线性化失败,则丢弃。
  if (skb_shinfo(skb)->nr_frags &&(!(dev->features & NETIF_F_SG) || 
  illegal_highdma(dev, skb)) && __skb_linearize(skb))
  goto out_kfree_skb;

  //如果当前报文需要硬件设备进行校验和,
  //但当前设备不支持任何校验和处理,或者当前设备不支持IP校验和,或者当前设备
  //支持IP校验和可是当前报文不是IP报文。
  //则需要进行软件校验和处理,
  if (skb->ip_summed == CHECKSUM_PARTIAL &&
  (!(dev->features & NETIF_F_GEN_CSUM) &&(!(dev->features & NETIF_F_IP_CSUM) ||
  skb->protocol != htons(ETH_P_IP))))
  if (skb_checksum_help(skb))
  goto out_kfree_skb;
  
  gso:
  
  spin_lock_prefetch(&dev->queue_lock);
  rcu_read_lock_bh();
  
  //QOS出口队列
  q = rcu_dereference(dev->qdisc);
  
  //如果当前关闭QOS队列(通常真实设备都有QOS队列,仅虚拟设备在发包时可以
  //不需要走QOS队列)
  if (q->enqueue)
  spin_lock(&dev->queue_lock);
  
  q = dev->qdisc;
  if (q->enqueue)
  //将报文加入QOS队列,后面单独对QOS相关源码进行分析。
  rc = q->enqueue(skb, q);
  
  qdisc_run(dev);
  //如果传送队列没有关闭,同时当前QOS传送调度还未运行,则
  //进行QOS传送调度处理,同时将当前设备状态标记上正在进行
  //QOS调度处理__LINK_STATE_QDISC_RUNNING。
  if (!netif_queue_stopped(dev) &&
  !test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))
  __qdisc_run(dev);
  //如果当前QOS队列还未正确初始化,则noop_qdisc为无效
  //队列类型,不进行队列处理。
  if (unlikely(dev->qdisc == &noop_qdisc))
  goto out;
  
  //调用qdisc_restart进行队列处理,返回值为负数表示队列中
  //还有数据,但设备发送阀值已经上限,同时当该队列还未
  //停止时,则循环进行处理。
  while (qdisc_restart(dev) < 0 && !netif_queue_stopped(dev))
  /* NOTHING */;
out:
  //标记已经不在进行QOS调度处理。
  clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);

  spin_unlock(&dev->queue_lock);
  
  rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;
  goto out;
  
  spin_unlock(&dev->queue_lock);
  
  //当前没有开启QOS队列则走如下流程
  
  //设备必须为开启才处理报文
  if (dev->flags & IFF_UP)
  cpu = smp_processor_id();
  
  //当前CPU还未使用该设备发送报文
  if (dev->xmit_lock_owner != cpu)
  //该宏会检测当前设备是否支持无发送锁操作(NETIF_F_LLTX),如果设备
  //不支持,就需要获取发送锁,同时设置xmit_lock_owner为当前CPU
  HARD_TX_LOCK(dev, cpu);
  
  //如果当前发送队列没有被关闭,则进行传输
  //test_bit(__LINK_STATE_XOFF, &dev->state);
  if (!netif_queue_stopped(dev))
  Rv = dev_hard_start_xmit(skb, dev)
  //单个skb报文则走此流程
  if (likely(!skb->next))
  //netdev_nit变量为正是当用户创建AF_PACKET套接口是增加
  //的,该套接口主要用于像TCPDUMP之类的监听软件使用,这
  //就会把当前要发出的报文给该类监听软件传送一份。接收报文
  //也有类似处理,可以参见收包处理的分析。
  if (netdev_nit)
  dev_queue_xmit_nit(skb, dev);
  
  //如果当前报文是GSO类型,但当前设备不支持该特性处理,
  //则在此使用软件拆分来实现。netif_needs_gso在上面有分析。
  if (netif_needs_gso(dev, skb))
  //进行软件拆分处理
  if (unlikely(dev_gso_segment(skb)))
  //失败则丢弃报文
  goto out_kfree_skb;
  
  //通常拆分后会将有效拆分报文链接到原报文之后,这里
  //判断如果拆分成功,则往向gso标签去处理,如果没有拆
  //分成功,则将当前单个skb报文通过网卡发送出去。
  if (skb->next)
  goto gso;
  
  //将当前单个skb报文通过设备发送回调发送出去。
  return dev->hard_start_xmit(skb, dev);
  
  gso:
  //当报文原本由多个skb链接组成,或者此报文是gso类型,硬件不
  //支持这种处理,借助软件交报文拆分成了多个skb链接,则走此处
  //理流程。
  
  do {
  nskb = skb->next;
  skb->next = nskb->next;
  nskb->next = NULL;
  
  //调用设备回调将分段报文一个个送出。
  rc = dev->hard_start_xmit(nskb, dev);
  
  //如果出错,则将分段报文在放入链表,返回错误
  if (unlikely(rc))
  nskb->next = skb->next;
  skb->next = nskb;
  return rc;
  
  //如果当前传送队列停止,还有未分送的报文,则返回BUSY
  if (unlikely(netif_queue_stopped(dev) && skb->next))
  return NETDEV_TX_BUSY;
  		} while (skb->next);
  
  //之前在dev_gso_segment进行GSO类型报文进行拆分时,skb的释
  //放回调已经改变,同时将原释放回调存储到了
  //DEV_GSO_CB(skb)->destructor中,这里进行还原
  skb->destructor = DEV_GSO_CB(skb)->destructor;
  
  //这个是最原始的GSO类型报文,在进行拆分时,是将新的报文链
  //接到该原始报文的后面,同时在进行循环调用设备接口发送时,也
  //是将该原始报文之后的链接报文发送,到达这里时,原始报文已经
  //没有作用,拆分的报文已经通过设备回调发出,这里把原始报文使
  //用原始的释放回调进行释放。
  out_kfree_skb:
  kfree_skb(skb);
  return 0;
  
  //正常传输完成
  If( rv == 0 )
  HARD_TX_UNLOCK(dev);
  goto out;
  
  //如果传送队列没有开启,或者使用dev_hard_start_xmit传送失败,则报
  //文会被丢弃,仅上面使用QOS队列的情况时,传送失败才会重新入队
  HARD_TX_UNLOCK(dev);
  else
  //打印错误,因为一个CPU在给使用该设备发送报文时,不会再次进入,否则
  //就有BUG,进行打印
  
  //其它情况将返回错误,报文丢弃
  rc = -ENETDOWN;
  rcu_read_unlock_bh();
out_kfree_skb:
  kfree_skb(skb);
  return rc;
  
  //正常退出
out:
  rcu_read_unlock_bh();
  return rc;
  
-------------------------------------------------------------------------------------------------------------------
//将原始的GSO报文使用软件分割成多个skb报文链表,并挂接到原始skb之后。
dev_gso_segment(skb)
  //illegal_highdma函数判断如果当前设备不支持高端内存DMZ处理,并且当前的
  //skb链表中有处于高端内存页的报文段,则返回为1,表明此GSO没办法通过
  //硬件来处理了。这样的话即使设备的dev->features特性标有支持分散聚合,在
  //临时变量features中也需要将该特性标记去除。
  features = dev->features & ~(illegal_highdma(dev, skb) ?NETIF_F_SG : 0);
  
  //进行软件分割处理
  segs = skb_gso_segment(skb, features);
  skb->mac.raw = skb->data;
  skb->mac_len = skb->nh.raw - skb->data;
  __skb_pull(skb, skb->mac_len);
  //skb的data指针指向三层报文头,同时len也减去二层头长度
  
  //如果报文IP需要软件实现校验和,同时该报文被克隆,则必须对报文线性
  //数据进行复制。当复制出错后,返回错误。
  if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL))
  if (skb_header_cloned(skb) &&
  (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
  //当复制出错后,返回错误
  return ERR_PTR(err);
  
  rcu_read_lock();
  
  //遍历当前LINUX二层负载所支持的所有包类型(如IP、VLAN等)
  list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & 15], list)
  //当前报文的二层负载类型与列表中支持的包类型匹配,同时该包类型还未
  //进行处理,同时该包类型支持GSO分段。
  if (ptype->type == type && !ptype->dev && ptype->gso_segment)
  //该报需要软件实现校验和
  if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL))
  //对二层负载以上层调用gso_send_check进行有效处理检测,
  //比如检测线性空间是否还可以存储对应头部,以及上层协议
  //是否支持GSO(如三层IP支持,但再往上四层UDP就不支持)
  err = ptype->gso_send_check(skb);
  //如果检测出错,或者硬件本身支持此种类型的GSO处理,直接返
  //回
  if (err || skb_gso_ok(skb, features))
  break;
  
  //经过上层协议调用gso_send_check处理,skb的data及len字段
  //已经变动,这里再恢复到ip头的位置
  __skb_push(skb, skb->data - skb->nh.raw);

  //调用上层的GSO分段函数进行拆分处理。未详细分析。
  segs = ptype->gso_segment(skb, features);
  break;
  
  rcu_read_unlock();
  
  //将skb中的data及len恢复到二层头。
  __skb_push(skb, skb->data - skb->mac.raw);
  
  return segs;
  
  //处理成功,没有进行分割,直接返回
  if (!segs)
  Return 0;
  
  //分割出错
  if (unlikely(IS_ERR(segs)))
  return PTR_ERR(segs);
  
  //分割成功,将分割的报文链表插入到原始GSO报文之后,同时将原始报文的释放
  //回调替换成dev_gso_skb_destructor,原始报文的原释放回调保存到
  //DEV_GSO_CB(skb)->destructor,在报文发送成功后,该回调会还原回来。
  skb->next = segs;
  DEV_GSO_CB(skb)->destructor = skb->destructor;
  skb->destructor = dev_gso_skb_destructor;
  
  Return 0;
---------------------------------------------------------------------------------------------------------------------

//进行QOS队列调度处理
qdisc_restart
  //gso类型包直接从dev->gso_skb链表取,否则使用队列的dequeue出列回调进行提取
  if (((skb = dev->gso_skb)) || ((skb = q->dequeue(q))))
  //当前设备支持无锁传送特性
  nolock = (dev->features & NETIF_F_LLTX);
  
  dev->gso_skb = NULL;
  
  //设备不支持无锁传送,则软件进行获取发送锁
  if (!nolock)
  netif_tx_trylock(dev)
  
  spin_unlock(&dev->queue_lock);
  
  //传送队列未停止
  if (!netif_queue_stopped(dev))
  //进行数据发送,上面已经分析过该函数
  ret = dev_hard_start_xmit(skb, dev);
  
  //发送成功,返回-1,用于调用qdisc_restart的循环流程继续处理下面报文
  if (ret == NETDEV_TX_OK)
  if (!nolock)
  if (!nolock)
  spin_lock(&dev->queue_lock);
  return -1;
  
  //当前设备标识支持无锁传送,结果驱动又返回锁错误,则走到collision标签
  //进行错误处理,当前该标签相关源码未贴出来。
  if (ret == NETDEV_TX_LOCKED && nolock)
  spin_lock(&dev->queue_lock);
  goto collision;

  if (!nolock)
  netif_tx_unlock(dev);
  
  spin_lock(&dev->queue_lock);
  q = dev->qdisc;
  
  requeue:
  //到达这里表明发送队列未开启,或者驱动发送错误(锁错误除外),则将报文重
  //新放入队列。(使用非QOS队列机制的报文当出错后,报文会被丢弃,这种使用
  //QOS队列处理的机制有些不同)
  if (skb->next)
  dev->gso_skb = skb;
  else
  q->ops->requeue(skb, q);

  //触发发送报文软中断
  netif_schedule(dev);
  //当前未关闭发送
  if (!test_bit(__LINK_STATE_XOFF, &dev->state))
  __netif_schedule(dev);
  //当前还未进行发送调度则运行
  if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state))
  local_irq_save(flags);
  //将当前发包设备加入到output_queue发送队列中,同时触发
  //发送软中断。这里发送软中断任务函数为net_tx_action,这是
  //在net_dev_init初始化时设置的。
  sd = &__get_cpu_var(softnet_data);
  dev->next_sched = sd->output_queue;
  sd->output_queue = dev;
  raise_softirq_irqoff(NET_TX_SOFTIRQ);
  local_irq_restore(flags);
  
  return 1;
  
  //没有取到报文,返回队列长度。
  return q->q.qlen;
  
----------------------------------------------------------------------------------------------------------------------
//发送软中断任务函数,该函数比较常见的触发条件1、像该文档上文分析,在使用QOS队列发送报文,在队列未开启或驱动发送出错时,会
触发该软中断。
队列看门狗超时会触发该软中断。
有些设备驱动在发送完报文后,会有发送完成的中断,在硬中断中会触发该软中断。
net_tx_action
  //这里主要作用于第3种触发条件,当使用设备驱动发送报文时,不进行skb的删除
  //处理,仅在设备驱动发送完成后,放入completion_queue队列,由软中断任务来处
  //理这种不是很紧急释放事情。
  if (sd->completion_queue)
  local_irq_disable();
  clist = sd->completion_queue;
  sd->completion_queue = NULL;
  local_irq_enable();
  
  while (clist)
  skb = clist;
  clist = clist->next;
  
  __kfree_skb(skb);
  
  if (sd->output_queue)
  local_irq_disable();
  head = sd->output_queue;
  sd->output_queue = NULL;
  local_irq_enable();
  
  while (head)
  dev = head;
  head = head->next_sched;
  
  //内存栅栏,保证前后指令不会因为CPU流水线机制打乱。
  smp_mb__before_clear_bit();
  
  //清除发送软中断任务正在调度标记
  clear_bit(__LINK_STATE_SCHED, &dev->state);
  
  //获取队列锁成功后,则使用qdisc_run再进行QOS队列处理,该函数上面
  //已经分析过。否则重新调度发送软中断任务。
  if (spin_trylock(&dev->queue_lock))
  qdisc_run(dev);
  spin_unlock(&dev->queue_lock);
  else
  netif_schedule(dev);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值