Linux内核中流量控制(15)

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

5.15. Qdisc的netlink控制

各网卡的Qdisc的用户层操作控制是通过rtnetlink接口实现用户空间和内核之间的通信的: rtnetlink_link, rtnetlink是专门针对路由控制的netlink接口.
/* include/linux/rtnetlink.h */
struct rtnetlink_link
{
// 就两个成员函数, 操作和输出
 int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
 int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};

// 全局数组, 具体在 net/core/rtnetlink.c中定义
extern struct rtnetlink_link * rtnetlink_links[NPROTO];

其中的两个成员定义如下:
/* net/core/rtnetlink.c */

void __init rtnetlink_init(void)
{
......
 rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table;
 rtnetlink_links[PF_PACKET] = link_rtnetlink_table;
......
}

其中link_rtnetlink_table是一个数组, 定义为:
static struct rtnetlink_link link_rtnetlink_table[RTM_NR_MSGTYPES] =
{
// 数组基本元素, 处理路由, 邻居ARP等相关信息, 这些不是本文重点,
// 只是Qdisc的相关操作也是定义在这个数组中
 [RTM_GETLINK     - RTM_BASE] = { .doit   = rtnl_getlink,
      .dumpit = rtnl_dump_ifinfo  },
 [RTM_SETLINK     - RTM_BASE] = { .doit   = rtnl_setlink   },
 [RTM_GETADDR     - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_GETROUTE    - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_NEWNEIGH    - RTM_BASE] = { .doit   = neigh_add   },
 [RTM_DELNEIGH    - RTM_BASE] = { .doit   = neigh_delete   },
 [RTM_GETNEIGH    - RTM_BASE] = { .dumpit = neigh_dump_info  },
#ifdef CONFIG_FIB_RULES
 [RTM_NEWRULE     - RTM_BASE] = { .doit   = fib_nl_newrule  },
 [RTM_DELRULE     - RTM_BASE] = { .doit   = fib_nl_delrule  },
#endif
 [RTM_GETRULE     - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_GETNEIGHTBL - RTM_BASE] = { .dumpit = neightbl_dump_info  },
 [RTM_SETNEIGHTBL - RTM_BASE] = { .doit   = neightbl_set   },
};
 
5.15.1 初始化

初始化过程是定义对应tc的qdisc和class的操作命令的处理函数:
/* net/sched/sch_api.c */
static int __init pktsched_init(void)
{
 struct rtnetlink_link *link_p;
// 流控调度的时钟初始化
#ifdef CONFIG_NET_SCH_CLK_CPU
 if (psched_calibrate_clock() < 0)
  return -1;
#elif defined(CONFIG_NET_SCH_CLK_JIFFIES)
 psched_tick_per_us = HZ<<PSCHED_JSCALE;
 psched_us_per_tick = 1000000;
#endif
// 使用PF_UNSPEC(0)号rtnetlink_links元素用来作为QDISC操作的接口
 link_p = rtnetlink_links[PF_UNSPEC];
 /* Setup rtnetlink links. It is made here to avoid
    exporting large number of public symbols.
  */
// link_p将指向link_rtnetlink_table数组
 if (link_p) {
// 对数组中流控相关元素进行赋值
// Qdisc操作, 也就是对应tc qdisc add/modify等操作
  link_p[RTM_NEWQDISC-RTM_BASE].doit = tc_modify_qdisc;
// 删除/获取Qdisc操作
  link_p[RTM_DELQDISC-RTM_BASE].doit = tc_get_qdisc;
  link_p[RTM_GETQDISC-RTM_BASE].doit = tc_get_qdisc;
// 获取Qdisc信息, 也就是对应tc qdisc show
  link_p[RTM_GETQDISC-RTM_BASE].dumpit = tc_dump_qdisc;
// 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;
 }
// 登记FIFO流控处理, 这是网卡设备基本流控方法, 缺省必有的
 register_qdisc(&pfifo_qdisc_ops);
 register_qdisc(&bfifo_qdisc_ops);
 proc_net_fops_create("psched", 0, &psched_fops);
 return 0;
}

5.15.2 相关操作

以下函数中用到的Qdisc操作函数可见本系列第一篇, 第4节

5.15.2.1 创建/修改qdisc

/*
   Create/change qdisc.
 */
static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm;
 struct rtattr **tca;
 struct net_device *dev;
 u32 clid;
 struct Qdisc *q, *p;
 int err;
replay:
 /* Reinit, just in case something touches this. */
// tc消息指针
 tcm = NLMSG_DATA(n);
 tca = arg;
// class id
 clid = tcm->tcm_parent;
 q = p = NULL;
// 该tc命令针对的网卡
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
 if (clid) {
// 指定了类别ID的情况
  if (clid != TC_H_ROOT) {
// 如果不是根节点
   if (clid != TC_H_INGRESS) {
// 非ingress节点时, 根据类别ID的高16位查找Qdisc节点
    if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
     return -ENOENT;
// 获取p节点的叶子节点, 将调用p->ops->cl_ops->leaf()函数
    q = qdisc_leaf(p, clid);
   } else { /*ingress */
// 使用设备ingress流控
    q = dev->qdisc_ingress;
   }
  } else {
// 根节点情况下流控用的是设备的qdisc_sleeping
   q = dev->qdisc_sleeping;
  }
  /* It may be default qdisc, ignore it */
// 如果找到的Qdisc的句柄为0, 放弃q
  if (q && q->handle == 0)
   q = NULL;
  if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {
// 没找到Qdisc节点, 或没在tc消息中指定句柄值, 或者找到的Qdisc句柄和tc消息中
// 的句柄不同
   if (tcm->tcm_handle) {
// TC指定了句柄
// 如果Qdisc存在但不是更新命令, 返回对象存在错误
    if (q && !(n->nlmsg_flags&NLM_F_REPLACE))
     return -EEXIST;
// TC句柄低16位不能位0
    if (TC_H_MIN(tcm->tcm_handle))
     return -EINVAL;
// 根据TC句柄查找该设备上的Qdisc, 找不到的话跳转到创建新节点操作
    if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
     goto create_n_graft;
// 找到但设置了NLM_F_EXCL排斥标志, 返回对象存在错误
    if (n->nlmsg_flags&NLM_F_EXCL)
     return -EEXIST;
// 比较TC命令中的算法名称和Qdisc名称算法相同
    if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
     return -EINVAL;
// 检查算法出现回环情况, p是用clid找到的Qdisc
    if (q == p ||
        (p && check_loop(q, p, 0)))
     return -ELOOP;
// 新找到的Qdisc有效, 转到嫁接操作
    atomic_inc(&q->refcnt);
    goto graft;
   } else {
// 没指定TC句柄, 如果没找到Qdisc, 跳转到创建新节点
    if (q == NULL)
     goto create_n_graft;
    /* This magic test requires explanation.
     *
     *   We know, that some child q is already
     *   attached to this parent and have choice:
     *   either to change it or to create/graft new one.
     *
     *   1. We are allowed to create/graft only
     *   if CREATE and REPLACE flags are set.
     *
     *   2. If EXCL is set, requestor wanted to say,
     *   that qdisc tcm_handle is not expected
     *   to exist, so that we choose create/graft too.
     *
     *   3. The last case is when no flags are set.
     *   Alas, it is sort of hole in API, we
     *   cannot decide what to do unambiguously.
     *   For now we select create/graft, if
     *   user gave KIND, which does not match existing.
     */
// 检查各种标志是否冲突, Qdisc名称是否正确
    if ((n->nlmsg_flags&NLM_F_CREATE) &&
        (n->nlmsg_flags&NLM_F_REPLACE) &&
        ((n->nlmsg_flags&NLM_F_EXCL) ||
         (tca[TCA_KIND-1] &&
          rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))))
     goto create_n_graft;
   }
  }
 } else {
// 如果没指定类别ID, 从tc消息的句柄来查找Qdisc
  if (!tcm->tcm_handle)
   return -EINVAL;
  q = qdisc_lookup(dev, tcm->tcm_handle);
 }
// 到这里是属于Qdisc修改操作
 /* Change qdisc parameters */
// 没找到Qdisc节点, 返回错误
 if (q == NULL)
  return -ENOENT;
// 找到Qdisc节点, 但设置了NLM_F_EXCL(排斥)标志, 返回对象存在错误
 if (n->nlmsg_flags&NLM_F_EXCL)
  return -EEXIST;
// 检查找到的Qdisc节点的名称和tc中指定的是否匹配
 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
  return -EINVAL;
// 修改Qdisc参数
 err = qdisc_change(q, tca);
 if (err == 0)
  qdisc_notify(skb, n, clid, NULL, q);
 return err;
create_n_graft:
// 创建新Qdisc节点
// 如果TC命令中没有创建标志, 返回错误
 if (!(n->nlmsg_flags&NLM_F_CREATE))
  return -ENOENT;
// 创建新Qdisc节点
 if (clid == TC_H_INGRESS)
  q = qdisc_create(dev, tcm->tcm_parent, tca, &err);
        else
  q = qdisc_create(dev, tcm->tcm_handle, tca, &err);
 if (q == NULL) {
// 创建失败, 如果不是EAGAIN(重来一次), 返回失败
  if (err == -EAGAIN)
   goto replay;
  return err;
 }
graft:
// 嫁接操作
 if (1) {
  struct Qdisc *old_q = NULL;
// 进行嫁接操作, 返回老节点
  err = qdisc_graft(dev, p, clid, q, &old_q);
  if (err) {
// 失败, 释放新建立的Qdisc
   if (q) {
    spin_lock_bh(&dev->queue_lock);
    qdisc_destroy(q);
    spin_unlock_bh(&dev->queue_lock);
   }
   return err;
  }
// Qdisc通告
  qdisc_notify(skb, n, clid, old_q, q);
  if (old_q) {
// 如果存在老Qdisc节点, 释放之
   spin_lock_bh(&dev->queue_lock);
   qdisc_destroy(old_q);
   spin_unlock_bh(&dev->queue_lock);
  }
 }
 return 0;
}

5.15.2.2 获取/删除qdisc
/*
 * Delete/get qdisc.
 */
static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm = NLMSG_DATA(n);
 struct rtattr **tca = arg;
 struct net_device *dev;
// class id
 u32 clid = tcm->tcm_parent;
 struct Qdisc *q = NULL;
 struct Qdisc *p = NULL;
 int err;
// 根据TC参数中的网卡索引号查找网卡设备
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
// 根据类别ID或TC句柄查找Qdisc, 和上面函数类似
 if (clid) {
  if (clid != TC_H_ROOT) {
   if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) {
    if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
     return -ENOENT;
    q = qdisc_leaf(p, clid);
   } else { /* ingress */
    q = dev->qdisc_ingress;
                        }
  } else {
   q = dev->qdisc_sleeping;
  }
  if (!q)
   return -ENOENT;
  if (tcm->tcm_handle && q->handle != tcm->tcm_handle)
   return -EINVAL;
 } else {
  if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
   return -ENOENT;
 }
// 检查找到的Qdisc名称和TC命令中指定的是否一致
 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
  return -EINVAL;
// 删除Qdisc操作
 if (n->nlmsg_type == RTM_DELQDISC) {
// 必须指定类别ID
  if (!clid)
   return -EINVAL;
// 如果找到的Qdisc句柄为0, 返回错误
  if (q->handle == 0)
   return -ENOENT;
// 进行Qdisc嫁接操作, 新节点是NULL, 即将叶子节点替换为NULL, 即删除了原叶子节点
// 原叶子节点返回到q
  if ((err = qdisc_graft(dev, p, clid, NULL, &q)) != 0)
   return err;
  if (q) {
// 释放原叶子节点
   qdisc_notify(skb, n, clid, q, NULL);
   spin_lock_bh(&dev->queue_lock);
   qdisc_destroy(q);
   spin_unlock_bh(&dev->queue_lock);
  }
 } else {
// 非删除操作, 通告一下, q作为获得的Qdisc参数返回
  qdisc_notify(skb, n, clid, NULL, q);
 }
 return 0;
}

// 发送Qdisc通知信息, new是处理后新Qdisc节点信息, old是处理前老节点信息
static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n,
   u32 clid, struct Qdisc *old, struct Qdisc *new)
{
 struct sk_buff *skb;
 u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
// 分配netlink数据包
 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
 if (!skb)
  return -ENOBUFS;
 if (old && old->handle) {
// 填充老Qdisc的信息
  if (tc_fill_qdisc(skb, old, clid, pid, n->nlmsg_seq, 0, RTM_DELQDISC) < 0)
   goto err_out;
 }
 if (new) {
// 填充新Qdisc的信息
  if (tc_fill_qdisc(skb, new, clid, pid, n->nlmsg_seq, old ? NLM_F_REPLACE : 0, RTM_NEWQDISC) < 0)
   goto err_out;
 }
// 发送数据包
 if (skb->len)
  return rtnetlink_send(skb, pid, RTNLGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
err_out:
// 错误处理, 释放数据包
 kfree_skb(skb);
 return -EINVAL;
}

5.15.2.3 输出网卡qdisc参数

static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
{
 int idx, q_idx;
 int s_idx, s_q_idx;
 struct net_device *dev;
 struct Qdisc *q;
// 起始网卡索引
 s_idx = cb->args[0];
// 起始Qdisc索引
 s_q_idx = q_idx = cb->args[1];
 read_lock(&dev_base_lock);
// 遍历所有网卡
 for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
// 索引值小于所提供的起始索引值, 跳过
// 这个索引和网卡的索引号应该没啥关系
  if (idx < s_idx)
   continue;
// 索引值大于所提供的起始索引值, 将起始Qdisc索引清零
  if (idx > s_idx)
   s_q_idx = 0;
  read_lock(&qdisc_tree_lock);
// q_idx清零, 这样前面也用不着在初始化时赋值
  q_idx = 0;
// 遍历该网卡设备的所有Qdisc
  list_for_each_entry(q, &dev->qdisc_list, list) {
// 当前Qdisc索引小于起始Qdisc索引, 跳过
// 所以当idx > s_idx时, s_q_idx = 0, 只处理第一个Qdisc
// 当idx == s_idx时, 处理从s_q_idx开始的所有Qdisc
   if (q_idx < s_q_idx) {
    q_idx++;
    continue;
   }
// 填充Qdisc信息到数据包
   if (tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
       cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0) {
    read_unlock(&qdisc_tree_lock);
    goto done;
   }
   q_idx++;
  }
  read_unlock(&qdisc_tree_lock);
 }
done:
 read_unlock(&dev_base_lock);
// 返回处理的所有网卡数和Qdisc数
 cb->args[0] = idx;
 cb->args[1] = q_idx;
 return skb->len;
}

// 填充Qdisc信息到skb数据包
static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
    u32 pid, u32 seq, u16 flags, int event)
{
 struct tcmsg *tcm;
 struct nlmsghdr  *nlh;
 unsigned char  *b = skb->tail;
 struct gnet_dump d;
// skb中的netlink数据头位置
 nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
// TC信息数据头位置
 tcm = NLMSG_DATA(nlh);
// 填充TC信息参数
 tcm->tcm_family = AF_UNSPEC;
 tcm->tcm__pad1 = 0;
 tcm->tcm__pad2 = 0;
 tcm->tcm_ifindex = q->dev->ifindex;
 tcm->tcm_parent = clid;
 tcm->tcm_handle = q->handle;
 tcm->tcm_info = atomic_read(&q->refcnt);
 RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
// Qdisc的输出函数
 if (q->ops->dump && q->ops->dump(q, skb) < 0)
  goto rtattr_failure;
 q->qstats.qlen = q->q.qlen;
// 准备开始拷贝统计信息
 if (gnet_stats_start_copy_compat(skb, TCA_STATS2, TCA_STATS,
   TCA_XSTATS, q->stats_lock, &d) < 0)
  goto rtattr_failure;
// 输出统计信息
 if (q->ops->dump_stats && q->ops->dump_stats(q, &d) < 0)
  goto rtattr_failure;
// 拷贝基本统计信息, 流控速率统计信息, 队列统计信息
 if (gnet_stats_copy_basic(&d, &q->bstats) < 0 ||
#ifdef CONFIG_NET_ESTIMATOR
     gnet_stats_copy_rate_est(&d, &q->rate_est) < 0 ||
#endif
     gnet_stats_copy_queue(&d, &q->qstats) < 0)
  goto rtattr_failure;
// 结束封口操作 
 if (gnet_stats_finish_copy(&d) < 0)
  goto rtattr_failure;
 
 nlh->nlmsg_len = skb->tail - b;
 return skb->len;
nlmsg_failure:
rtattr_failure:
 skb_trim(skb, b - skb->data);
 return -1;
}
 

5.16 Qdisc小结
 
关于流控(Qdisc)的分析就此告一段落, 后面将继续分析分类(class), 过滤(filter)和动作(action)的处理过程.

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

猜你喜欢

转载自cxw06023273.iteye.com/blog/867346
今日推荐