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)的处理过程....... 待续 ......