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

Linux内核中流量控制(15)
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: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()函数