//网络接口发送数据包(转发或由本地输出都会调用此接口函数)
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);
接口设备发包
最新推荐文章于 2023-10-17 23:19:10 发布