歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

dev_queue_xmi函數詳解

不得不說,ChinaUnix裡面的大神太多了。。。。。還沒細看先記錄下
前面在分析IPv6的數據流程時,當所有的信息都准備好了之後,例如,出口設備,下一跳的地址,以及鏈路層地址。就會調用dev.c文件中的dev_queue_xmin函數,該函數是設備驅動程序執行傳輸的接口。也就是所有的數據包在填充完成後,最終發送數據時,都會調用該函數。
Dev_queue_xmit函數只接收一個skb_buff結構作為輸入的值。此數據結構包含了此函數所需要的一切信息。Skb->dev是出口設備,skb->data為有效的載荷的開頭,其長度為skb->len.下面是2.6.37版本內核中的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;
/* Disable soft irqs for various locks below. Also
* stops preemption for RCU.
*/
//關閉軟中斷 - __rcu_read_lock_bh()--->local_bh_disable();
rcu_read_lock_bh();
// 選擇一個發送隊列,如果設備提供了select_queue回調函數就使用它,否則由內核選擇一個隊列,這裡只是Linux內核多隊列的實現,但是要真正的使用都隊列,需要網卡支持多隊列才可以,一般的網卡都只有一個隊列。在調用alloc_etherdev分配net_device是,設置隊列的個數
txq = dev_pick_tx(dev, skb);
//從netdev_queue結構上獲取設備的qdisc
q = rcu_dereference_bh(txq->qdisc);
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
//如果硬件設備有隊列可以使用,該函數由dev_queue_xmit函數直接調用或由dev_queue_xmit通過qdisc_run函數調用
trace_net_dev_queue(skb);
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq); //使用流控對象發送數據包(包含入隊和出隊)
//更詳細的內容參考說明3
goto out;
}
//下面的處理是在沒有發送隊列的情況下
/* The device has no queue. Common case for software
devices:
loopback, all the sorts of tunnels...
Really, it is unlikely that netif_tx_lock protection is necessary
here.
(f.e. loopback and IP tunnels are clean ignoring statistics
counters.)
However, it is possible, that they rely on protection
made by us here.
Check this and shot the lock. It is not prone from deadlocks.
Either shot noqueue qdisc, it is even simpler 8)
*/
//首先,確定設備是開啟的,並且還要確定隊列是運行的,啟動和停止隊列有驅動程序決定
//設備沒有輸出隊列典型的是回環設備。這裡需要做的就是直接調用dev_start_queue_xmit、、函數,經過驅動發送出去,如果發送失敗,就直接丟棄,沒有隊列可以保存。
if (dev->flags & IFF_UP) {
int cpu = smp_processor_id(); /* ok
because BHs are off */
if (txq->xmit_lock_owner != cpu) {
if (__this_cpu_read(xmit_recursion) > RECURSION_LIMIT)
goto recursion_alert;
HARD_TX_LOCK(dev, txq, cpu);
if (!netif_tx_queue_stopped(txq)) {
__this_cpu_inc(xmit_recursion);
rc = dev_hard_start_xmit(skb, dev, txq);//見說明4
__this_cpu_dec(xmit_recursion);
if (dev_xmit_complete(rc)) {
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 {
/* Recursion is It is possible,
* unfortunately
*/
recursion_alert:
if (net_ratelimit())
printk(KERN_CRIT "Dead loop on virtual device "
"%s, fix it urgently!\n", dev->name);
}
}
rc = -ENETDOWN;
rcu_read_unlock_bh();
kfree_skb(skb);
return rc;
out:
rcu_read_unlock_bh();
return rc;
}
1. 下面是dev_pick_tx函數。
點擊(此處)折疊或打開
static struct netdev_queue *dev_pick_tx(struct net_device *dev,
struct sk_buff *skb)
{
int queue_index;
const struct net_device_ops *ops = dev->netdev_ops;
if (ops->ndo_select_queue) {
//選擇一個索引,這個策略可以設置,比如優先選擇視頻和音頻隊列,而哪個隊列邦定哪個策略也是設定的。
queue_index = ops->ndo_select_queue(dev, skb);
queue_index = dev_cap_txqueue(dev, queue_index);
} else {
struct sock *sk = skb->sk;
queue_index = sk_tx_queue_get(sk);
if (queue_index < 0 || queue_index >= dev->real_num_tx_queues) {
queue_index = 0;
if (dev->real_num_tx_queues > 1)
queue_index = skb_tx_hash(dev, skb);
if (sk) {
struct dst_entry *dst = rcu_dereference_check(sk->sk_dst_cache, 1);
if (dst && skb_dst(skb) == dst)
sk_tx_queue_set(sk, queue_index);
}
}
}
skb_set_queue_mapping(skb, queue_index);
return netdev_get_tx_queue(dev, queue_index);
}
2. 下面是其中的一種網卡類型調用函數alloc_etherdev時,
dev = alloc_etherdev(sizeof(struct ether1_priv));
其實該函數是一個宏定義:其中第二參數表示的就是隊列的數量,這裡在Linux2.6.37內核中找到的一種硬件網卡的實現,可用的隊列是1個。
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
下面是alloc_etherdev_mq函數的定義實現。
點擊(此處)折疊或打開
struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)
{
return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
}
3. 幾乎所有的設備都會使用隊列調度出口的流量,而內核可以使用對了規則的算法安排那個幀進行發送,使其以最優效率的次序進行傳輸。這裡檢查這個隊列中是否有enqueue函數,如果有則說明設備會使用這個隊列,否則需另外處理。關於enqueue函數的設置,我找到dev_open->dev_activate中調用了qdisc_create_dflt來設置,需要注意的是,這裡並不是將傳進來的skb直接發送,而是先入隊,然後調度隊列,具體發送哪個數據包由enqueue和dequeue函數決定,這體現了設備的排隊規則
Enqueue
把一個元素添加的隊列
Dequeue
從隊列中提取一個元素
Requeue
把一個原先已經提取的元素放回到隊列,可以由於傳輸失敗。
if (q->enqueue)為真的話,表明這個設備有隊列,可以進行相關的流控。調用__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);
bool contended = qdisc_is_running(q);
int rc;
/*
* Heuristic to force contended enqueues to serialize on a
* separate lock before trying to get qdisc main lock.
* This permits __QDISC_STATE_RUNNING owner to get the lock more often
* and dequeue packets faster.
*/
if (unlikely(contended))
spin_lock(&q->busylock);
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) &&
qdisc_run_begin(q)) {
/*
* This is a work-conserving queue; there are no old skbs
* waiting to be sent out; and the qdisc is not running -
* xmit the skb directly.
*/
if (!(dev->priv_flags & IFF_XMIT_DST_RELEASE))
skb_dst_force(skb);
__qdisc_update_bstats(q, skb->len);
if (sch_direct_xmit(skb, q, dev, txq, root_lock)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
__qdisc_run(q);
} else
qdisc_run_end(q);
rc = NET_XMIT_SUCCESS;
} else {
skb_dst_force(skb);
rc = qdisc_enqueue_root(skb, q);
if (qdisc_run_begin(q)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
__qdisc_run(q);
}
}
spin_unlock(root_lock);
if (unlikely(contended))
spin_unlock(&q->busylock);
return rc;
}
_dev_xmit_skb函數主要做兩件事情:
(1) 如果流控對象為空的,試圖直接發送數據包。
(2)如果流控對象不空,將數據包加入流控對象,並運行流控對象。
當設備進入調度隊列准備傳輸時,qdisc_run函數就會選出下一個要傳輸的幀,而該函數會間接的調用相關聯的隊列規則dequeue函數,從對了中取出數據進行傳輸。
有兩個時機將會調用qdisc_run():
1.__dev_xmit_skb()
2. 軟中斷服務線程NET_TX_SOFTIRQ
其實,真正的工作有qdisc_restart函數實現。
點擊(此處)折疊或打開
void __qdisc_run(struct Qdisc *q)
{
unsigned long start_time = jiffies;
while (qdisc_restart(q)) { //返回值大於0,說明流控對象非空。
/*
* Postpone processing if
* 1. another process needs the CPU;
* 2. we've been doing it for too long.
*/
if (need_resched() || jiffies != start_time) { //已經不允許繼續運行本流控對象。
__netif_schedule(q); //將本隊列加入軟中斷的output_queue鏈表中。
break;
}
}
qdisc_run_end(q);
}
如果發現本隊列運行的時間太長了,將會停止隊列的運行,並將隊列加入output_queue鏈表頭。
點擊(此處)折疊或打開
static inline int qdisc_restart(struct Qdisc *q)
{
struct netdev_queue *txq;
struct net_device *dev;
spinlock_t *root_lock;
struct sk_buff *skb;
/* Dequeue packet */
skb = dequeue_skb(q);//一開始就調用dequeue函數。
if (unlikely(!skb))
return 0;
WARN_ON_ONCE(skb_dst_is_noref(skb));
root_lock = qdisc_lock(q);
dev = qdisc_dev(q);
txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
return sch_direct_xmit(skb, q, dev, txq, root_lock);//用於發送數據包
}
* Returns to the caller:
* 0 - queue is empty or throttled.
* >0 - queue is not empty.
*/
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());
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);
if (dev_xmit_complete(ret)) {
/* Driver sent out skb successfully or skb was consumed */
//發送成功,返回新的隊列的長度
ret = qdisc_qlen(q);
} else if (ret == NETDEV_TX_LOCKED) {
/* Driver try lock failed */
ret = handle_dev_cpu_collision(skb, txq, q);
} else {
/* 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);
//設備繁忙,重新調度發送(利用softirq)
ret = dev_requeue_skb(skb, q);
}
if (ret && (netif_tx_queue_stopped(txq) ||
netif_tx_queue_frozen(txq)))
ret = 0;
return ret;
}
4. 我們看一下下面的發送函數。
從此函數可以看出,當驅動使用發送隊列的時候會循環從隊列中取出包發送
而不使用隊列的時候只發送一次,如果沒發送成功就直接丟棄
點擊(此處)折疊或打開
struct netdev_queue *txq)
{
const struct net_device_ops *ops = dev->netdev_ops;//驅動程序的函數集
int rc = NETDEV_TX_OK;
if (likely(!skb->next)) {
if (!list_empty(&ptype_all))
dev_queue_xmit_nit(skb, dev);//如果dev_add_pack加入的是ETH_P_ALL,那麼就會復制一份給你的回調函數。
/*
* 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);
skb_orphan_try(skb);
if (vlan_tx_tag_present(skb) &&
!(dev->features & NETIF_F_HW_VLAN_TX)) {
skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
if (unlikely(!skb))
goto out;
skb->vlan_tci = 0;
}
if (netif_needs_gso(dev, skb)) {
if (unlikely(dev_gso_segment(skb)))
goto out_kfree_skb;
if (skb->next)
goto gso;
} else {
if (skb_needs_linearize(skb, dev) &&
__skb_linearize(skb))
goto out_kfree_skb;
/* If packet is not checksummed and device
does not
* support checksumming for this protocol, complete
* checksumming here.
*/
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;
}
}
rc = ops->ndo_start_xmit(skb, dev);//調用網卡的驅動程序發送數據。不同的網絡設備有不同的發送函數
trace_net_dev_xmit(skb, rc);
if (rc == NETDEV_TX_OK)
txq_trans_update(txq);
return rc;
}
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); //調用網卡的驅動程序發送數據。不同的網絡設備有不同的發送函數
trace_net_dev_xmit(nskb, rc);
if (unlikely(rc != NETDEV_TX_OK)) {
if (rc & ~NETDEV_TX_MASK)
goto out_kfree_gso_skb;
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);
out_kfree_gso_skb:
if (likely(skb->next == NULL))
skb->destructor = DEV_GSO_CB(skb)->destructor;
out_kfree_skb:
kfree_skb(skb);
out:
return rc;
}
5. 下面看一下dev_queue_xmit_nit函數。對於通過socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL))創建的原始套接口,不但可以接受從外部輸入的數據包,而且對於由於本地輸出的數據包,如果滿足條件,也可以能接受。
該函數就是用來接收由於本地輸出的數據包,在鏈路層的輸出過程中,會調用此函數,將滿足條件的數據包輸入到RAW套接口,
點擊(此處)折疊或打開
static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{
struct packet_type *ptype;
#ifdef CONFIG_NET_CLS_ACT
if (!(skb->tstamp.tv64 && (G_TC_FROM(skb->tc_verd) & AT_INGRESS)))
net_timestamp_set(skb);-----------------(1)
#else
net_timestamp_set(skb);
#endif
rcu_read_lock();
list_for_each_entry_rcu(ptype, &ptype_all, list) {-----------------(2)
/* Never send packets back to the socket
* they originated from - MvS ([email protected])
*/
if ((ptype->dev == dev || !ptype->dev) &&
(ptype->af_packet_priv == NULL ||
(struct sock *)ptype->af_packet_priv != skb->sk)) {-----------------(3)
struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); -----------------(4)
if (!skb2)
break;
/* skb->nh should be correctly
set by sender, so that the second statement is
just protection against buggy protocols.
*/
skb_reset_mac_header(skb2);
if (skb_network_header(skb2) < skb2->data ||
skb2->network_header > skb2->tail) {
if (net_ratelimit())
printk(KERN_CRIT "protocol %04x is "
"buggy, dev %s\n",
ntohs(skb2->protocol),
dev->name);
skb_reset_network_header(skb2); -----------------(5)
}
skb2->transport_header = skb2->network_header;
skb2->pkt_type = PACKET_OUTGOING;
ptype->func(skb2, skb->dev, ptype, skb->dev); -----------------(6)
}
}
rcu_read_unlock();
}
說明:
(1) 記錄該數據包輸入的時間戳
(2) 遍歷ptype_all鏈表,查找所有符合輸入條件的原始套接口,並循環將數據包輸入到滿足條件的套接口
(3) 數據包的輸出設備與套接口的輸入設備相符或者套接口不指定輸入設備,並且該數據包不是有當前用於比較的套接口輸出,此時該套接口滿足條件,數據包可以輸入
(4) 由於該數據包是額外輸入到這個原始套接口的,因此需要克隆一個數據包
(5) 校驗數據包是否有效
(6) 將數據包輸入原始套接口
6. 對於lookback設備來說處理有些不同。它的hard_start_xmit函數是loopback_xmit
在net/lookback.c文件中,定義的struct net_device_ops loopback_ops結構體
點擊(此處)折疊或打開
static const struct net_device_ops loopback_ops = {
.ndo_init = loopback_dev_init,
.ndo_start_xmit= loopback_xmit,
.ndo_get_stats64 = loopback_get_stats64,
};
從這裡可以看到起發送函數為loopback_xmit函數。
點擊(此處)折疊或打開
static netdev_tx_t loopback_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct pcpu_lstats *lb_stats;
int len;
skb_orphan(skb);
skb->protocol = eth_type_trans(skb, dev);
/* it's OK to use per_cpu_ptr() because
BHs are off */
lb_stats = this_cpu_ptr(dev->lstats);
len = skb->len;
if (likely(netif_rx(skb) == NET_RX_SUCCESS)) {//直接調用了netif_rx進行了接收處理
u64_stats_update_begin(&lb_stats->syncp);
lb_stats->bytes += len;
lb_stats->packets++;
u64_stats_update_end(&lb_stats->syncp);
}
return NETDEV_TX_OK;
}
7. 已經有了dev_queue_xmit函數,為什麼還需要軟中斷來發送呢?
dev_queue_xmit是對skb做些最後的處理並且第一次嘗試發送,軟中斷是將前者發送失敗或者沒發完的包發送出去。
主要參考文獻:
Linux發送函數dev_queue_xmit分析 http://shaojiashuai123456.iteye.com/blog/842236
TC流量控制實現分析(初步) /content/10833609.html
Linux內核源碼剖析 TCP/IP實現
Copyright © Linux教程網 All Rights Reserved