linux协议栈中提供的报文发送函数有两个,一个是链路层提供给网络层的发包函数dev_queue_xmit()。另一个就是软中断发包函数直接调用的函数sch_direct_xmit()。
这两个函数最终都会调用dev_hard_start_xmit()来发送报文。
一、发送函数的调用关系:
二、发送过程中锁的使用:
1、队列策略缓存报文的队列使用 qdisc->q.lock来保护。使用函数qdisc_lock来返回锁。
static inline spinlock_t *qdisc_lock(struct Qdisc *qdisc)
{
return &qdisc->q.lock;
}
2、调用网卡驱动发送报文时,使用锁dev->dev_queue->_xmit_lock来保护,防止多个cpu核同时操作网络设备。使用宏HARD_TX_LOCK 来上锁。
dev->dev_queue->xmt_lock_owner 记录持有该锁的cpu id.
static inline void __netif_tx_lock(struct netdev_queue *txq,
int cpu)
{
spin_lock(&txq->_xmit_lock);
txq->xmit_lock_owner = cpu;
}
#define HARD_TX_LOCK(dev, txq, cpu) { \
if ((dev->features & NETIF_F_LLTX) == 0) { \
__netif_tx_lock(txq, cpu); \
} \
}
static inline void __netif_tx_lock(struct netdev_queue *txq, int cpu)
{
spin_lock(&txq->_xmit_lock);
//记录持有队列锁的cpu id。
txq->xmit_lock_owner = cpu;
}
三、发送函数详解
1、dev_hard_start_xmit()
调用该函数时必须持有dev->dev_queue->_xmit_lock锁。
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq)
{
/*获得发送设备的操作函数*/
const struct net_device_ops *ops = dev->netdev_ops;
int rc;
/*当GSO发送大数据时,如果设备硬件不支持GSO功能,
就需要使用软件来对大报文进行切割,
这时切割后的所有小报文就挂在skb->next后面。
进入该函数时,大报文一般还没经过GSO的切割,
skb->next = =NULL*/
if (likely(!skb->next))
{
/*如果注册有嗅探器来关注发送报文,
就拷贝一份报文给嗅探器*/
if (!list_empty(&ptype_all))
{
dev_queue_xmit_nit(skb, dev);
}
/*如果报文需要进行GSO软件分割*/
if (netif_needs_gso(dev, skb))
{
/*如果调用GSO分割大包失败,就丢弃报文*/
if (unlikely(dev_gso_segment(skb)))
{
goto out_kfree_skb;
}
/*经过GSO分割后的报文不止一个,
跳到处理GSO处进行处理*/
if (skb->next)
{
goto gso;
}
}
/*如果报文不需要GSO软件分割,直接发送*/
/*
* If device doesnt need skb->dst,
* release it right now while
* its hot in this cpu cache
*/
if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
{
skb_dst_drop(skb);
}
/*调用网络驱动提供的发送函数把报文发送出去*/
rc = ops->ndo_start_xmit(skb, dev);
if (rc == NETDEV_TX_OK)
{
txq_trans_update(txq);
}
return rc;
}
/*如果报文经过软件分割后,skb->next后挂着一串分割出的小包,
循环把挂着的小包发送出去*/
gso:
do
{
struct sk_buff *nskb = skb->next;
skb->next = nskb->next;
nskb->next = NULL;
/*
* If device doesnt need nskb->dst,
* release it right now while
* its hot in this cpu cache
*/
if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
{
skb_dst_drop(nskb);
}
rc = ops->ndo_start_xmit(nskb, dev);
if (unlikely(rc != NETDEV_TX_OK))
{
nskb->next = skb->next;
skb->next = nskb;
return rc;
}
txq_trans_update(txq);
if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
{
return NETDEV_TX_BUSY;
}
} while (skb->next);
/*在GSO软件分割报文时,原始skb的destructor 函数被替换成
释放一串报文的释放函数,一旦发送失败后
保证分割后的一串skb 都能释放掉。
现在一串skb 已经都发送成功了,
还原原始skb 的destructor函数,释放原始skb时使用
*/
skb->destructor = DEV_GSO_CB(skb)->destructor;
out_kfree_skb:
kfree_skb(skb);
return NETDEV_TX_OK;
}
2、sch_direct_xmit
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev, struct netdev_queue *txq,
spinlock_t *root_lock)
{
int ret = NETDEV_TX_BUSY;
/*调用该函数时队列策略的队列锁已经被锁了,现在解锁*/
/* And release qdisc */
spin_unlock(root_lock);
/*取得发送队列的锁*/
HARD_TX_LOCK(dev, txq, smp_processor_id());
/*如果发送队列已经开启并且发送队列没有僵死,
直接调用 dev_hard_start_xmit发送报文*/
if (!netif_tx_queue_stopped(txq) &&
!netif_tx_queue_frozen(txq))
{
ret = dev_hard_start_xmit(skb, dev, txq);
}
HARD_TX_UNLOCK(dev, txq);
spin_lock(root_lock);
switch (ret) {
/*如果发送报文成功,返回队列策略队列中缓存报文的个数*/
case NETDEV_TX_OK:
/* Driver sent out skb successfully */
ret = qdisc_qlen(q);
break;
/*如果没有获取到网络设备的发送锁,处理锁冲突*/
case NETDEV_TX_LOCKED:
/* Driver try lock failed */
ret = handle_dev_cpu_collision(skb, txq, q);
break;
/*否则,表明发送报文失败,这时把报文存入队列策略的发送队列中*/
default:
/* Driver returned NETDEV_TX_BUSY - requeue skb */
if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
printk(KERN_WARNING "BUG %s code %d qlen %d\n",
dev->name, ret, q->q.qlen);
ret = dev_requeue_skb(skb, q);
break;
}
if (ret && (netif_tx_queue_stopped(txq) ||
netif_tx_queue_frozen(txq)))
ret = 0;
return ret;
}
处理获取发送锁失败的函数:
static inline int handle_dev_cpu_collision(struct sk_buff *skb,
struct netdev_queue *dev_queue,
struct Qdisc *q)
{
int ret;
/*如果cpu 获取锁失败,并且该锁还被本cpu 占用,
表示发送队列已经死锁,丢弃报文并打印警告*/
if (unlikely(dev_queue->xmit_lock_owner ==
smp_processor_id()))
{
/*
* Same CPU holding the lock. It may be a transient
* configuration error, when hard_start_xmit() recurses. We
* detect it by checking xmit owner and drop the packet when
* deadloop is detected. Return OK to try the next skb.
*/
kfree_skb(skb);
if (net_ratelimit())
printk(KERN_WARNING "Dead loop on netdevice %s, "
"fix it urgently!\n", dev_queue->dev->name);
ret = qdisc_qlen(q);
}
/*锁被其他cpu锁占用,这时我们把报文
重新存入队列策略的发送队列,延迟发送*/
else {
/*
* Another cpu is holding lock, requeue & delay xmits for
* some time.
*/
__get_cpu_var(netdev_rx_stat).cpu_collision++;
ret = dev_requeue_skb(skb, q);
}
return ret;
}
发送失败后重新入队函数:
static inline int dev_requeue_skb(struct sk_buff *skb,
struct Qdisc *q)
{
//把skb 挂到 q->gs_skb上,下次优先发送
q->gso_skb = skb;
q->qstats.requeues++;
/* it's still part of the queue */
q->q.qlen++;
//重新调度队列策略
__netif_schedule(q);
return 0;
}
3、__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb,
struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
{
spinlock_t *root_lock = qdisc_lock(q);
int rc;
/*取得队列策略的锁*/
spin_lock(root_lock);
/*如果队列策略被显示的禁止,就丢弃报文*/
if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
{
kfree_skb(skb);
rc = NET_XMIT_DROP;
}
/*如果队列策略中没有等待发送的报文,发送该报文*/
else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))
{
__qdisc_update_bstats(q, skb->len);
if (sch_direct_xmit(skb, q, dev, txq, root_lock))
{
/*如果发送报文返回不为0,
表示队列策略队列中还存在等待发送的报文,
这时重新调度队列策略*/
__qdisc_run(q);
}
else
{
/*如果发送报文返回为0,
表示发送成功后队列策略中没有等待发送的报文,
这时清除队列策略的running 状态*/
clear_bit(__QDISC_STATE_RUNNING, &q->state);
}
rc = NET_XMIT_SUCCESS;
}
/*如果队列策略中有等待发送的报文,
就把该报文放入队列中,
调用qdisc_run来进行调度发送*/
else
{
rc = qdisc_enqueue_root(skb, q);
qdisc_run(q);
}
spin_unlock(root_lock);
return rc;
}
4、dev_queue_xmit
该函数是提供给协议栈中网络层调用的发送函数。
int dev_queue_xmit(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct netdev_queue *txq;
struct Qdisc *q;
int rc = -ENOMEM;
/*如果需要GSO,进行GSO分割处理*/
if (netif_needs_gso(dev, skb))
{
goto gso;
}
/*如果skb 是一串ip 分片报文,
并且网络硬件不支持自动重组操作,
就进行分片重组成一个报文
*/
/*如果失败,就丢弃报文*/
if (skb_has_frags(skb) &&
!(dev->features & NETIF_F_FRAGLIST) &&
__skb_linearize(skb))
{
goto out_kfree_skb;
}
/*如果报文分别放在多个页中,
并且网络硬件不支持 G/S功能,就进行重组*/
/*如果重组失败,就丢弃报文*/
if (skb_shinfo(skb)->nr_frags &&
(!(dev->features & NETIF_F_SG) ||
illegal_highdma(dev, skb)) &&
__skb_linearize(skb))
{
goto out_kfree_skb;
}
/*如果报文没有添写校验和*/
if (skb->ip_summed == CHECKSUM_PARTIAL)
{
/*设备传输层的指针*/
skb_set_transport_header(skb,
skb->csum_start - skb_headroom(skb));
/*并且网络设备不支持自动填写校验和就要由
软件来填写,如果软件填写失败,就丢弃报文*/
if (!dev_can_checksum(dev, skb) &&
skb_checksum_help(skb))
{
goto out_kfree_skb;
}
}
gso:
rcu_read_lock_bh();
/*根据报文来取得发送队列*/
txq = dev_pick_tx(dev, skb);
/*取得发送队列的队列策略*/
q = rcu_dereference(txq->qdisc);
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
/*如果队列策略实现了入队操作,
就调用__dev_xmit_skb进行发送*/
if (q->enqueue)
{
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
/*如果队列策略没有实现入队操作,表明发送队列长度为0,
*一般发送队列长度为0 的设备是网络虚拟设备,发送失败
*后不需要缓存*/
/*如果设备是up 状态,进行报文的发送*/
if (dev->flags & IFF_UP)
{
int cpu = smp_processor_id();
/*如果设备的发送队列锁的持有cpu 不是本cpu时*/
if (txq->xmit_lock_owner != cpu)
{
/*获取发送队列的锁*/
HARD_TX_LOCK(dev, txq, cpu);
/*如果发送队列时开启的*/
if (!netif_tx_queue_stopped(txq))
{
rc = NET_XMIT_SUCCESS;
/*调用 dev_hard_start_xmit发送报文*/
if (!dev_hard_start_xmit(skb, dev, txq))
{
HARD_TX_UNLOCK(dev, txq);
goto out;
}
}
HARD_TX_UNLOCK(dev, txq);
/*如果发送队列被关闭,这时就要丢弃报文*/
if (net_ratelimit())
{
printk(KERN_CRIT "Virtual device %s asks to "
queue packet!\n", dev->name);
}
}
else
{
if (net_ratelimit())
{
printk(KERN_CRIT "Dead loop on virtual device "
"%s, fix it urgently!\n", dev->name);
}
}
}
rc = -ENETDOWN;
rcu_read_unlock_bh();
out_kfree_skb:
kfree_skb(skb);
return rc;
out:
rcu_read_unlock_bh();
return rc;
}
EXPORT_SYMBOL(dev_queue_xmit);
转载于:https://blog.51cto.com/yaoyang/1359420