免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 979 | 回复: 0

Linux内核中流量控制(8) [复制链接]

论坛徽章:
0
发表于 2009-11-14 00:55 |显示全部楼层
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 DPs; i++) {
  struct gred_sched_data *q = table->tab;
  int n;
  if (q == NULL)
   continue;
  for (n = 0; n 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)
// 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 tab) {
   printk(KERN_WARNING "GRED: Warning: Destroying "
          "shadowed VQ 0x%x\n", i);
   gred_destroy_vq(table->tab);
   table->tab = 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])
// 控制数据
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
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) 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 DPs; i++) {
// 如果表项的prio小于当前表项的prio而且是非休眠状态
   if (t->tab && t->tab->prio prio &&
       !red_is_idling(&t->tab->parms))
// 累加平均队列长度
    qavg +=t->tab->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 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 DPs; i++) {
  struct gred_sched_data *q = t->tab;
  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 DPs; i++) {
  if (table->tab)
   gred_destroy_vq(table->tab);
}
}

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 tab;
// 准备填写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) stop = 1;
    return;
   }
  walker->count++;
}
}
// 查找分类过滤规则, 空函数
static struct tcf_proto **red_find_tcf(struct Qdisc *sch, unsigned long cl)
{
return NULL;
}
...... 待续 ......
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/33048/showart_2094270.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP