`

Linux内核中流量控制(4)

阅读更多
本文档的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;
}
 
...... 待续 ......

发表于: 2007-08-02,修改于: 2007-08-02 22:08,已浏览2757次,有评论3条 推荐 投诉
	网友: 本站网友 	时间:2007-08-20 13:53:14 IP地址:222.68.182.★
	

请问能具体解释一下tc_classify返回正数时代表什么意思吗?谢谢


	网友: yfydz 	时间:2007-08-25 21:38:33 IP地址:123.116.96.★
	

分类操作成功

分享到:
评论

相关推荐

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    Linux高级路由和流量控制

    15.8. 终极的流量控制:低延迟,高速上/下载 98 15.8.1. 为什么缺省设置不让人满意 99 15.8.2. 实际的脚本(CBQ) 100 15.8.3. 实际的脚本(HTB) 102 15.9. 为单个主机或子网限速 103 15.10. 一个完全NAT和QOS的范例...

    基于Linux LQL流量控制系统的研究与实现

    该方法摒弃了传统方法中所运用的TC命令解析,netlink传输,内核空间执行的3层结构,而直接在Linux内核的框架下,采用LQL库直接对内核进行操控,并改进了相关U32过滤器以对IP段的流量控制,从而实现对系统的智能流量控制。...

    编写Linuxc操作系统设备驱动程序概述

    在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。Linux的网络系统主要是基于BSD unix的socket机制 。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据...

    xt_fset:扩展到Linux内核netfilter子系统(iptables)(插件),使您可以通过发送ICMP包远程操作linux内核ipset(向ipset中添加或删除一些ip地址)。

    xt_fset是linux内核netfilter子系统的内核模块和iptables扩展(插件),允许您通过发送控制ICMP数据包来远程操作linux内核ipset(在ipset中添加或删除一些ip地址)。 该插件的创建是对Linux内核netfilter子系统的...

    Linux Kernel v4.19.1 Stable.zip

    Linux Kernel主要新特性包括:合并了来自Android项目的内核代码,支持新的架构TI C6X,改进了Btrfs...网络优先控制组允许管理员动态设置网络流量的优先次序;支持EFI引导固件;改进内存管理,等等。 Linux Kernel截图

    Linux的高级路由和流量控制HOWTO-中文版

    虽然这些工具能够工作,但它们在 Linux2.2 和更高版本的内核上显 得有一些落伍。比如,现在 GRE 隧道已经成为了路由的一个主要概念,但却不 能通过上述工具来配置。 使用了 iproute2,隧道的配置与其他部分完全集成了。

    TC限制源和目标IP脚本

    资源描述:TC(Traffic Control)脚本实战指南...TC是Linux内核中强大的网络流量控制和整形工具,适用于网络测试、QoS设置及流量管理等多种场景。本教程适合网络管理员、系统工程师以及对Linux网络管理感兴趣的开发者。

    lksctp-rs:Rust 的 Linux 内核 SCTP 低级绑定

    流量控制和拥塞控制 此外,它还补充说: 多路复用流:通过单个连接,您可以多路复用多个信息流。 端点的多宿主:一个端点可以有多个 IP 地址,允许网络故障转移/可靠性(注意:它需要是一台机器,或者复制端点的...

    《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

    15.2.3 流量控制315 15.3 缓冲区管理和并发控制315 15.4 设备实例:以太网nic316 15.5 isa网络驱动程序321 15.6 atm321 15.7 网络吞吐量322 15.7.1 驱动程序性能322 15.7.2 协议性能323 15.8 查看...

    精通LINUX设备驱动程序开发

    311 15.1.6 统计 312 15.1.7 配置 313 15.1.8 总线相关内容 314 15.2 与协议层会话 314 15.2.1 接收路径 314 15.2.2 发送路径 315 15.2.3 流量控制 315 15.3 缓冲区管理和并发控制 315 15.4 设备实例:...

    基于Linux 的防火墙技术研究

    络地址转换、流量控制及高级的包处理等。Netfilter/Iptables 系统采用模块化的架构方式,其主要模块 有:通用框架Netfilter、数据包选择系统、连接跟踪系统及NAT系统等等。 2.1 Netfilter/Iptables 系统工作原理 ...

    Linux模拟网络丢包与延迟的方法

    netem 与 tc: netem 是 Linux 2.6 及以上...tc 是 Linux 系统中的一个工具,全名为traffic control(流量控制)。tc 可以用来控制 netem 的工作模式,也就是说,如果想使用 netem ,需要至少两个条件,一个是内核中的

    Linux C 一站式学习

    7.3. 流量控制 37. socket编程 1. 预备知识 1.1. 网络字节序 1.2. socket地址的数据类型及相关函数 2. 基于TCP协议的网络程序 2.1. 最简单的TCP网络程序 2.2. 错误处理与读写控制 2.3. 把client改为交互式输入 2.4. ...

    DarkShell_Linux-Win集群版V2014年

    Linux支持路由内核、2.6、3.1等普通内核,路由内核支持路由三大内核、Ubuntu、admin等,独立开发的Linux穿盾CC模式,SYN稳定发包100%,自启动,无需Root权限上线即可发包。 VIP版本攻击代码实时更新,通过服务器...

Global site tag (gtag.js) - Google Analytics