- 論壇徽章:
- 0
|
Linux內(nèi)核中流量控制(7)
本文檔的Copyleft歸yfydz所有,使用GPL發(fā)布,可以自由拷貝,轉(zhuǎn)載,轉(zhuǎn)載時(shí)請(qǐng)保持文檔的完整性,嚴(yán)禁用于任何商業(yè)用途。
msn:
yfydz_no1@hotmail.com
來源:
http://yfydz.cublog.cn
5.7 RED(Random Early Detection queue)
RED算法由Sally Floyd和Van Jacobson提出, 論文為"Random Early Detection
Gateways for Congestion Avoidance", 1993, IEEE/ACM Transactions on
Networking.
基本算法:
對(duì)新數(shù)據(jù)包計(jì)算平均隊(duì)列長度:
平均長度avg=(1-W) * avg + W*當(dāng)前隊(duì)列長度
W是參數(shù), 取值為1/(2^Wlog), Wlog可配置, W越小, 平滑能力越強(qiáng).
算法中兩個(gè)閾值: th_min和th_max, 這兩個(gè)參數(shù)可配置
當(dāng)avg > th_max時(shí), 該新包被丟棄;
當(dāng)avg
當(dāng)th_min
Pb = max_P * (avg - th_min)/(th_max-th_min)
然后按此概率丟包, max_P為一小數(shù), 通常為0.01, 0.02等, 一般在算法中通過右移操作來實(shí)現(xiàn):
max_P = (qth_max-qth_min)>>Plog
Plog為可配置參數(shù)
5.7.1 RED操作結(jié)構(gòu)定義
// RED算法參數(shù)
struct red_parms
{
/* Parameters */
// 最小隊(duì)列長度
u32 qth_min; /* Min avg length threshold: A scaled */
// 最大隊(duì)列長度
u32 qth_max; /* Max avg length threshold: A scaled */
// 最大休眠時(shí)間
u32 Scell_max;
// 保存隨機(jī)掩碼
u32 Rmask; /* Cached random mask, see red_rmask */
//
u8 Scell_log;
// Wlog, Plog參數(shù)含義如上所示
u8 Wlog; /* log(W) */
u8 Plog; /* random number bits */
// 256個(gè)元素
u8 Stab[RED_STAB_SIZE];
/* Variables */
// 以下的參數(shù)是在處理過程中會(huì)改變的參數(shù)
// 從上次隨機(jī)數(shù)產(chǎn)生時(shí)的處理的數(shù)據(jù)包數(shù)
int qcount; /* Number of packets since last random
number generation */
// 緩存的隨機(jī)數(shù)
u32 qR; /* Cached random number */
// 平均隊(duì)列長度
unsigned long qavg; /* Average queue length: A scaled */
// 當(dāng)前休眠起始時(shí)間
psched_time_t qidlestart; /* Start of current idle period */
};
// RED私有數(shù)據(jù)
struct red_sched_data
{
// 最大隊(duì)列長度, 這是硬限制
u32 limit; /* HARD maximal queue length */
// 標(biāo)志
unsigned char flags;
// RED算法參數(shù)
struct red_parms parms;
// RED統(tǒng)計(jì)值
struct red_stats stats;
struct Qdisc *qdisc;
};
// RED流控操作結(jié)構(gòu)
static struct Qdisc_ops red_qdisc_ops = {
.id = "red",
.priv_size = sizeof(struct red_sched_data),
.cl_ops = &red_class_ops,
.enqueue = red_enqueue,
.dequeue = red_dequeue,
.requeue = red_requeue,
.drop = red_drop,
.init = red_init,
.reset = red_reset,
.destroy = red_destroy,
.change = red_change,
.dump = red_dump,
.dump_stats = red_dump_stats,
.owner = THIS_MODULE,
};
// RED類別操作結(jié)構(gòu)
static struct Qdisc_class_ops red_class_ops = {
.graft = red_graft,
.leaf = red_leaf,
.get = red_get,
.put = red_put,
.change = red_change_class,
.delete = red_delete,
.walk = red_walk,
.tcf_chain = red_find_tcf,
.dump = red_dump_class,
};
5.7.2 RED一些基本操作函數(shù)
在include/net/red.h中定義
// 返回Plog對(duì)應(yīng)RED掩碼, 和網(wǎng)絡(luò)掩碼不同,RED掩碼值是從低位開始算的
// 掩碼值位2^Plog-1, , Plog超過31后就和31相同
static inline u32 red_rmask(u8 Plog)
{
return Plog
// 設(shè)置RED參數(shù), 平均長度閾值的最大最小值等
static inline void red_set_parms(struct red_parms *p,
u32 qth_min, u32 qth_max, u8 Wlog, u8 Plog,
u8 Scell_log, u8 *stab)
{
/* Reset average queue length, the value is strictly bound
* to the parameters below, reseting hurts a bit but leaving
* it might result in an unreasonable qavg for a while. --TGR
*/
p->qavg = 0;
// 隊(duì)列元素統(tǒng)計(jì)
p->qcount = -1;
// 內(nèi)部平均長度閾值的最大最小值為設(shè)置值的2^Wlog倍
p->qth_min = qth_min qth_max = qth_max Wlog = Wlog;
p->Plog = Plog;
// 隨機(jī)掩碼
p->Rmask = red_rmask(Plog);
p->Scell_log = Scell_log;
// 最大休眠時(shí)間
p->Scell_max = (255 Stab, stab, sizeof(p->Stab));
}
// 算法是否在休眠狀態(tài), 也就是看qidlestart是否為0, qidlestart非0表示正在休眠
static inline int red_is_idling(struct red_parms *p)
{
return !PSCHED_IS_PASTPERFECT(p->qidlestart);
}
// RED休眠, 將p->qidlestart設(shè)置為當(dāng)前時(shí)間
static inline void red_start_of_idle_period(struct red_parms *p)
{
PSCHED_GET_TIME(p->qidlestart);
}
// RED停止休眠, 將p->qidlestart設(shè)置清零
static inline void red_end_of_idle_period(struct red_parms *p)
{
PSCHED_SET_PASTPERFECT(p->qidlestart);
}
// RED算法重新啟動(dòng)
static inline void red_restart(struct red_parms *p)
{
// RED數(shù)值清零,
red_end_of_idle_period(p);
p->qavg = 0;
p->qcount = -1;
}
// 從休眠恢復(fù)后重新計(jì)算隊(duì)列平均值
static inline unsigned long red_calc_qavg_from_idle_time(struct red_parms *p)
{
psched_time_t now;
long us_idle;
int shift;
// 獲取當(dāng)前時(shí)間
PSCHED_GET_TIME(now);
// 計(jì)算當(dāng)前時(shí)間與休眠時(shí)的時(shí)間差, 也就是計(jì)算休眠了多少時(shí)間
// p->Scell_max是休眠時(shí)間上限
us_idle = PSCHED_TDIFF_SAFE(now, p->qidlestart, p->Scell_max);
/*
* The problem: ideally, average length queue recalcultion should
* be done over constant clock intervals. This is too expensive, so
* that the calculation is driven by outgoing packets.
* When the queue is idle we have to model this clock by hand.
*
* SF+VJ proposed to "generate":
*
* m = idletime / (average_pkt_size / bandwidth)
*
* dummy packets as a burst after idle time, i.e.
*
* p->qavg *= (1-W)^m
*
* This is an apparently overcomplicated solution (f.e. we have to
* precompute a table to make this calculation in reasonable time)
* I believe that a simpler model may be used here,
* but it is field for experiments.
*/
// 根據(jù)休眠數(shù)和Scell_log計(jì)算索引值獲取stab數(shù)組中的偏移值
shift = p->Stab[(us_idle >> p->Scell_log) & RED_STAB_MASK];
if (shift)
// 偏移值非0, 當(dāng)前平均隊(duì)列值左移后返回
return p->qavg >> shift;
else {
/* Approximate initial part of exponent with linear function:
*
* (1-W)^m ~= 1-mW + ...
*
* Seems, it is the best solution to
* problem of too coarse exponent tabulation.
*/
// 重新計(jì)算休眠時(shí)間
us_idle = (p->qavg * (u64)us_idle) >> p->Scell_log;
// 如果休眠時(shí)間數(shù)值小于隊(duì)列長度一半
if (us_idle qavg >> 1))
// 返回隊(duì)列長度減休眠時(shí)間
return p->qavg - us_idle;
else
// 否則返回隊(duì)列長度的一半
return p->qavg >> 1;
}
}
// 非休眠情況下計(jì)算隊(duì)列平均值
static inline unsigned long red_calc_qavg_no_idle_time(struct red_parms *p,
unsigned int backlog)
{
/*
* NOTE: p->qavg is fixed point number with point at Wlog.
* The formula below is equvalent to floating point
* version:
*
* qavg = qavg*(1-W) + backlog*W;
*
* --ANK (980924)
*/
return p->qavg + (backlog - (p->qavg >> p->Wlog));
}
// RED計(jì)算滑動(dòng)隊(duì)列平均值
static inline unsigned long red_calc_qavg(struct red_parms *p,
unsigned int backlog)
{
// 分活動(dòng)狀態(tài)和非活動(dòng)狀態(tài), 分別計(jì)算
if (!red_is_idling(p))
return red_calc_qavg_no_idle_time(p, backlog);
else
return red_calc_qavg_from_idle_time(p);
}
// 生成RED隨機(jī)數(shù)
static inline u32 red_random(struct red_parms *p)
{
return net_random() & p->Rmask;
}
// 概率隨機(jī)決定是否丟包
static inline int red_mark_probability(struct red_parms *p, unsigned long qavg)
{
/* The formula used below causes questions.
OK. qR is random number in the interval 0..Rmask
i.e. 0..(2^Plog). If we used floating point
arithmetics, it would be: (2^Plog)*rnd_num,
where rnd_num is less 1.
Taking into account, that qavg have fixed
point at Wlog, and Plog is related to max_P by
max_P = (qth_max-qth_min)/2^Plog; two lines
below have the following floating point equivalent:
max_P*(qavg - qth_min)/(qth_max-qth_min)
Any questions? --ANK (980924)
*/
// 根據(jù)當(dāng)前隊(duì)列平均值計(jì)算出一個(gè)值是否小于隨機(jī)數(shù)qR來實(shí)現(xiàn)概率丟包
return !(((qavg - p->qth_min) >> p->Wlog) * p->qcount qR);
}
enum {
RED_BELOW_MIN_THRESH,
RED_BETWEEN_TRESH,
RED_ABOVE_MAX_TRESH,
};
// 將當(dāng)前隊(duì)列平均值與閾值進(jìn)行比較
static inline int red_cmp_thresh(struct red_parms *p, unsigned long qavg)
{
// 小于低閾值
if (qavg qth_min)
return RED_BELOW_MIN_THRESH;
// 不小于高閾值
else if (qavg >= p->qth_max)
return RED_ABOVE_MAX_TRESH;
else
// 高低閾值之間
return RED_BETWEEN_TRESH;
}
enum {
// 放行
RED_DONT_MARK,
// 根據(jù)概率標(biāo)記
RED_PROB_MARK,
// 標(biāo)記
RED_HARD_MARK,
};
// RED動(dòng)作判定
static inline int red_action(struct red_parms *p, unsigned long qavg)
{
// 將當(dāng)前平均隊(duì)列值與閾值進(jìn)行比較
switch (red_cmp_thresh(p, qavg)) {
case RED_BELOW_MIN_THRESH:
// 低于低閾值, 放行
p->qcount = -1;
return RED_DONT_MARK;
case RED_BETWEEN_TRESH:
// 高低之間, 按概率標(biāo)記
if (++p->qcount) {
// 是否根據(jù)概率標(biāo)記
if (red_mark_probability(p, qavg)) {
// 標(biāo)記
p->qcount = 0;
p->qR = red_random(p);
return RED_PROB_MARK;
}
} else
p->qR = red_random(p);
// 不標(biāo)記
return RED_DONT_MARK;
case RED_ABOVE_MAX_TRESH:
// 超過高閾值, 直接標(biāo)記
p->qcount = -1;
return RED_HARD_MARK;
}
BUG();
return RED_DONT_MARK;
}
5.7.3 初始化
static int red_init(struct Qdisc* sch, struct rtattr *opt)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 內(nèi)部Qdisc初始化為noop_qdisc
q->qdisc = &noop_qdisc;
// 調(diào)用change函數(shù)進(jìn)行初始化
return red_change(sch, opt);
}
5.7.4 參數(shù)修改
static int red_change(struct Qdisc *sch, struct rtattr *opt)
{
struct red_sched_data *q = qdisc_priv(sch);
struct rtattr *tb[TCA_RED_MAX];
struct tc_red_qopt *ctl;
struct Qdisc *child = NULL;
// 檢查數(shù)據(jù)范圍是否合法
if (opt == NULL || rtattr_parse_nested(tb, TCA_RED_MAX, opt))
return -EINVAL;
if (tb[TCA_RED_PARMS-1] == NULL ||
RTA_PAYLOAD(tb[TCA_RED_PARMS-1])
ctl = RTA_DATA(tb[TCA_RED_PARMS-1]);
// 如果流量限制值有效, 建立RED流控的內(nèi)部缺省流控結(jié)構(gòu), 為一BFIFO流控結(jié)構(gòu)
if (ctl->limit > 0) {
child = red_create_dflt(sch->dev, ctl->limit);
if (child == NULL)
return -ENOMEM;
}
sch_tree_lock(sch);
// 設(shè)置RED流控結(jié)構(gòu)標(biāo)志和流量限制參數(shù)
q->flags = ctl->flags;
q->limit = ctl->limit;
// 對(duì)內(nèi)部流控賦值
if (child)
qdisc_destroy(xchg(&q->qdisc, child));
// 設(shè)置RED流控算法參數(shù)
red_set_parms(&q->parms, ctl->qth_min, ctl->qth_max, ctl->Wlog,
ctl->Plog, ctl->Scell_log,
RTA_DATA(tb[TCA_RED_STAB-1]));
// 如果隊(duì)列空, RED進(jìn)入休眠狀態(tài)
if (skb_queue_empty(&sch->q))
red_end_of_idle_period(&q->parms);
sch_tree_unlock(sch);
return 0;
}
// 建立缺省的RED內(nèi)部Qdisc
static struct Qdisc *red_create_dflt(struct net_device *dev, u32 limit)
{
// 內(nèi)部Qdisc使用的是bfifo, 按字節(jié)數(shù)限制
struct Qdisc *q = qdisc_create_dflt(dev, &bfifo_qdisc_ops);
struct rtattr *rta;
int ret;
if (q) {
rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)),
GFP_KERNEL);
if (rta) {
// 填寫rtattr結(jié)構(gòu), 實(shí)際有效數(shù)據(jù)就是流量限制值limit
rta->rta_type = RTM_NEWQDISC;
rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
// 調(diào)用bfifo的修改函數(shù)設(shè)置bfifo流控結(jié)構(gòu)的limit參數(shù)
ret = q->ops->change(q, rta);
kfree(rta);
if (ret == 0)
return q;
}
qdisc_destroy(q);
}
return NULL;
}
5.7.5 入隊(duì)
static int red_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// RED內(nèi)部流控節(jié)點(diǎn): bfifo
struct Qdisc *child = q->qdisc;
int ret;
// 計(jì)算隊(duì)列滑動(dòng)平均值
q->parms.qavg = red_calc_qavg(&q->parms, child->qstats.backlog);
// 如果在休眠, 停止休眠
if (red_is_idling(&q->parms))
red_end_of_idle_period(&q->parms);
// 根據(jù)隊(duì)列平均值獲取判定結(jié)果
switch (red_action(&q->parms, q->parms.qavg)) {
// 通過
case RED_DONT_MARK:
break;
// 概率標(biāo)記
case RED_PROB_MARK:
sch->qstats.overlimits++;
// 如果沒用ECN擁塞標(biāo)志, 丟包
if (!red_use_ecn(q) || !INET_ECN_set_ce(skb)) {
q->stats.prob_drop++;
goto congestion_drop;
}
// 概率標(biāo)記增加,允許入隊(duì)
q->stats.prob_mark++;
break;
// 直接標(biāo)記
case RED_HARD_MARK:
// overlimits增加
sch->qstats.overlimits++;
// 如果RED設(shè)置HARDDROP標(biāo)志或沒使用ECN, 丟包
if (red_use_harddrop(q) || !red_use_ecn(q) ||
!INET_ECN_set_ce(skb)) {
q->stats.forced_drop++;
goto congestion_drop;
}
// forced_mask增加, 允許入隊(duì)
q->stats.forced_mark++;
break;
}
// 調(diào)度內(nèi)部流控的入隊(duì)函數(shù)
ret = child->enqueue(skb, child);
// 根據(jù)入隊(duì)是否成功更新相關(guān)統(tǒng)計(jì)
if (likely(ret == NET_XMIT_SUCCESS)) {
sch->bstats.bytes += skb->len;
sch->bstats.packets++;
sch->q.qlen++;
} else {
q->stats.pdrop++;
sch->qstats.drops++;
}
return ret;
congestion_drop:
// 擁塞丟包處理
qdisc_drop(skb, sch);
return NET_XMIT_CN;
}
5.7.6 重入隊(duì)
static int red_requeue(struct sk_buff *skb, struct Qdisc* sch)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 內(nèi)部流控
struct Qdisc *child = q->qdisc;
int ret;
// 如果在休眠, 停止休眠
if (red_is_idling(&q->parms))
red_end_of_idle_period(&q->parms);
// 使用內(nèi)部流控的重入隊(duì)函數(shù)
ret = child->ops->requeue(skb, child);
// 成功的話更新統(tǒng)計(jì)數(shù)據(jù)
if (likely(ret == NET_XMIT_SUCCESS)) {
sch->qstats.requeues++;
sch->q.qlen++;
}
return ret;
}
5.7.7 出隊(duì)
static struct sk_buff * red_dequeue(struct Qdisc* sch)
{
struct sk_buff *skb;
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 內(nèi)部流控
struct Qdisc *child = q->qdisc;
// 調(diào)用內(nèi)部流控的出隊(duì)函數(shù)
skb = child->dequeue(child);
// 如果出隊(duì)成功, RED隊(duì)列長度減1, 返回?cái)?shù)據(jù)包
if (skb)
sch->q.qlen--;
else if (!red_is_idling(&q->parms))
// 否則隊(duì)列空, RED進(jìn)入休眠狀態(tài)
red_start_of_idle_period(&q->parms);
return skb;
}
5.7.8 丟包
static unsigned int red_drop(struct Qdisc* sch)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 內(nèi)部流控
struct Qdisc *child = q->qdisc;
unsigned int len;
// 如果內(nèi)部流控結(jié)構(gòu)定義的丟包函數(shù), 執(zhí)行該丟包函數(shù), 更新統(tǒng)計(jì)返回
if (child->ops->drop && (len = child->ops->drop(child)) > 0) {
q->stats.other++;
sch->qstats.drops++;
sch->q.qlen--;
return len;
}
// 否則使RED進(jìn)入休眠狀態(tài)
if (!red_is_idling(&q->parms))
red_start_of_idle_period(&q->parms);
return 0;
}
5.7.9 復(fù)位
// 就是內(nèi)部流控的復(fù)位函數(shù), RED隊(duì)列長度清零
static void red_reset(struct Qdisc* sch)
{
struct red_sched_data *q = qdisc_priv(sch);
qdisc_reset(q->qdisc);
sch->q.qlen = 0;
red_restart(&q->parms);
}
5.7.10 釋放
// 就是內(nèi)部流控的釋放函數(shù)
static void red_destroy(struct Qdisc *sch)
{
struct red_sched_data *q = qdisc_priv(sch);
qdisc_destroy(q->qdisc);
}
5.7.11 輸出參數(shù)
static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
struct rtattr *opts = NULL;
// 填寫RED參數(shù)結(jié)構(gòu)
struct tc_red_qopt opt = {
.limit = q->limit,
.flags = q->flags,
.qth_min = q->parms.qth_min >> q->parms.Wlog,
.qth_max = q->parms.qth_max >> q->parms.Wlog,
.Wlog = q->parms.Wlog,
.Plog = q->parms.Plog,
.Scell_log = q->parms.Scell_log,
};
// 將參數(shù)拷貝到skb數(shù)據(jù)區(qū)
opts = RTA_NEST(skb, TCA_OPTIONS);
RTA_PUT(skb, TCA_RED_PARMS, sizeof(opt), &opt);
// 發(fā)送數(shù)據(jù)包
return RTA_NEST_END(skb, opts);
rtattr_failure:
return RTA_NEST_CANCEL(skb, opts);
}
5.7.12 輸出統(tǒng)計(jì)值
static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 構(gòu)造標(biāo)準(zhǔn)TC統(tǒng)計(jì)結(jié)構(gòu), 填寫相關(guān)參數(shù)
struct tc_red_xstats st = {
// 提早丟包數(shù): 概率丟和強(qiáng)迫丟
.early = q->stats.prob_drop + q->stats.forced_drop,
// 丟包數(shù)
.pdrop = q->stats.pdrop,
// 其他
.other = q->stats.other,
// 被標(biāo)記的包數(shù)
.marked = q->stats.prob_mark + q->stats.forced_mark,
};
// 返回
return gnet_stats_copy_app(d, &st, sizeof(st));
}
5.7.13 RED類別操作
// 輸出分類
static int red_dump_class(struct Qdisc *sch, unsigned long cl,
struct sk_buff *skb, struct tcmsg *tcm)
{
struct red_sched_data *q = qdisc_priv(sch);
if (cl != 1)
return -ENOENT;
// 設(shè)置tcm參數(shù):
// handle或1
tcm->tcm_handle |= TC_H_MIN(1);
// 信息為內(nèi)部流控handle
tcm->tcm_info = q->qdisc->handle;
return 0;
}
// 嫁接, 增加葉子qdisc
static int red_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
struct Qdisc **old)
{
// RED私有數(shù)據(jù)
struct red_sched_data *q = qdisc_priv(sch);
// 如果沒定義新流控, 用noop_qdisc
if (new == NULL)
new = &noop_qdisc;
sch_tree_lock(sch);
// 將當(dāng)前RED內(nèi)部流控和新流控結(jié)構(gòu)指針對(duì)換
*old = xchg(&q->qdisc, new);
// 復(fù)位老流控結(jié)構(gòu)
qdisc_reset(*old);
// 流控隊(duì)列長度清零
sch->q.qlen = 0;
sch_tree_unlock(sch);
return 0;
}
// 獲取葉子流控節(jié)點(diǎn)
static struct Qdisc *red_leaf(struct Qdisc *sch, unsigned long arg)
{
struct red_sched_data *q = qdisc_priv(sch);
// 返回RED內(nèi)部流控: bfifo
return q->qdisc;
}
// 引用計(jì)數(shù)
static unsigned long red_get(struct Qdisc *sch, u32 classid)
{
return 1;
}
// 釋放計(jì)數(shù),空函數(shù)
static void red_put(struct Qdisc *sch, unsigned long arg)
{
return;
}
// 更改類別, 無定義
static int red_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
struct rtattr **tca, unsigned long *arg)
{
return -ENOSYS;
}
// 刪除節(jié)點(diǎn), 無定義
static int red_delete(struct Qdisc *sch, unsigned long cl)
{
return -ENOSYS;
}
// 遍歷
static void red_walk(struct Qdisc *sch, struct qdisc_walker *walker)
{
// 其實(shí)也說不上遍歷, 因?yàn)榫椭粓?zhí)行一次
if (!walker->stop) {
if (walker->count >= walker->skip)
if (walker->fn(sch, 1, walker) stop = 1;
return;
}
walker->count++;
}
}
// 查找分類過濾規(guī)則, 空函數(shù)
static struct tcf_proto **red_find_tcf(struct Qdisc *sch, unsigned long cl)
{
return NULL;
}
...... 待續(xù) ......
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u/33048/showart_2094269.html |
|