Linux内核中流量控制(8)

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

5.8 GRED(Generic Random Early Detection queue)

GRED算法是GRED的通用化,还是以RED算法为基础,但不再是根据一个RED流控节点计算丢包情况,而
是可以定义多个虚拟RED流控节点,然后根据skb数据包中的tc_index参数将数据分配到不同的节点,
每个节点都按RED算法进行流控。之所以叫虚拟队列,因为实际的队列数据结构还是只有一个队列,
但每个skb包的入队出队是由不同的RED流控结构控制的。
GRED和RED的关系就类似PRIO和pfifo_fast的关系,但队列只是一个。

5.8.1 GRED操作结构定义

// GRED算法参数
struct gred_sched_data
{
// 流量限制值
u32 limit; /* HARD maximal queue length */
u32 DP; /* the drop pramaters */
// 该虚拟队列看到的字节数和包数
u32 bytesin; /* bytes seen on virtualQ so far*/
u32 packetsin; /* packets seen on virtualQ so far*/
// 队列中正在等待的数据长度
u32 backlog; /* bytes on the virtualQ */
// 该虚拟队列优先级
u8 prio; /* the prio of this vq */
// RED算法参数和统计结构
struct red_parms parms;
struct red_stats stats;
};

// WRED模式或RIO模式
enum {
// WRED模式是处理有相同的PRIO的不同虚拟队列的情况
GRED_WGRED_MODE = 1,
// RIO应该就是PRIO吧, 队列优先级有效
GRED_RIO_MODE,
};

// GRED私有数据结构
struct gred_sched
{
// 最大MAX_DPs(16)个GRED参数项, 每个相当于一个RED虚拟队列
struct gred_sched_data *tab[MAX_DPs];
unsigned long flags; // 包括WRED和RIO标志
u32 red_flags; // RED算法标志, 包括ECN和HARDDROP
u32 DPs; // DP的数量, 有效的tab的数量, 小于16
u32 def; // 缺省DP
struct red_parms wred_set; // RED算法总体参数
};

// GRED流控操作结构
static struct Qdisc_ops gred_qdisc_ops = {
.id = "gred",
.priv_size = sizeof(struct gred_sched),
.enqueue = gred_enqueue,
.dequeue = gred_dequeue,
.requeue = gred_requeue,
.drop = gred_drop,
.init = gred_init,
.reset = gred_reset,
.destroy = gred_destroy,
.change = gred_change,
.dump = gred_dump,
.owner = THIS_MODULE,
};

GRED没有定义类别操作结构

5.8.1 GRED一些操作函数

// 检查是否设置WRED模式, 检测WRED位是否设置
static inline int gred_wred_mode(struct gred_sched *table)
{
return test_bit(GRED_WRED_MODE, &table->flags);
}

// 打开WRED模式
static inline void gred_enable_wred_mode(struct gred_sched *table)
{
__set_bit(GRED_WRED_MODE, &table->flags);
}

// 关闭WRED模式
static inline void gred_disable_wred_mode(struct gred_sched *table)
{
__clear_bit(GRED_WRED_MODE, &table->flags);
}

// 检测是否设置RIO模式, 检测RIO位是否设置
static inline int gred_rio_mode(struct gred_sched *table)
{
return test_bit(GRED_RIO_MODE, &table->flags);
}

// 打开RIO模式
static inline void gred_enable_rio_mode(struct gred_sched *table)
{
__set_bit(GRED_RIO_MODE, &table->flags);
}

// 关闭RIO模式
static inline void gred_disable_rio_mode(struct gred_sched *table)
{
__clear_bit(GRED_RIO_MODE, &table->flags);
}

// WRED模式检查
static inline int gred_wred_mode_check(struct Qdisc *sch)
{
// GRED私有数据
struct gred_sched *table = qdisc_priv(sch);
int i;
// 两层循环比较不同的表项的prio值是否相同
/* Really ugly O(n^2) but shouldn't be necessary too frequent. */
for (i = 0; i < table->DPs; i++) {
struct gred_sched_data *q = table->tab[i];
int n;
if (q == NULL)
continue;
for (n = 0; n < table->DPs; n++)
if (table->tab[n] && table->tab[n] != q &&
table->tab[n]->prio == q->prio)
// 有prio相同的不同表项返回1
return 1;
}
// prio值都不同返回0
return 0;
}

// GRED等待队列值
static inline unsigned int gred_backlog(struct gred_sched *table,
struct gred_sched_data *q,
struct Qdisc *sch)
{
// WRED模式下使用qdisc的统计值
if (gred_wred_mode(table))
return sch->qstats.backlog;
else
// 否则返回GRED的backlog
return q->backlog;
}

// 将skb包的tc_index转换为DP索引值
static inline u16 tc_index_to_dp(struct sk_buff *skb)
{
// 直接和GRED_VQ_MASK(MAX_DPs - 1)相与, 不知道为什么不用%, 这样就不必限制
// MAX_DPs是2的整数幂
return skb->tc_index & GRED_VQ_MASK;
}

// 加载RED参数, 从gred_sched到gred_sched_data
static inline void gred_load_wred_set(struct gred_sched *table,
struct gred_sched_data *q)
{
// 平均队列值
q->parms.qavg = table->wred_set.qavg;
// 休眠起始时间
q->parms.qidlestart = table->wred_set.qidlestart;
}

// 恢复RED参数, 从gred_sched_data到gred_sched
static inline void gred_store_wred_set(struct gred_sched *table,
struct gred_sched_data *q)
{
table->wred_set.qavg = q->parms.qavg;
}

// 检查RED算法是否使用ECN, 检查TC_RED_ECN位
static inline int gred_use_ecn(struct gred_sched *t)
{
return t->red_flags & TC_RED_ECN;
}

// 检查RED算法是否使用HARDDROP, 检查TC_RED_HARDDROP位
static inline int gred_use_harddrop(struct gred_sched *t)
{
return t->red_flags & TC_RED_HARDDROP;
}

5.8.3 初始化

static int gred_init(struct Qdisc *sch, struct rtattr *opt)
{
struct rtattr *tb[TCA_GRED_MAX];
// 输入参数检查并解析
if (opt == NULL || rtattr_parse_nested(tb, TCA_GRED_MAX, opt))
return -EINVAL;
// 不能有TCA_GRED_PARAMS和TCA_GRED_STAB类型数据
if (tb[TCA_GRED_PARMS-1] || tb[TCA_GRED_STAB-1])
return -EINVAL;
// 参数修改, 针对TCA_GRED_DPS类型数据
return gred_change_table_def(sch, tb[TCA_GRED_DPS-1]);
}

static inline int gred_change_table_def(struct Qdisc *sch, struct rtattr *dps)
{
// GRED私有数据,
struct gred_sched *table = qdisc_priv(sch);
// 这里是sopt, 针对配置的
struct tc_gred_sopt *sopt;
int i;
// 数据合法性检查
if (dps == NULL || RTA_PAYLOAD(dps) < sizeof(*sopt))
return -EINVAL;

// TC输入的GRED设置(setup)相关选项参数
sopt = RTA_DATA(dps);

// DPs参数检查, DPs范围为(0, MAX_DPs), 不过为什么要32位数呢, 8位就够了
if (sopt->DPs > MAX_DPs || sopt->DPs == 0 || sopt->def_DP >= sopt->DPs)
return -EINVAL;
sch_tree_lock(sch);
// GRED基本参数设置
table->DPs = sopt->DPs;
table->def = sopt->def_DP;
table->red_flags = sopt->flags;
/*
* Every entry point to GRED is synchronized with the above code
* and the DP is checked against DPs, i.e. shadowed VQs can no
* longer be found so we can unlock right here.
*/
sch_tree_unlock(sch);
if (sopt->grio) {
// 打开RIO模式, 关闭WRED模式
gred_enable_rio_mode(table);
gred_disable_wred_mode(table);
// 不同的tab表项的prio参数相同时也打开WRED模式, 这时两个标志都被设置了
if (gred_wred_mode_check(sch))
gred_enable_wred_mode(table);
} else {
// 关闭RIO和WRED模式
gred_disable_rio_mode(table);
gred_disable_wred_mode(table);
}
// 释放多余的GRED结构表项
for (i = table->DPs; i < MAX_DPs; i++) {
if (table->tab[i]) {
printk(KERN_WARNING "GRED: Warning: Destroying "
"shadowed VQ 0x%x\n", i);
gred_destroy_vq(table->tab[i]);
table->tab[i] = NULL;
}
}
// 但注意的是没有对 0~DPs-1 表项进行初始化
return 0;
}

// 释放虚拟队列项
static inline void gred_destroy_vq(struct gred_sched_data *q)
{
// 直接释放空间
kfree(q);
}

5.8.4 参数修改

// 一次调用只修改一个DP表项
static int gred_change(struct Qdisc *sch, struct rtattr *opt)
{
// GRED私有数据
struct gred_sched *table = qdisc_priv(sch);
// 注意这里的选项是qopt, 不再是sopt, 是针对队列的
struct tc_gred_qopt *ctl;
struct rtattr *tb[TCA_GRED_MAX];
// PRIO初始化为缺省值
int err = -EINVAL, prio = GRED_DEF_PRIO;
u8 *stab;
// 参数检查
if (opt == NULL || rtattr_parse_nested(tb, TCA_GRED_MAX, opt))
return -EINVAL;
// 没有TCA_GRED_STAB和TCA_GRED_PARMS类型数据是就只是设置sopt
if (tb[TCA_GRED_PARMS-1] == NULL && tb[TCA_GRED_STAB-1] == NULL)
return gred_change_table_def(sch, opt);
// 检查参数合法性
if (tb[TCA_GRED_PARMS-1] == NULL ||
RTA_PAYLOAD(tb[TCA_GRED_PARMS-1]) < sizeof(*ctl) ||
tb[TCA_GRED_STAB-1] == NULL ||
RTA_PAYLOAD(tb[TCA_GRED_STAB-1]) < 256)
return -EINVAL;
// 控制数据
ctl = RTA_DATA(tb[TCA_GRED_PARMS-1]);
// STAB数据
stab = RTA_DATA(tb[TCA_GRED_STAB-1]);
// 参数中的DP数不能超过当前有效DP数
if (ctl->DP >= table->DPs)
goto errout;
// 如果是RIO模式, 更新prio参数
if (gred_rio_mode(table)) {
// 如果没提供prio参数
if (ctl->prio == 0) {
// 先使用缺省缺省prio
int def_prio = GRED_DEF_PRIO;
// 如果有缺省GRED表项, 使用其prio
if (table->tab[table->def])
def_prio = table->tab[table->def]->prio;
printk(KERN_DEBUG "GRED: DP %u does not have a prio "
"setting default to %d\n", ctl->DP, def_prio);
prio = def_prio;
} else
// 提供了prio的话就用此prio参数
prio = ctl->prio;
}
sch_tree_lock(sch);
// 更新DP位置表项的RED参数
err = gred_change_vq(sch, ctl->DP, ctl, prio, stab);
if (err < 0)
goto errout_locked;
// 如果是RIO模式, 关闭WRED模式
if (gred_rio_mode(table)) {
gred_disable_wred_mode(table);
// 不同的tab表项的prio参数相同时再打开WRED模式
if (gred_wred_mode_check(sch))
gred_enable_wred_mode(table);
}
err = 0;
errout_locked:
sch_tree_unlock(sch);
errout:
return err;
}

// 虚拟队列修改, 修改DP表项参数
static inline int gred_change_vq(struct Qdisc *sch, int dp,
struct tc_gred_qopt *ctl, int prio, u8 *stab)
{
// GRED私有数据, DP表
struct gred_sched *table = qdisc_priv(sch);
struct gred_sched_data *q;
// 如果dp位置表项为空,先分配空间
if (table->tab[dp] == NULL) {
table->tab[dp] = kzalloc(sizeof(*q), GFP_KERNEL);
if (table->tab[dp] == NULL)
return -ENOMEM;
}
// 参数赋值
q = table->tab[dp];
q->DP = dp;
q->prio = prio;
q->limit = ctl->limit;
// 如果当前队列为空, 结束休眠
if (q->backlog == 0)
red_end_of_idle_period(&q->parms);
// 设置RED算法的基本参数
red_set_parms(&q->parms,
ctl->qth_min, ctl->qth_max, ctl->Wlog, ctl->Plog,
ctl->Scell_log, stab);
return 0;
}

5.8.5 入队

static int gred_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
// 用来指向tab表项, 即虚拟队列
struct gred_sched_data *q=NULL;
// GRED私有数据
struct gred_sched *t= qdisc_priv(sch);
unsigned long qavg = 0;
// 根据skb的tc_index参数获取dp
u16 dp = tc_index_to_dp(skb);
// 如果dp超过有效表项数或该表项指针为空
if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
// 使用缺省dp流控节点
dp = t->def;
// 如果该缺省dp处的表项为空, 即没有缺省RED流控节点
if ((q = t->tab[dp]) == NULL) {
/* Pass through packets not assigned to a DP
* if no default DP has been configured. This
* allows for DP flows to be left untouched.
*/
// 如果队列没满
if (skb_queue_len(&sch->q) < sch->dev->tx_queue_len)
// 直接添加到流控节点队列末尾,不用进行RED流控
return qdisc_enqueue_tail(skb, sch);
else
// 否则丢弃
goto drop;
}
/* fix tc_index? --could be controvesial but needed for
requeueing */
// 表项非空, 更新数据包的tc_index参数, 准备进行RED流控计算
skb->tc_index = (skb->tc_index & ~GRED_VQ_MASK) | dp;
}
/* sum up all the qaves of prios <= to ours to get the new qave */
// PRIO模式而且非WRED模式下, 将所有PRIO值小于当前表项的PRIO的所有RED节点
// 的平均队列值累加作为平均队列长度
if (!gred_wred_mode(t) && gred_rio_mode(t)) {
int i;
// 遍历所有有效表项
for (i = 0; i < t->DPs; i++) {
// 如果表项的prio小于当前表项的prio而且是非休眠状态
if (t->tab[i] && t->tab[i]->prio < q->prio &&
!red_is_idling(&t->tab[i]->parms))
// 累加平均队列长度
qavg +=t->tab[i]->parms.qavg;
}
}
// 更新统计数据
q->packetsin++;
q->bytesin += skb->len;
// 如果是WRED模式, t赋值到q, 使用总体的RED参数
if (gred_wred_mode(t))
gred_load_wred_set(t, q);
// 计算队列平均值
q->parms.qavg = red_calc_qavg(&q->parms, gred_backlog(t, q, sch));
// 如果在休眠, 停止休眠, 因为有数据了
if (red_is_idling(&q->parms))
red_end_of_idle_period(&q->parms);
// 如果是WRED模式, t赋值到q, 使用总体的RED参数
if (gred_wred_mode(t))
gred_store_wred_set(t, q);
// 根据平均队列长度计算RED算法动作结果
switch (red_action(&q->parms, q->parms.qavg + qavg)) {
// 允许
case RED_DONT_MARK:
break;
case RED_PROB_MARK:
// 概率标记
sch->qstats.overlimits++;
// 如果没用ECN拥塞标志, 丢包
if (!gred_use_ecn(t) || !INET_ECN_set_ce(skb)) {
q->stats.prob_drop++;
goto congestion_drop;
}
// 允许入队
q->stats.prob_mark++;
break;
case RED_HARD_MARK:
// 必须标记
sch->qstats.overlimits++;
// 如果GRED设置HARDDROP标志或没使用ECN, 丢包
if (gred_use_harddrop(t) || !gred_use_ecn(t) ||
!INET_ECN_set_ce(skb)) {
q->stats.forced_drop++;
goto congestion_drop;
}
// 允许入队
q->stats.forced_mark++;
break;
}
// 如果当前虚拟队列中的数据长度不超过限制, 添加到数据包队列末尾
if (q->backlog + skb->len <= q->limit) {
q->backlog += skb->len;
return qdisc_enqueue_tail(skb, sch);
}
// 否则丢包
q->stats.pdrop++;
drop:
return qdisc_drop(skb, sch);
congestion_drop:
// 拥塞情况也丢包
qdisc_drop(skb, sch);
return NET_XMIT_CN;
}

5.8.6 重入队

static int gred_requeue(struct sk_buff *skb, struct Qdisc* sch)
{
// GRED私有数据
struct gred_sched *t = qdisc_priv(sch);
struct gred_sched_data *q;
// 根据skb的tc_index参数获取dp
u16 dp = tc_index_to_dp(skb);
// 如果dp超过有效表项数或者对应位置的表项为空, 打印警告信息
if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
if (net_ratelimit())
printk(KERN_WARNING "GRED: Unable to relocate VQ 0x%x "
"for requeue, screwing up backlog.\n",
tc_index_to_dp(skb));
} else {
// 否则停止休眠, 数据量增加
if (red_is_idling(&q->parms))
red_end_of_idle_period(&q->parms);
q->backlog += skb->len;
}
// 进行标准的重入队操作, 直接添加到数据队列尾, 因为GRED实际只有一个数据队列
return qdisc_requeue(skb, sch);
}

5.8.7 出队

static struct sk_buff *gred_dequeue(struct Qdisc* sch)
{
struct sk_buff *skb;
// GRED私有数据
struct gred_sched *t = qdisc_priv(sch);
// 从数据队列中取一个数据包
skb = qdisc_dequeue_head(sch);
if (skb) {
struct gred_sched_data *q;
// 根据skb的tc_index参数获取dp表项索引的虚拟队列
u16 dp = tc_index_to_dp(skb);
// 如果dp超过有效表项数或者对应位置的表项为空, 打印警告信息
if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
if (net_ratelimit())
printk(KERN_WARNING "GRED: Unable to relocate "
"VQ 0x%x after dequeue, screwing up "
"backlog.\n", tc_index_to_dp(skb));
} else {
// 更新该虚拟队列RED参数
// 减少队列数据量
q->backlog -= skb->len;
// 如果没数据而且非WRED模式, 进行休眠
if (!q->backlog && !gred_wred_mode(t))
red_start_of_idle_period(&q->parms);
}
// 返回数据包
return skb;
}
// 数据队列没数据包了, WRED模式下进入休眠
if (gred_wred_mode(t) && !red_is_idling(&t->wred_set))
red_start_of_idle_period(&t->wred_set);
return NULL;
}

5.8.8 丢包

static unsigned int gred_drop(struct Qdisc* sch)
{
struct sk_buff *skb;
// GRED私有数据
struct gred_sched *t = qdisc_priv(sch);
// 从数据队列中取一个数据包
skb = qdisc_dequeue_tail(sch);
if (skb) {
// 取到数据包
unsigned int len = skb->len;
struct gred_sched_data *q;
// 根据skb包的tc_index转换为DP索引值
u16 dp = tc_index_to_dp(skb);
// 如果DP值非法或该表项为空, 打印警告信息
if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
if (net_ratelimit())
printk(KERN_WARNING "GRED: Unable to relocate "
"VQ 0x%x while dropping, screwing up "
"backlog.\n", tc_index_to_dp(skb));
} else {
// 该虚拟队列数据更新
// 统计值更新
q->backlog -= len;
q->stats.other++;
// 如果等待队列已经空了, 在WRED模式下启动休眠
if (!q->backlog && !gred_wred_mode(t))
red_start_of_idle_period(&q->parms);
}
// 丢弃数据包
qdisc_drop(skb, sch);
return len;
}
// 队列空, 在WRED模式下启动休眠
if (gred_wred_mode(t) && !red_is_idling(&t->wred_set))
red_start_of_idle_period(&t->wred_set);
return 0;
}


5.8.9 复位

static void gred_reset(struct Qdisc* sch)
{
int i;
struct gred_sched *t = qdisc_priv(sch);
// 标准队列复位
qdisc_reset_queue(sch);
// 遍历所有DP
for (i = 0; i < t->DPs; i++) {
struct gred_sched_data *q = t->tab[i];
if (!q)
continue;
// 重新启动每个子RED结构
red_restart(&q->parms);
// 虚拟队列等待队列计数清零
q->backlog = 0;
}
}

5.8.10 释放

static void gred_destroy(struct Qdisc *sch)
{
struct gred_sched *table = qdisc_priv(sch);
int i;
// 释放所有DP表项
// 最好重新赋值为NULL
for (i = 0; i < table->DPs; i++) {
if (table->tab[i])
gred_destroy_vq(table->tab[i]);
}
}

5.8.11 输出参数

static int gred_dump(struct Qdisc *sch, struct sk_buff *skb)
{
struct gred_sched *table = qdisc_priv(sch);
struct rtattr *parms, *opts = NULL;
int i;
// 向TC输出的GRED算法参数选项结构
struct tc_gred_sopt sopt = {
.DPs = table->DPs,
.def_DP = table->def,
.grio = gred_rio_mode(table),
.flags = table->red_flags,
};
// 将sopt参数填到数据包
opts = RTA_NEST(skb, TCA_OPTIONS);
RTA_PUT(skb, TCA_GRED_DPS, sizeof(sopt), &sopt);
parms = RTA_NEST(skb, TCA_GRED_PARMS);
// 遍历DP表项
for (i = 0; i < MAX_DPs; i++) {
struct gred_sched_data *q = table->tab[i];
// 准备填写GRED的qopt
struct tc_gred_qopt opt;
memset(&opt, 0, sizeof(opt));
if (!q) {
/* hack -- fix at some point with proper message
This is how we indicate to tc that there is no VQ
at this DP */
// 对于空表项, DP值设置为超过MAX_DPs的值, 其他参数都为0
opt.DP = MAX_DPs + i;
goto append_opt;
}
// 填写qopt参数
opt.limit = q->limit;
opt.DP = q->DP;
opt.backlog = q->backlog;
opt.prio = q->prio;
opt.qth_min = q->parms.qth_min >> q->parms.Wlog;
opt.qth_max = q->parms.qth_max >> q->parms.Wlog;
opt.Wlog = q->parms.Wlog;
opt.Plog = q->parms.Plog;
opt.Scell_log = q->parms.Scell_log;
opt.other = q->stats.other;
opt.early = q->stats.prob_drop;
opt.forced = q->stats.forced_drop;
opt.pdrop = q->stats.pdrop;
opt.packets = q->packetsin;
opt.bytesin = q->bytesin;
if (gred_wred_mode(table)) {
q->parms.qidlestart =
table->tab[table->def]->parms.qidlestart;
q->parms.qavg = table->tab[table->def]->parms.qavg;
}
// 计算平均队列
opt.qave = red_calc_qavg(&q->parms, q->parms.qavg);
append_opt:
// 将qopt参数填到数据包中
RTA_APPEND(skb, sizeof(opt), &opt);
}
RTA_NEST_END(skb, parms);
// 数据包返回, 包括sopt和MAX_DPs个qopt
return RTA_NEST_END(skb, opts);
rtattr_failure:
return RTA_NEST_CANCEL(skb, opts);
}


5.8.13 GRED类别操作

// 输出分类
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;
// 设置tcm参数:
// handle或1
tcm->tcm_handle |= TC_H_MIN(1);
// 信息为内部流控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)
{
// GRED私有数据
struct red_sched_data *q = qdisc_priv(sch);
// 如果没定义新流控, 用noop_qdisc
if (new == NULL)
new = &noop_qdisc;
sch_tree_lock(sch);
// 将当前GRED内部流控和新流控结构指针对换
*old = xchg(&q->qdisc, new);
// 复位老流控结构
qdisc_reset(*old);
// 流控队列长度清零
sch->q.qlen = 0;
sch_tree_unlock(sch);
return 0;
}

// 获取叶子流控节点
static struct Qdisc *red_leaf(struct Qdisc *sch, unsigned long arg)
{
struct red_sched_data *q = qdisc_priv(sch);
// 返回GRED内部流控: bfifo
return q->qdisc;
}

// 引用计数
static unsigned long red_get(struct Qdisc *sch, u32 classid)
{
return 1;
}
// 释放计数,空函数
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;
}
// 删除节点, 无定义
static int red_delete(struct Qdisc *sch, unsigned long cl)
{
return -ENOSYS;
}

// 遍历
static void red_walk(struct Qdisc *sch, struct qdisc_walker *walker)
{
// 其实也说不上遍历, 因为就只执行一次
if (!walker->stop) {
if (walker->count >= walker->skip)
if (walker->fn(sch, 1, walker) < 0) {
walker->stop = 1;
return;
}
walker->count++;
}
}
// 查找分类过滤规则, 空函数
static struct tcf_proto **red_find_tcf(struct Qdisc *sch, unsigned long cl)
{
return NULL;
}

...... 待续 ......

发表于: 2007-08-18,修改于: 2007-08-18 11:52,已浏览2576次,有评论4条 推荐 投诉
网友: fcgao 时间:2007-10-29 17:58:28 IP地址:218.80.238.★


你好, WRED算法和GRED算法有什么关系嘛?


网友: yfydz 时间:2007-10-30 13:00:55 IP地址:218.247.216.★


W是指什么?weight?本质应该差不多


网友: fcgao 时间:2007-10-30 13:31:55 IP地址:218.80.238.★


W指的就是Weight,在GRED中存在WRED模式和RIO模式,按照上面的解释,只在优先级相同的VQ中执行WRED模式,这个是什么意思,如果优先级相同,靠什么实现“Weight”。我还没有看完整个过程,只是对这些个概念(WRED模式/RIO模式,GRED实现)不是很明确它们的关系。

我感觉RIO模式才是WRED实现,使用优先级作为Weight。不知道我的理解是否正确?


网友: yfydz 时间:2007-10-31 13:03:08 IP地址:218.247.216.★


这东西光看代码的确不容易理解作者是怎么想的,也得自己实验看看
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值