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()函数
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)的处理过程.

...... 待续 ......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值