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);