本文檔的Copyleft歸yfydz所有,使用GPL發布,可以自由拷貝,轉載,轉載時請保持文檔的完整性, 嚴禁用于任何商業用途。 msn: yfydz_no1@hotmail.com 來源:http://yfydz.cublog.cn 5.4 PRIO(priority) PRIO是PFIFO_FAST算法的擴展,PFIFO_FAST中一共是3個隊列, 而PRIO最多可設置16個帶(band),每 個帶都相當于是一個PFIFO_FAST, 因此可以進行更細粒度地分類然后進行排隊, 在 net/sched/sch_prio.c中定義。 5.4.1 操作結構定義 // 最大帶數 #define TCQ_PRIO_BANDS?16 // 最小帶數 #define TCQ_MIN_PRIO_BANDS 2 // PRIO私有數據結構 struct prio_sched_data { // 有效帶數, 不超過16 ?int bands; // 協議過濾器鏈表 ?struct tcf_proto *filter_list; // 優先權轉帶值的轉換數組, 數組是16個元素 ?u8? prio2band[TC_PRIO_MAX+1]; // 16個qdisc指針的數組 ?struct Qdisc *queues[TCQ_PRIO_BANDS]; }; // PRIO流控算法操作結構 static struct Qdisc_ops prio_qdisc_ops = { ?.next??=?NULL, ?.cl_ops??=?&prio_class_ops, ?.id??=?"prio", ?.priv_size?=?sizeof(struct prio_sched_data), ?.enqueue?=?prio_enqueue, ?.dequeue?=?prio_dequeue, ?.requeue?=?prio_requeue, ?.drop??=?prio_drop, ?.init??=?prio_init, ?.reset??=?prio_reset, ?.destroy?=?prio_destroy, ?.change??=?prio_tune, ?.dump??=?prio_dump, ?.owner??=?THIS_MODULE, }; // PRIO類別操作結構 static struct Qdisc_class_ops prio_class_ops = { ?.graft??=?prio_graft, ?.leaf??=?prio_leaf, ?.get??=?prio_get, ?.put??=?prio_put, ?.change??=?prio_change, ?.delete??=?prio_delete, ?.walk??=?prio_walk, ?.tcf_chain?=?prio_find_tcf, ?.bind_tcf?=?prio_bind, ?.unbind_tcf?=?prio_put, ?.dump??=?prio_dump_class, }; 5.4.2 初始化 static int prio_init(struct Qdisc *sch, struct rtattr *opt) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); ?int i; // 16個Qdisc都初始化為noop_qdisc ?for (i=0; i<TCQ_PRIO_BANDS; i++) ??q->queues[i] = &noop_qdisc; if (opt == NULL) { ??return -EINVAL; ?} else { ??int err; // 根據參數選項設置PRIO算法內部參數 ??if ((err= prio_tune(sch, opt)) != 0) ???return err; ?} ?return 0; } // 算法參數調整, 同時也是prio_qdisc_ops結構的change成員函數 // 指定有多少個帶, 每個帶對應一個pfifo_fast的流控節點 static int prio_tune(struct Qdisc *sch, struct rtattr *opt) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); // TC的PRIO的參數, 包括帶數和優先權值到帶值的轉換數組 ?struct tc_prio_qopt *qopt = RTA_DATA(opt); ?int i; // 長度檢查 ?if (opt->rta_len < RTA_LENGTH(sizeof(*qopt))) ??return -EINVAL; // 帶數為2~16個 ?if (qopt->bands > TCQ_PRIO_BANDS || qopt->bands < 2) ??return -EINVAL; // 檢查轉換數組中的值是否都不超過帶數, 否則非法 ?for (i=0; i<=TC_PRIO_MAX; i++) { ??if (qopt->priomap[i] >= qopt->bands) ???return -EINVAL; ?} sch_tree_lock(sch); // 有效帶數 ?q->bands = qopt->bands; // 映射數組: 優先權值 -> 帶值 ?memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1); // 將大于等于帶值的的Qdisc數組項都釋放掉, 指向noop_qdisc ?for (i=q->bands; i<TCQ_PRIO_BANDS; i++) { ??struct Qdisc *child = xchg(&q->queues[i], &noop_qdisc); ??if (child != &noop_qdisc) ???qdisc_destroy(child); ?} ?sch_tree_unlock(sch); // 設置有效的Qdisc數組, 數量為指定的帶數 ?for (i=0; i<q->bands; i++) { // 為noop_qdisc表示該qdisc數組項可用 ??if (q->queues[i] == &noop_qdisc) { ???struct Qdisc *child; // 創建一個pfifo_fast的Qdisc ???child = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops); ???if (child) { ????sch_tree_lock(sch); // 將生成的PFIFO_FAST的Qdisc賦給PRIO的Qdisc中的一個數組元素 ????child = xchg(&q->queues[i], child); // 這個判斷應該是unlikely的, 結果應該是假 ????if (child != &noop_qdisc) ?????qdisc_destroy(child); ????sch_tree_unlock(sch); ???} ??} ?} ?return 0; } 值得注意的是在初始化賦值函數中沒有設置過濾器鏈表q->filter_list, 應該是后續執行單獨命令進 行綁定的。 5.4.3 入隊 static int prio_enqueue(struct sk_buff *skb, struct Qdisc *sch) { ?struct Qdisc *qdisc; ?int ret; // 根據skb數據包的優先權值(priority)確定帶值, 返回該帶值對應的Qdisc ?qdisc = prio_classify(skb, sch, &ret); #ifdef CONFIG_NET_CLS_ACT ?if (qdisc == NULL) { // 該處的qdisc為空, 丟包 ??if (ret == NET_XMIT_BYPASS) ???sch->qstats.drops++; ??kfree_skb(skb); ??return ret; ?} #endif // 調用該qdisc的入隊函數, 正常就是pfifo_fast流控算法的入隊函數 ?if ((ret = qdisc->enqueue(skb, qdisc)) == NET_XMIT_SUCCESS) { // 入隊成功, 統計值更新 ??sch->bstats.bytes += skb->len; ??sch->bstats.packets++; ??sch->q.qlen++; ??return NET_XMIT_SUCCESS; ?} ?sch->qstats.drops++; ?return ret; } // PRIO分類操作 static struct Qdisc * prio_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); // 帶值初始化為數據包優先權值 ?u32 band = skb->priority; ?struct tcf_result res; // 缺省返回錯誤值: 旁路 ?*qerr = NET_XMIT_BYPASS; // 優先權的低16位清零后不等于Qdisc的句柄值的情況 ?if (TC_H_MAJ(skb->priority) != sch->handle) { #ifdef CONFIG_NET_CLS_ACT // 該內核選項表示Qdisc可以對數據包進行最終動作,如發送, 丟棄等 // TC分類 ??switch (tc_classify(skb, q->filter_list, &res)) { // STOLEN或QUEUED都表示成功 ??case TC_ACT_STOLEN: ??case TC_ACT_QUEUED: ???*qerr = NET_XMIT_SUCCESS; ??case TC_ACT_SHOT: ???return NULL; ??}; // 沒有過濾表 ??if (!q->filter_list ) { #else // 沒有過濾表或者分類不成功 ??if (!q->filter_list || tc_classify(skb, q->filter_list, &res)) { #endif // 如果帶值高16位非0, 帶值取為0 ???if (TC_H_MAJ(band)) ????band = 0; // 用帶值的最低4位作為轉換數組的索引返回相應的Qdisc流控結構 ???return q->queues[q->prio2band[band&TC_PRIO_MAX]]; ??} // 分類成功, 將返回的類別值賦值為帶值 ??band = res.classid; ?} // 優先權的低16位清零后等于Qdisc的句柄值的情況 // 帶值為取priority的低16位 ?band = TC_H_MIN(band) - 1; // 如果超過Qdisc中的有效帶數, 取0號優先權對應的帶值對應的Qdisc數組項 ?if (band > q->bands) ??return q->queues[q->prio2band[0]]; // 取帶值對應的Qdisc數組項 ?return q->queues[band]; } /* net/sched/sch_api.c */ /* Main classifier routine: scans classifier chain attached ?? to this qdisc, (optionally) tests for protocol and asks ?? specific classifiers. ?*/ // TC分類, 返回0表示分類成功, 負數表示沒有合適的類, 正數是各種重新操作方法 int tc_classify(struct sk_buff *skb, struct tcf_proto *tp, ?struct tcf_result *res) { ?int err = 0; // 數據包協議, 是以太頭中的協議類型 ?u32 protocol = skb->protocol; #ifdef CONFIG_NET_CLS_ACT ?struct tcf_proto *otp = tp; reclassify: #endif ?protocol = skb->protocol; // 循環tcf_proto鏈表 ?for ( ; tp; tp = tp->next) { ??if ((tp->protocol == protocol || ???tp->protocol == __constant_htons(ETH_P_ALL)) && // 調用tcf_proto的分類算法 ???(err = tp->classify(skb, tp, res)) >= 0) { // 協議符合的情況 #ifdef CONFIG_NET_CLS_ACT // 需要重新分類 ???if ( TC_ACT_RECLASSIFY == err) { // verdict: 對數據包的處理結果 ????__u32 verd = (__u32) G_TC_VERD(skb->tc_verd); ????tp = otp; if (MAX_REC_LOOP < verd++) { ?????printk("rule prio %d protocol %02x reclassify is buggy packet dropped\n", ??????tp->prio&0xffff, ntohs(tp->protocol)); ?????return TC_ACT_SHOT; ????} // ????skb->tc_verd = SET_TC_VERD(skb->tc_verd,verd); ????goto reclassify; ???} else { // 分類成功 // 設置數據包的TC處理結果 if (skb->tc_verd) ?????skb->tc_verd = SET_TC_VERD(skb->tc_verd,0); // 返回分類結果 ????return err; ???} #else 如果內核沒定義CONFIG_NET_CLS_ACT, 直接返回 ???return err; #endif ??} } ?return -1; } 5.4.4 出隊 static struct sk_buff * prio_dequeue(struct Qdisc* sch) { ?struct sk_buff *skb; // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); ?int prio; ?struct Qdisc *qdisc; // 從0號帶開始循環 ?for (prio = 0; prio < q->bands; prio++) { // 該帶的Qdisc ??qdisc = q->queues[prio]; // 執行該qdisc的出隊操作, 應該就是pfifo_fast的出隊操作 ??skb = qdisc->dequeue(qdisc); ??if (skb) { // 取得數據包, prio隊列數減一 ???sch->q.qlen--; ???return skb; ??} ?} ?return NULL; } 由此可見, 0號帶優先權最高, 15號帶最低, 總是高優先級的帶中的數據隊列都清空后才發送低優先 級的帶, 同時每個帶實際有3個隊列(見pfifo_fast), 因此最多可有48個隊列,這樣數據粒度就可以 比較細了,高優先權數據總是在低優先權數據之前發送。 5.4.5 重入隊 static int prio_requeue(struct sk_buff *skb, struct Qdisc* sch) { ?struct Qdisc *qdisc; ?int ret; // 查找該skb對應的qdisc ?qdisc = prio_classify(skb, sch, &ret); #ifdef CONFIG_NET_CLS_ACT ?if (qdisc == NULL) { // 查找失敗丟包 ??if (ret == NET_XMIT_BYPASS) ???sch->qstats.drops++; ??kfree_skb(skb); ??return ret; ?} #endif // 執行該qdisc的重入隊操作, 就是pfifo_fast的requeue ?if ((ret = qdisc->ops->requeue(skb, qdisc)) == NET_XMIT_SUCCESS) { // 統計數更新 ??sch->q.qlen++; ??sch->qstats.requeues++; ??return 0; ?} // 失敗, 丟包 ?sch->qstats.drops++; ?return NET_XMIT_DROP; } 5.4.6 復位 static void prio_reset(struct Qdisc* sch) { ?int prio; ?struct prio_sched_data *q = qdisc_priv(sch); // 循環有效帶數, 不是所有帶 ?for (prio=0; prio<q->bands; prio++) // 標準的qdisc復位操作 ??qdisc_reset(q->queues[prio]); ?sch->q.qlen = 0; } 5.4.7 輸出 static int prio_dump(struct Qdisc *sch, struct sk_buff *skb) { ?struct prio_sched_data *q = qdisc_priv(sch); ?unsigned char? *b = skb->tail; ?struct tc_prio_qopt opt; // 輸出當前的帶數和優先權值到帶值的轉換數組 ?opt.bands = q->bands; ?memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX+1); ?RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt); ?return skb->len; rtattr_failure: ?skb_trim(skb, b - skb->data); ?return -1; } 5.4.8 丟包 static unsigned int prio_drop(struct Qdisc* sch) { ?struct prio_sched_data *q = qdisc_priv(sch); ?int prio; ?unsigned int len; ?struct Qdisc *qdisc; // 倒序操作, 先丟優先權最低的 ?for (prio = q->bands-1; prio >= 0; prio--) { // 該帶的qdisc ??qdisc = q->queues[prio]; // 調用該qdisc的drop函數, 問題是pfifo_fast算法中是沒有drop函數的 ??if (qdisc->ops->drop && (len = qdisc->ops->drop(qdisc)) != 0) { ???sch->q.qlen--; ???return len; ??} ?} ?return 0; } 由于PFIFO_FAST中沒有drop成員函數, 使得這個函數似乎沒意義, 除非進行了嫁接操作, 使用了其他 類型的qdisc作為數組元素 5.4.9 PRIO類別操作 // 嫁接, 替換prio_qdisc中的內部qdisc數組中的qdisc元素 static int prio_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, ??????? struct Qdisc **old) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); ?unsigned long band = arg - 1; // 數組位置超過prio_qidsc中的帶數, 錯誤 ?if (band >= q->bands) ??return -EINVAL; // 如果新qdisc為空,設為noop_qdisc ?if (new == NULL) ??new = &noop_qdisc; sch_tree_lock(sch); // 老的qdisc ?*old = q->queues[band]; // 將該數組位置的qdisc設置為新的qdisc ?q->queues[band] = new; // 將老qdisc的排隊數去除 ?sch->q.qlen -= (*old)->q.qlen; // 復位老qdisc ?qdisc_reset(*old); ?sch_tree_unlock(sch); return 0; } // 返回葉子qdisc static struct Qdisc * prio_leaf(struct Qdisc *sch, unsigned long arg) { // prio私有數組 ?struct prio_sched_data *q = qdisc_priv(sch); ?unsigned long band = arg - 1; // 數組位置超過prio_qidsc中的帶數, 錯誤 ?if (band >= q->bands) ??return NULL; // 返回指定位置的qdisc數組元素 ?return q->queues[band]; } // 將類別ID轉換為帶號 static unsigned long prio_get(struct Qdisc *sch, u32 classid) { ?struct prio_sched_data *q = qdisc_priv(sch); // 取類別ID的低16位 ?unsigned long band = TC_H_MIN(classid); // 如果超過了當前帶數, 返回0 ?if (band - 1 >= q->bands) ??return 0; // 否則作為有效帶值返回 ?return band; } // 綁定, 獲取與類別ID相關的帶值 static unsigned long prio_bind(struct Qdisc *sch, unsigned long parent, u32 classid) { ?return prio_get(sch, classid); } // 釋放, 空函數 static void prio_put(struct Qdisc *q, unsigned long cl) { ?return; } // 修改, 基本是空函數, 沒進行任何修改 static int prio_change(struct Qdisc *sch, u32 handle, u32 parent, struct rtattr **tca, unsigned long *arg) { ?unsigned long cl = *arg; ?struct prio_sched_data *q = qdisc_priv(sch); if (cl - 1 > q->bands) ??return -ENOENT; ?return 0; } // 刪除操作, 基本是空函數, 但沒進行任何實際刪除操作 static int prio_delete(struct Qdisc *sch, unsigned long cl) { ?struct prio_sched_data *q = qdisc_priv(sch); ?if (cl - 1 > q->bands) ??return -ENOENT; ?return 0; } // 輸出類別, cl指定類別 static int prio_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb, ????? struct tcmsg *tcm) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); // 檢查cl是否合法 ?if (cl - 1 > q->bands) ??return -ENOENT; // tc句柄或cl的低16位 ?tcm->tcm_handle |= TC_H_MIN(cl); // 如果Qdisc數組項非空(應該是非空的, 即使不用的也指向noop_qdisc), 保存其句柄值 ?if (q->queues[cl-1]) ??tcm->tcm_info = q->queues[cl-1]->handle; ?return 0; } // PRIO節點遍歷進行某種操作 static void prio_walk(struct Qdisc *sch, struct qdisc_walker *arg) { // 私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); ?int prio; // 是否設置了停止標志 ?if (arg->stop) ??return; // 遍歷有效的帶值 ?for (prio = 0; prio < q->bands; prio++) { // 可以忽略一些元素 ??if (arg->count < arg->skip) { ???arg->count++; ???continue; ??} // 調用指定的操作 ??if (arg->fn(sch, prio+1, arg) < 0) { ???arg->stop = 1; ???break; ??} ??arg->count++; ?} } // 查找協議分類, 返回結果是指針的指針 static struct tcf_proto ** prio_find_tcf(struct Qdisc *sch, unsigned long cl) { // PRIO私有數據 ?struct prio_sched_data *q = qdisc_priv(sch); // 定義了cl的話返回空 ?if (cl) ??return NULL; // 返回filter_list的地址 ?return &q->filter_list; } ...... 待續 ......
|
|