首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

Linux内核中游量控制(15)

2012-07-20 
Linux内核中流量控制(15)本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的

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

热点排行