先來看下traffic control在核心裡是如何?的,首先核心在發送資料時,最終會調用到dev_queue_xmit,
struct Qdisc *q
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
如果q->enqueue函數不為空白,此時就進入traffic control的邏輯,下面調用__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
函數會判斷Qdisc->state狀態,如果__QDISC_STATE_DEACTIVATED,那麼free skb返回NET_XMIT_DROP。如果Qdisc->state狀態為非__QDISC_STATE_RUNNING,同時Qdisc的標籤有TCQ_F_CAN_BYPASS,那麼直接把包發出去。否則調用qdisc_enqueue_skb把skb放到root Qdisc中,再調用qdisc_run
qdisc_run如果判斷Qdisc->state為__QDISC_STATE_RUNNING,則調用__qdisc_run,
void __qdisc_run(struct Qdisc *q)
{
unsigned long start_time = jiffies;
while (qdisc_restart(q)) {
/*
* 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);
break;
}
}
clear_bit(__QDISC_STATE_RUNNING, &q->state);
}
__qdisc_run會迴圈調用qdisc_restart,直到消耗了一個jiffy或者該CPU需要被調度給其他進程(need_resched),此時調用__netif_reschedule把當前CPU的softnet_data->output_queue交給Qdisc->output_queue,並觸發一個NET_TX_SOFTIRQ非強制中斷
qdisc_restart函數如下:
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);
if (unlikely(!skb))
return 0;
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);
}
qdisc_restart從Qdisc頭裡取一個skb出來,調用qdisc_lock擷取一個Qdisc根鎖,然後調用netdev_get_tx_queue基於skb雜湊擷取netdev_queue隊列,調用sch_direct_xmit直接把skb發送出去
sch_direct_xmit先調用qdisc_unlock釋放Qdisc根鎖,調用dev_hard_start_xmit通過驅動發送skb,然後判斷傳回值,如果是NETDEV_TX_OK,返回qdisc_qlen的Qdisc隊列長度;如果是NETDEV_TX_LOCKED,此時出現了鎖異常,這裡不深入探討了;如果返回了NETDEV_TX_BUSY,則調用dev_requeue_skb把skb重新入隊列
traffic control還支援對入包進行控制,核心對於入包會調用netif_receive_skb,該函數會調用handle_ing。handle_ing首先判斷skb->dev->rx_queue.qdisc是否是noop_qdisc,如果是noop_qdisc那麼不會有任何QoS控制,否則調用ing_filter
static int ing_filter(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
u32 ttl = G_TC_RTTL(skb->tc_verd);
struct netdev_queue *rxq;
int result = TC_ACT_OK;
struct Qdisc *q;
if (MAX_RED_LOOP < ttl++) {
printk(KERN_WARNING
"Redir loop detected Dropping packet (%d->%d)\n",
skb->iif, dev->ifindex);
return TC_ACT_SHOT;
}
skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
rxq = &dev->rx_queue;
q = rxq->qdisc;
if (q != &noop_qdisc) {
spin_lock(qdisc_lock(q));
if (likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
result = qdisc_enqueue_root(skb, q);
spin_unlock(qdisc_lock(q));
}
return result;
}
ing_filter首先找出skb->dev->rx_queue.qdisc,如果不是noop_qdisc,同時狀態為非__QDISC_STATE_DEACTIVATED,則調用qdisc_enqueue_root,把skb入隊列
可以看出,如果網路裝置支援traffic control,其驅動的收發函數必須要支援Qdisc的enqueue/dequeue,從測試結果和代碼上看,xen netback就可以支援,而bridge就無法支援
對於xen netback而言,限制虛擬機器出包QoS有兩種方法,首先要知道,netfront發包到了netback,會觸發netback的net_tx_action tasklet,裡面會調用net_tx_submit,繼而最終調用到netif_rx,還記得這個函數不,核心協議棧處理非NAPI的時候就是調用netif_rx來收包的。netif_rx會調用netif_receive_skb,裡面會有一個和handle_ing函數,用來對ingress的包做QoS,這就是第一種方法,對netback做tc
qdisc ingress規則來進行traffic control;
限制出包的第二鐘方法前面也提過啦,就是在橋接器上做mark神馬的,然後在物理口針對mark做traffic control,由於這裡是egress規則,因此可以用各種tc class來做。
那麼netback上限制虛擬機器入包呢?對於入包而言,首先從物理口進入橋接器,然後從橋接器進入netback,橋接器把包交換到網路裝置的過程中,會調用br_forward_finish,繼而調用br_dev_queue_push_xmit通過dev_queue_xmit把包“發”出去。因此限制虛擬機器入包QoS,只需要在netback上設定tc egress規則即可。由於netback是net_device裝置的一個超集,因此支援dev_queue_xmit方法,也支援egress方向的qdisc規則
最後,來看下ingress QoS的相關設定,核心內建的ingress qdisc的功能是非常簡單的,只支援最基本的rate limit,對於超出的流量一律drop,目前好的做法是通過一個虛擬網路裝置把ingress流量redirect到這個裝置上,再在這個虛擬設備設定traffic control規則
modprobe ifb
ip link set ifb0 up
ip link set ifb1 up
ifb裝置是核心用來做traffic control redirect的,驅動載入之後,可以對該裝置設定和egress相同的規則
tc qdisc add dev ifb0 root handle 1: htb default 100
tc class add dev ifb0 parent 1: classid 1:10 htb rate 100mbit ceil 100mbit
tc filter add dev ifb0 parent 1: protocol ip prio 1 u32 match ip src 0.0.0.0/0 flowid 1:10 #所有流量都match classid 1:10
最後還需要一條規則把peth0的入流量redirect到ifb0裝置上
tc qdisc add dev peth0 ingress
tc filter add dev peth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0