Linux 2.4.x 網路通訊協定棧QoS模組(TC)的設計與實現

來源:互聯網
上載者:User

本文描述了linux 2.4.x核心中對QoS支援的設計與實現,並且對預設的資料包調度機制PFIFO進行了詳細的分析。

在傳統的TCP/IP網路的路由器中,所有的IP資料包的傳輸都是採用FIFO(先進先出),盡最大努力傳輸的處理機制。在早期網路資料量和關鍵業務資料不多的時候,並沒有體現出非常大的缺點,路由器簡單的把資料報丟棄來處理擁塞。但是隨著電腦網路的發展, 資料量的急劇增長,以及多媒體,VOIP資料等對延時要求高的應用的增加。路由器簡單丟棄資料包的處理方法已經不再適合當前的網路。單純的增加網路頻寬也不能從根本上解決問題。所以網路的開發人員們提出了服務品質的概念。概括的說:就是針對各種不同需求,提供不同服務品質的網路服務功能。提供QoS能力將是對未來IP網路的基本要求。

1.Linux核心對QoS的支援

Linux核心網路通訊協定棧從2.2.x開始,就實現了對服務品質的支援模組。具體的代碼位於net/sched/目錄。在Linux裡面,對這個功能模組的稱呼是Traffic Control ,簡稱TC。

首先我們瞭解一下Linux網路通訊協定棧在沒有TC模組時發送資料包的大致流程。1。

註:的分層是按照Linux實現來畫,並沒有嚴格遵守OSI分層

從可以看出,沒有TC的情況下,每個資料包的發送都會調用dev_queue_xmit,然後判斷是否需要向AF_PACKET協議支援體傳遞資料包內容,最後直接調用網卡驅動註冊的發送函數把資料包發送出去。發送資料包的機制就是本文開始講到的FIFO機制。一旦出現擁塞,協議棧只是盡自己最大的努力去調用網卡發送函數。所以這種傳統的處理方法存在著很大的弊端。

為了支援QoS,Linux的設計者在發送資料包的代碼中加入了TC模組。從而可以對資料包進行分類,管理,檢測擁塞和處理擁塞。為了避免和以前的代碼衝突,並且讓使用者可以選擇是否使用TC。核心開發人員在中的兩個紅色圓圈之間添加了TC模組。(實際上在TC模組中,發送資料包也實現對AF_PACKET協議的支援,本文為了描述方便,把兩個地方的AF_PACKET協議處理分開來了)。

下面從具體的代碼中分析一下對TC模組的支援。

net/core/dev.c: dev_queue_xmit函數中略了部分代碼:

int dev_queue_xmit(struct sk_buff *skb){……………….    q = dev->qdisc;    if (q->enqueue) {   /*如果這個裝置啟動了TC,那麼把資料包壓入隊列*/        int ret = q->enqueue(skb, q);   /*啟動這個裝置發送*/        qdisc_run(dev);        return;    }    if (dev->flags&IFF_UP) {………….                if (netdev_nit)                    dev_queue_xmit_nit(skb,dev);/*對AF_PACKET協議的支援*/                if (dev->hard_start_xmit(skb, dev) == 0) {/*調用網卡驅動發送函數發送資料包*/                    return 0;                }            }………………}

 

從上面的代碼中可以看出,當q->enqueue為假的時候,就不採用TC處理,而是直接發送這個資料包。如果為真,則對這個資料包進行QoS處理。

 

2.TC的具體設計與實現

第一節描述了linux核心是如何對QoS進行支援的,以及是如何在以前的代碼基礎上添加了tc模組。本節將對TC的設計和實現進行詳細的描述。

QoS有很多的擁塞處理機制,如FIFO Queueing(先入先出隊列),PQ(優先隊列),CQ(定製隊列),WFQ(加權公平隊列)等等。QoS還要求能夠對每個介面分別採用不同的擁塞處理。為了能夠實現上述功能,Linux採用了基於對象的實現方法。

 

 

 

是一個資料發送隊列管理機制的模型圖。其中的QoS策略可以是各種不同的擁塞處理機制。我們可以把這一種策略看成是一個類,策略類。在實現中,這個類有很多的執行個體對象,策略對象。使用者可以分別採用不同的對象來管理資料包。策略類有很多的方法。如入隊列(enqueue),出隊列(dequeue),重新入隊列(requeue),初始化(init),撤銷(destroy)等方法。在Linux中,用Qdisc_ops結構體來代表上面描述的策略類。

前面提到,每個裝置可以採用不同的策略對象。所以在裝置和對象之間需要有一個橋樑,使裝置和裝置採用的對象相關。在Linux中,起到橋樑作用的是Qdisc結構體。

通過上面的描述,整個TC的架構也就出來了。如:

加上TC之後,發送資料包的流程應該是這樣的:

(1) 上層協議開始發送資料包

(2) 獲得當前裝置所採用的策略對象

(3) 調用此對象的enqueue方法把資料包壓入隊列

(4) 調用此對象的dequeue方法從隊列中取出資料包

(5) 調用網卡驅動的發送函數發送

接下來從代碼上來分析TC是如何對每個裝置安裝策略對象的。

在網卡註冊的時候,都會調用register_netdevice,給裝置安裝一個Qdisc和Qdisc_ops。

int register_netdevice(struct net_device *dev){………………….dev_init_scheduler(dev);………………….}void dev_init_scheduler(struct net_device *dev){…………./*安裝裝置的qdisc為noop_qdisc*/dev->qdisc = &noop_qdisc;………….dev->qdisc_sleeping = &noop_qdisc;dev_watchdog_init(dev);}此時,網卡裝置剛註冊,還沒有UP,採用的是noop_qdisc,struct Qdisc noop_qdisc ={noop_enqueue,noop_dequeue,TCQ_F_BUILTIN,&noop_qdisc_ops,};noop_qdisc採用的資料包處理方法是noop_qdisc_ops,struct Qdisc_ops noop_qdisc_ops ={NULL,NULL,"noop",0,noop_enqueue,noop_dequeue,noop_requeue,};

從noop_enqueue,noop_dequeue,noop_requeue函數的定義可以看出,他們並沒有對資料包進行任何的分類或者排隊,而是直接釋放掉skb。所以此時網卡裝置還不能發送任何資料包。必須ifconfig up起來之後才能發送資料包。

調用ifconfig up來啟動網卡裝置會走到dev_open函數。

int dev_open(struct net_device *dev){…………….dev_activate(dev);……………..}void dev_activate(struct net_device *dev){…………. if (dev->qdisc_sleeping == &noop_qdisc) {qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops);/*安裝預設的qdisc*/}……………if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc) {……………./*.安裝特定的qdisc*/}……………..}

裝置啟動之後,此時當前裝置預設的Qdisc->ops是pfifo_fast_ops。如果需要採用不同的ops,那麼就需要為裝置安裝其他的Qdisc。本質上是替換掉dev->Qdisc指標。見sched/sch_api.c 的dev_graft_qdisc函數。

static struct Qdisc *dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc){……………oqdisc = dev->qdisc_sleeping;/* 首先刪除掉舊的qdisc */if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)qdisc_reset(oqdisc);/*安裝新的qdisc */if (qdisc == NULL)qdisc = &noop_qdisc;dev->qdisc_sleeping = qdisc;dev->qdisc = &noop_qdisc;/*啟動新安裝的qdisc*/if (dev->flags & IFF_UP)dev_activate(dev);…………………}

從dev_graft_qdisc可以看出,如果需要使用新的Qdisc,那麼首先需要刪除舊的,然後安裝新的,使dev->qdisc_sleeping 為新的qdisc,然後調用dev_activate函數來啟動新的qdisc。結合dev_activate函數中的語句:

if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc) 

可以看出,此時的dev->qdisc所指的就是新的qdisc。(注意,上面語句中左邊是一個指派陳述式。)

在網卡down掉的時候,通過調用dev_close -> dev_deactivate重新使裝置的qdisc為noop_qdisc,停止發送資料包。

Linux中的所有的QoS策略最終都是通過上面這個方法來安裝的。在sch_api.c中,對dev_graft_qdisc函數又封裝了一層函數(register_qdisc),供模組來安裝新的Qdisc。如RED(早期隨即檢測隊列)模組,就調用register_qdisc來安裝RED對象(net/sched/sch_red.c->init_module())。

在Linux中,如果裝置啟動之後,沒有配置特定的QoS策略,核心對每個裝置採用預設的策略,pfifo_fast_ops。下面的pfifo_fast_ops進行詳細的分析。

中的資訊可以對應於pfifo_fast_ops結構體的每個部分:

static struct Qdisc_ops pfifo_fast_ops ={NULL,NULL,"pfifo_fast",/*ops名稱*/3 * sizeof(struct sk_buff_head),/*資料包skb隊列*/pfifo_fast_enqueue,/*入隊列函數*/pfifo_fast_dequeue,/*出隊列函數*/pfifo_fast_requeue,/*重新壓入隊列函數*/NULL,pfifo_fast_init,/*隊列管理初始化函數*/pfifo_fast_reset,/*隊列管理重設函數*/};

在註冊pfifo_fast_ops的時候首先會調用pfifo_fast_init來初始化隊列管理,見qdisc_create_dflt函數。

static int pfifo_fast_init(struct Qdisc *qdisc, struct rtattr *opt){………for (i=0; i<3; i++)skb_queue_head_init(list+i);/*初始化3個優先順序隊列*/……….}

init函數的作用就是初始化3個隊列。

在登出一個Qdisc的時候都會調用Qdisc的ops的reset函數。見dev_graft_qdisc函數。

static voidpfifo_fast_reset(struct Qdisc* qdisc){…………..for (prio=0; prio < 3; prio++)skb_queue_purge(list+prio);/*釋放3個優先順序隊列中的所有資料包*/…………..}

在資料包發送的時候會調用Qdisc->enqueue函數(在qdisc_create_dflt函數中已經將Qdisc_ops的enqueue,dequeue,requeue函數分別賦值於Qdisc分別對應的函數指標)。

int dev_queue_xmit(struct sk_buff *skb){……………….    q = dev->qdisc;    if (q->enqueue) {   /* 對應於pfifo_fast_enqueue 函數*/        int ret = q->enqueue(skb, q);   /*啟動這個裝置的發送,這裡涉及到兩個函數pfifo_fast_dequeue ,pfifo_fast_requeue 稍後介紹*/        qdisc_run(dev);        return;    }……………}

入隊列函數pfifo_fast_enqueue:

static intpfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc){…………..list = ((struct sk_buff_head*)qdisc->data) +prio2band[skb->priority&TC_PRIO_MAX];/*首先確定這個資料包的優先順序,決定放入的隊列*/if (list->qlen <= skb->dev->tx_queue_len) {__skb_queue_tail(list, skb);/*將資料包放入隊列的尾部*/qdisc->q.qlen++;return 0;}……………..}

在資料包放入隊列之後,調用qdisc_run來發送資料包。

static inline void qdisc_run(struct net_device *dev){while (!netif_queue_stopped(dev) &&       qdisc_restart(dev)<0)/* NOTHING */;}

在qdisc_restart函數中,首先從隊列中取出一個資料包(調用函數pfifo_fast_dequeue)。然後調用網卡驅動的發送函數(dev->hard_start_xmit)發送資料包,如果發送失敗,則需要將這個資料包重新壓入隊列(pfifo_fast_requeue),然後啟動協議棧的發送非強制中斷進行再次的發送。

static struct sk_buff *pfifo_fast_dequeue(struct Qdisc* qdisc){…………..for (prio = 0; prio < 3; prio++, list++) {skb = __skb_dequeue(list);if (skb) {qdisc->q.qlen--;return skb;}}……………….}

從dequeue函數中可以看出,pfifo的策略是:從高優先順序隊列中取出資料包,只有高優先順序的隊列為空白,才會對下一優先順序的隊列進行處理。

requeue函數重新將資料包壓入相應優先順序隊列的頭部。

static intpfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc){struct sk_buff_head *list;list = ((struct sk_buff_head*)qdisc->data) +prio2band[skb->priority&TC_PRIO_MAX];/*確定相應優先順序的隊列*/__skb_queue_head(list, skb);/*將資料包壓入隊列的頭部*/qdisc->q.qlen++;return 0;}
 




回頁首

總結:QoS是當前一個非常熱門的話題,幾乎所有高端的網路裝置都支援QoS功能,並且這個功能也是當前網路裝置之間競爭的一個關鍵技術。Linux為了在在高端伺服器能夠佔有一席之地,從2.2.x核心開始就支援了QoS。本文在linux 2.4.0的代碼基礎上對Linux如何支援QoS進行了分析。並且分析了Linux核心的預設隊列處理方法PFIFO的實現。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.