日期:2014-05-16  浏览次数:20545 次

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

6. 类别操作

6.1 概述

类别操作是通过tc class命令来完成的, 当网卡使用的流控算法是可分类的(如HTB, CBQ等)时候使用, 功能是对Qdisc根节点进行划分, 定义出分类树, 同时可定义每个类别的流量限制参数,但具体那些数据属于哪一类则是通过tc filter命令来实现。

分类举例,以下命令在eth0上设置HTB流控,设置了3个类别,分别定义了各个类别的流量限制:
tc qdisc add dev eth0 root handle 1: htb default 12
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps

类别操作的具体实现实际是通过Qdisc的类别操作来完成的, 下面的处理仅仅是一个接口处理而已, 而具体的Qdisc类别操作函数已经在分析Qdisc时介绍了, 所以也没有引入新的数据结构。
 
6.2 初始化

前面5.15.1节中的初始化处理已经包括了类别的初始化:
......
// class操作, 也就是对应tc class add/delete/modify/get等操作
  link_p[RTM_NEWTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_DELTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].dumpit = tc_dump_tclass;
......

6.3 类别控制操作
/* net/sched/sch_api.c */
static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm = NLMSG_DATA(n);
 struct rtattr **tca = arg;
 struct net_device *dev;
 struct Qdisc *q = NULL;
 struct Qdisc_class_ops *cops;
 unsigned long cl = 0;
 unsigned long new_cl;
// parent id
 u32 pid = tcm->tcm_parent;
// class id
 u32 clid = tcm->tcm_handle;
// qdisc id: 初始化位类别id的高16位
 u32 qid = TC_H_MAJ(clid);
 int err;
// 根据TC信息中的网卡索引值查找网卡
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
 /*
// 以下是tc class的parent参数取值的说明
    parent == TC_H_UNSPEC - unspecified parent.
    parent == TC_H_ROOT   - class is root, which has no parent.
    parent == X:0  - parent is root class.
    parent == X:Y  - parent is a node in hierarchy.
    parent == 0:Y  - parent is X:Y, where X:0 is qdisc.
// 以下是tc class的classid参数取值的说明
    handle == 0:0  - generate handle from kernel pool.
    handle == 0:Y  - class is X:Y, where X:0 is qdisc.
    handle == X:Y  - clear.
    handle == X:0  - root class.
  */
 /* Step 1. Determine qdisc handle X:0 */
 if (pid != TC_H_ROOT) {
// parent id非根节点的情况
  u32 qid1 = TC_H_MAJ(pid);
  if (qid && qid1) {
   /* If both majors are known, they must be identical. */
   if (qid != qid1)
    return -EINVAL;
  } else if (qid1) {
   qid = qid1;
  } else if (qid == 0)
   qid = dev->qdisc_sleeping->handle;
  /* Now qid is genuine qdisc handle consistent
     both with parent and child.
     TC_H_MAJ(pid) still may be unspecified, complete it now.
   */
  if (pid)
   pid = TC_H_MAKE(qid, pid);
 } else {
// 为根节点, 如果当前qid为0, 更新为设备的qdisc_sleeping的handle
  if (qid == 0)
   qid = dev->qdisc_sleeping->handle;
 }
 /* OK. Locate qdisc */
// 根据qid查找该dev上的Qdisc指针, 找不到的话返回失败
 if ((q = qdisc_lookup(dev, qid)) == NULL)
  return -ENOENT;
 /* An check that it supports classes */
// 获取Qdisc的类别操作指针
 cops = q->ops->cl_ops;
// 如果Qdisc是非分类的, 类别操作结构指针位空, 返回失败
 if (cops == NULL)
  return -EINVAL;
 /* Now try to get class */
// 生成合法的类别ID
 if (clid == 0) {
  if (pid == TC_H_ROOT)
   clid = qid;
 } else
  clid = TC_H_MAKE(qid, clid);
// 如果clid非0, 调用get函数获取该类别, 增加类别的引用计数
// cl虽然定义是unsigned long, 但实际是个指针的数值
 if (clid)
  cl = cops->get(q, clid);
 if (cl == 0) {
// 类别为空
  err = -ENOENT;
// 如果netlink命令不是新建类别的话, 返回错误
  if (n->nlmsg_type != RTM_NEWTCLASS || !(n->nlmsg_flags&NLM_F_CREATE))
   goto out;
 } else {
// 获取类别成功, 根据netlink命令类型进行相关操作
  switch (n->nlmsg_type) {
  case RTM_NEWTCLASS: 
// 新建class
   err = -EEXIST;
// 如果设置了互斥标志, 返回错误, 因为现在该class已经存在
   if (n->nlmsg_flags&NLM_F_EXCL)
    goto out;
   break;
  case RTM_DELTCLASS:
// 删除class
   err = cops->delete(q, cl);
   if (err == 0)
    tclass_notify(skb, n, q, cl, RTM_DELTCLASS);
   goto out;
  case RTM_GETTCLASS:
// 获取class信息, 进行class通知操作