- 之前通过《默认FIFO_FAST出口排队规则分析》、《ingress入口排队规则分析》分析,已经对排队规则的基础架框有了简单的了解。那两种排队规则都是无类的,这里选出可以分类的HTB排队规则进行分析。
- <img src="https://img-blog.csdn.net/20141214040026609?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
- 当前实例分析的基本对象关联图
- 一、当前分析的配置范例
- //在eth0设备上创建一个根HTB排队规则,当未匹配任何过滤器时,将报文放入ID为20
- //的分类中
- tc qdisc add dev eth0 root handle 1: htb default 20
- //在根HTB排队规则上创建ID为1的分类
- tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
- //在ID为1的分类上分别创建两个ID为10、ID为20的分类
- tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
- tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
- //创建两个U32分类器
- U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
- $U32 match ip dport 80 0xffff flowid 1:10
- $U32 match ip sport 25 0xffff flowid 1:20
- 二、创建根HTB排队规则
- 1、用户层分析
- //初始化,获取每纳秒对应多少TICKET
- tc_core_init();
- fp = fopen("/proc/net/psched", "r");
- fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
- fclose(fp);
- if (clock_res == 1000000000)
- t2us = us2t;
- clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
- tick_in_usec = (double)t2us / us2t * clock_factor;
- //创建一个ROUTE类型的netlink套接口
- rtnl_open(&rth, 0)
- rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
- rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
- setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
- setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
- rth->local.nl_family = AF_NETLINK;
- rth->local.nl_groups = subscriptions; //0
- bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
- rth->seq = time(NULL);
- do_cmd(argc-1, argv+1);
- if (matches(*argv, "qdisc") == 0)
- //执行设置排队规则的命令
- do_qdisc(argc-1, argv+1);
- if (matches(*argv, "add") == 0)
- tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE,
- argc-1, argv+1);
- req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
- req.n.nlmsg_flags = NLM_F_REQUEST|flags;
- req.n.nlmsg_type = cmd; //RTM_NEWQDISC
- req.t.tcm_family = AF_UNSPEC;
- while (argc > 0)
- if (strcmp(*argv, "dev") == 0)
- NEXT_ARG();
- strncpy(d, *argv, sizeof(d)-1); //eth0
- else if (strcmp(*argv, "root") == 0)
- req.t.tcm_parent = TC_H_ROOT;
- else if (strcmp(*argv, "handle") == 0)
- NEXT_ARG();
- get_qdisc_handle(&handle, *argv)
- req.t.tcm_handle = handle; //0x00010000
- else
- //如果有/usr/lib/tc/htb.so动态库中则从中获
- //取htb_qdisc_util符号结构,否则检测当前tc
- //程序是否有htb_qdisc_util符号结构则从中获取
- //,否则返回q 为空。
- q = get_qdisc_kind(k);
- //在消息尾部追加KIND属性
- //rta->rta_type = type; //TCA_KIND
- //rta->rta_len = len;
- //属性值为 “htb”
- addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
- //当前q为htb_qdisc_util,使用其中parse_qopt回调进行其它参数
- //解析,当前回调函数为htb_parse_opt
- q->parse_qopt(q, argc, argv, &req.n)
- htb_parse_opt
- //默认参数
- opt.rate2quantum = 10;
- opt.version = 3;
- while (argc > 0)
- if (matches(*argv, "default") == 0)
- NEXT_ARG();
- get_u32(&opt.defcls, *argv, 16) //20
- //添加扩展属性OPTIONS,标记后面都是htb的选项
- addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
- //添加扩展属性HTB_INIT
- addattr_l(n, 2024, TCA_HTB_INIT, &opt,
- NLMSG_ALIGN(sizeof(opt)));
- //根据接口名获取接口索引
- if (d[0])
- idx = ll_name_to_index(d)
- req.t.tcm_ifindex = idx;
- //给内核发送该netlink消息
- rtnl_talk(&rth, &req.n, 0, 0, NULL)
- rtnl_close(&rth);
- 2、内核层分析
- 用户侧发出RTM_NEWQDISC套接口消息后,在内核侧对应的处理回调函数为tc_modify_qdisc,该函数是在pktsched_init中初始化的。
- tc_modify_qdisc
- tcm = NLMSG_DATA(n);
- clid = tcm->tcm_parent; //当前用户侧传入值为 TC_H_ROOT
- //根据设备索引获取设备对象,上面用户侧传入设备名为eth0
- dev = __dev_get_by_index(tcm->tcm_ifindex)
- if (clid)
- if (clid != TC_H_ROOT)
- //当前为根排队规则,不走此流程
- else
- q = dev->qdisc_sleeping;
- //当前设备存储的是默认的排队规则,则忽略
- if (q && q->handle == 0)
- q = NULL;
- if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle)
- if (tcm->tcm_handle) //用户侧传入为特定的0x1
- //当前设备的qdisc_list排队规则链表中不含有此规则,进行创建
- if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
- goto create_n_graft;
- create_n_graft:
- //创建排队规则
- q = qdisc_create(dev, tcm->tcm_handle, tca, &err);
- //从已经注册到qdisc_base链表中获取匹配排队规则,当前htb已经注册
- //,则ops = htb_qdisc_ops
- ops = qdisc_lookup_ops(kind);
- sch = qdisc_alloc(dev, ops);
- INIT_LIST_HEAD(&sch->list);
- skb_queue_head_init(&sch->q); //初始化规则中的SKB队列
- sch->ops = ops; //htb_qdisc_ops
- sch->enqueue = ops->enqueue; //ingress_enqueue
- sch->dequeue = ops->dequeue; //ingress_dequeue
- sch->dev = dev; //eth0设备对象
- dev_hold(dev); //设备对象引用递增
- sch->stats_lock = &dev->queue_lock;
- atomic_set(&sch->refcnt, 1);
- sch->handle = handle; //0x00010000
- //使用排队规则中的初始化回调进行初始化,当前htb的回调函数为
- //htb_init
- ops->init(sch, tca[TCA_OPTIONS-1])
- htb_init(tca[TCA_OPTIONS-1])
- htb_sched *q = qdisc_priv(sch);
- //HTB_INIT属性
- gopt = RTA_DATA(tb[TCA_HTB_INIT - 1]);
- //初始化根类链表
- INIT_LIST_HEAD(&q->root);
- for (i = 0; i < HTB_HSIZE; i++)
- INIT_HLIST_HEAD(q->hash + i);
- for (i = 0; i < TC_HTB_NUMPRIO; i++)
- INIT_LIST_HEAD(q->drops + i);
- init_timer(&q->timer);
- skb_queue_head_init(&q->direct_queue);
- q->direct_qlen = sch->dev->tx_queue_len;
- if (q->direct_qlen < 2)
- q->direct_qlen = 2;
- q->timer.function = htb_timer;
- q->timer.data = (unsigned long)sch;
- //启动了速率定时器,每秒触发一下,其中htb_rate_timer函数在每秒
- //触发会都会根据q->recmp_bucket索引来获取q->hash中的一个
- //HASH链表,对该HASH链表所有条目进行速率计算,之后递增
- //q->recmp_bucket索引,准备下一秒后对下一个HASH链表进行速
- //率计算。当前计算方法也比较简单
- //#define RT_GEN(D,R) R+=D-(R/HTB_EWMAC);D=0
- //RT_GEN(cl->sum_bytes, cl->rate_bytes);
- //RT_GEN(cl->sum_packets, cl->rate_packets);
- init_timer(&q->rttim);
- q->rttim.function = htb_rate_timer;
- q->rttim.data = (unsigned long)sch;
- q->rttim.expires = jiffies + HZ;
- add_timer(&q->rttim);
- if ((q->rate2quantum = gopt->rate2quantum) < 1) //用户默认值为10
- q->rate2quantum = 1;
- q->defcls = gopt->defcls; //20
- //将当前排队规则加入到设备的qdisc_list链表中
- qdisc_lock_tree(dev);
- list_add_tail(&sch->list, &dev->qdisc_list);
- qdisc_unlock_tree(dev);
- //排队规则嫁接处理
- qdisc_graft(dev, p, clid, q, &old_q);
- if (parent == NULL) //当前为根排队规则,未有父类
- dev_graft_qdisc(dev, new);
- //设备激活的情况下,先去激活
- if (dev->flags & IFF_UP)
- dev_deactivate(dev);
- oqdisc = dev->qdisc_sleeping;
- //假设当前仅使用的默认的fifo_fast排队规则,则当前这个老的排队规则
- //已经存在,需要将老的排队规则进行复位,这里fifo_fast的reset回调函
- //数为pfifo_fast_reset
- if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)
- qdisc_reset(oqdisc);
- pfifo_fast_reset
- //丢弃每个频道队列中的所有报文
- for (prio = 0; prio < PFIFO_FAST_BANDS; prio++)
- __qdisc_reset_queue(qdisc, list + prio);
- qdisc->qstats.backlog = 0;
- qdisc->q.qlen = 0;
- //将新建的排队规则设置到qdisc_sleeping,qdisc的当前规则指向空规则
- //noop_qdisc
- dev->qdisc_sleeping = qdisc;
- dev->qdisc = &noop_qdisc;
- if (dev->flags & IFF_UP)
- //设备激活
- dev_activate(dev);
- if (dev->qdisc_sleeping == &noop_qdisc)
- //当前已经有根排队规则,不走此流程
- //没有载波则直接返回
- if (!netif_carrier_ok(dev))
- return;
- //当前使用的排队规则设置为已经选择的根排队规则
- //启动看门狗。
- rcu_assign_pointer(dev->qdisc, dev->qdisc_sleeping);
- if (dev->qdisc != &noqueue_qdisc)
- dev->trans_start = jiffies;
- dev_watchdog_up(dev);
- //发送netlink消息,告知添加成功,并且老的已经删除
- qdisc_notify(skb, n, clid, old_q, q);
- //将老的排队规则去除
- qdisc_destroy(old_q);
- //属于内建规则,或者还有其它模块引用,则不进行去除。
- if (qdisc->flags & TCQ_F_BUILTIN || !atomic_dec_and_test(&qdisc->refcnt))
- return;
- //从设备的排队规则链表中去除
- list_del(&qdisc->list);
- //如果老的排队规则有reset、destroy回调,则进行处理
- if (ops->reset)
- ops->reset(qdisc);
- if (ops->destroy)
- ops->destroy(qdisc);
- //资源销毁
- module_put(ops->owner);
- dev_put(qdisc->dev);
- call_rcu(&qdisc->q_rcu, __qdisc_destroy);
- 三、创建ID为1的分类
- 1、用户层分析
- //初始化,获取每纳秒对应多少TICKET
- tc_core_init();
- fp = fopen("/proc/net/psched", "r");
- fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
- fclose(fp);
- if (clock_res == 1000000000)
- t2us = us2t;
- clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
- tick_in_usec = (double)t2us / us2t * clock_factor;
- //创建一个ROUTE类型的netlink套接口
- rtnl_open(&rth, 0)
- rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
- rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
- setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
- setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
- rth->local.nl_family = AF_NETLINK;
- rth->local.nl_groups = subscriptions; //0
- bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
- rth->seq = time(NULL);
- do_cmd(argc-1, argv+1);
- if (matches(*argv, "class") == 0)
- do_class(argc-1, argv+1);
- //创建新的类
- tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1,
- argv+1);
- req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
- req.n.nlmsg_flags = NLM_F_REQUEST|flags;
- req.n.nlmsg_type = cmd; //RTM_NEWTCLASS
- req.t.tcm_family = AF_UNSPEC;
- while (argc > 0)
- if (strcmp(*argv, "dev") == 0)
- NEXT_ARG();
- strncpy(d, *argv, sizeof(d)-1); //eth0
- else if (strcmp(*argv, "parent") == 0)
- NEXT_ARG();
- get_tc_classid(&handle, *argv)
- req.t.tcm_parent = handle; //0x00010000
- else if (strcmp(*argv, "classid") == 0)
- NEXT_ARG();
- get_tc_classid(&handle, *argv)
- req.t.tcm_handle = handle; //0x00010001
- else
- //如果有/usr/lib/tc/htb.so动态库中则从中获取htb_qdisc_util符
- //号结构,否则检测当前tc程序是否有htb_qdisc_util符号结构则
- //从中获取,否则返回q 为空。
- q = get_qdisc_kind(k);
- //添加KIND属性项,当前值为“htb”
- addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
- //使用当前扩展排队规则的parse_copt回调去解析后续命令字符,当前
- //htb的回调为htb_parse_class_opt
- q->parse_copt(q, argc, argv, &req.n)
- htb_parse_class_opt
- mtu = 1600;
- while (argc > 0)
- if (strcmp(*argv, "rate") == 0)
- NEXT_ARG();
- get_rate64(&rate64, *argv) //6 * 1000000 / 8
- else if (matches(*argv, "burst") == 0)
- NEXT_ARG();
- //buffer = 15 * 1024
- //cell_log = -1
- get_size_and_cell(&buffer, &cell_log, *argv)
- if (!ceil64)
- ceil64 = rate64;
- //超出32位最大值,则转换为全1?
- opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
- opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;
- //如果cbuffer为设置,则取值为当前mtu值加上最大速率下
- //每ticket单位的比特数
- if (!cbuffer)
- cbuffer = ceil64 / get_hz() + mtu;
- opt.ceil.overhead = overhead; //0
- opt.rate.overhead = overhead; //0
- opt.ceil.mpu = mpu; //0
- opt.rate.mpu = mpu; //0
- //计算最小速率表
- //cell_log = -1
- //mtu = 1600
- //linklayer = LINKLAYER_ETHERNET
- tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer)
- //根据最大传输单元计算需要多少槽我理解是不可能每个
- //字节都有准确速率,所以划定字节范围,从多少字节到
- //多少字节的速率相同。
- if (cell_log < 0)
- cell_log = 0;
- while ((mtu >> cell_log) > 255)
- cell_log++;
- for (i=0; i<256; i++)
- //校正当前槽位的字节大小。这个算法比较简单,当前
- //链路类型为以太网,则包根据原值处理,不会影响包
- //大小。Mpu为最小包大小,如果槽位字节小于mpu,
- //则校正为mpu的值。
- sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
- //根据当前槽位字节大小,及用户配置的速率,计算当
- //前槽位所需ticket时间
- rtab[i] = tc_calc_xmittime(bps, sz);
- r->cell_align=-1;
- r->cell_log=cell_log;
- r->linklayer = (linklayer & TC_LINKLAYER_MASK);
- //计算单包正常速率下的传送峰值,这里通过包大小转换成所
- //需要的ticket时间
- opt.buffer = tc_calc_xmittime(rate64, buffer);
- //计算最大速率表,仅在发包速率大于最小速率后,租借模式下
- //有效。
- tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer)
- //计算单包在租借模速率作用下,传送的峰值。
- opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);
- //添加扩展属性OPTIONS,标记后面都是htb的选项
- addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
- //超出32位,则添加扩展属性HTB_RATE64
- //超出32位,则添加扩展属性HTB_CEIL64
- if (rate64 >= (1ULL << 32))
- addattr_l(n, 1124, TCA_HTB_RATE64, &rate64,
- sizeof(rate64));
- if (ceil64 >= (1ULL << 32))
- addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64,
- sizeof(ceil64));
- //添加扩展属性HTB_PARMS
- addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
- //添加扩展属性HTB_RTAB
- addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
- //添加扩展属性HTB_CTAB
- addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
- //根据接口名获取接口索引
- if (d[0])
- idx = ll_name_to_index(d)
- req.t.tcm_ifindex = idx;
- //给内核发送该netlink消息
- rtnl_talk(&rth, &req.n, 0, 0, NULL)
- rtnl_close(&rth);
- 2、内核层分析
- 用户侧发出RTM_NEWTCLASS套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tclass,该函数是在pktsched_init中初始化的。
- tc_ctl_tclass
- pid = tcm->tcm_parent //父类ID 0x00010000
- clid = tcm->tcm_handle; //创建类ID 0x00010001
- qid = TC_H_MAJ(clid); //队列ID 0x00010000
- //eth0设备对象
- dev = __dev_get_by_index(tcm->tcm_ifindex))
- if (pid != TC_H_ROOT)
- pid = TC_H_MAKE(qid, pid); //0x00010000
- //从当前设备的qdisc_list链表中找到已经创建的排队规则,当前q为之前创建的HTB
- q = qdisc_lookup(dev, qid)
- //HTB的回调组为 cops = htb_class_ops
- cops = q->ops->cl_ops;
- clid = TC_H_MAKE(qid, clid); //0x00010001
- //get的回调函数为htb_get
- cl = cops->get(q, clid);
- htb_get
- htb_class *cl = htb_find(classid, sch);
- //使用类ID做HASH KEY在当前队列的HASH链表中查找已经创建的类
- hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)
- if (cl->classid == handle)
- return cl;
- return NULL;
- //如果找到则增加引用计数
- if (cl)
- cl->refcnt++;
- return (unsigned long)cl;
- //当前环境还没有该类存在,进行新类的属性设置
- new_cl = cl;
- //当前回调为 htb_change_class
- cops->change(q, clid, pid, tca, &new_cl);/
- htb_change_class
- //opt指向扩展属性基值索引,后续rtattr_parse_nested进行属性查找都从该
- //基值索引之外进行查找。
- rtattr *opt = tca[TCA_OPTIONS - 1];
- //查看枚举定义,TCA_HTB_RTAB值是最后一个,所以rtattr_parse_nested
- //函数会把用户设置的所有扩展参数存储到临时变量tb中。
- //enum
- //{
- // TCA_HTB_UNSPEC,
- // TCA_HTB_PARMS,
- // TCA_HTB_INIT,
- // TCA_HTB_CTAB,
- // TCA_HTB_RTAB,
- // __TCA_HTB_MAX,
- //};
- rtattr_parse_nested(tb, TCA_HTB_RTAB, opt)
- //parentid = 0x00010000
- //以类ID为HASH KEY向当前队规则的hash链表中查找父类,当前还未存在。
- parent = htb_find(parentid, sch);
- htb_sched *q = qdisc_priv(sch);
- hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)
- if (cl->classid == handle)
- return cl;
- return NULL;
- //取用户配置工具设置的HTB参数属性
- hopt = RTA_DATA(tb[TCA_HTB_PARMS - 1]);
- //将最小速率表加入到全局qdisc_rtab_list链表中
- rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB - 1]);
- //将最大速率表加入到全局qdisc_rtab_list链表中
- ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB - 1]);
- //当前为新类进行创建
- if (!cl)
- cl = kzalloc(sizeof(*cl), GFP_KERNEL)
- cl->refcnt = 1;
- INIT_LIST_HEAD(&cl->sibling);
- INIT_HLIST_NODE(&cl->hlist);
- INIT_LIST_HEAD(&cl->children);
- INIT_LIST_HEAD(&cl->un.leaf.drop_list);
- RB_CLEAR_NODE(&cl->pq_node);
- for (prio = 0; prio < TC_HTB_NUMPRIO; prio++)
- RB_CLEAR_NODE(&cl->node[prio]);
- //创建默认的pfifo排队规则,同时将该排队规则的父亲设置为当前类
- //sch->parent = parentid; 0x00010001
- new_q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops, classid);
- //当前新建的类的默认排队规则设置为pfifo
- cl->un.leaf.q = new_q;
- cl->classid = classid; //0x00010001
- cl->parent = parent; //NULL
- cl->tokens = hopt->buffer; //正常速率下单包峰值
- cl->ctokens = hopt->cbuffer; //借用速率下单包峰值
- cl->mbuffer = PSCHED_JIFFIE2US(HZ * 60);
- PSCHED_GET_TIME(cl->t_c);
- cl->cmode = HTB_CAN_SEND; //初始时可以发送报文
- //当新建的类加入到根排队规则的hash表中
- hlist_add_head(&cl->hlist, q->hash + htb_hash(classid));
- //将新建的类加入到根排队规则的root链表中
- list_add_tail(&cl->sibling, &q->root);
- if (!cl->level)
- //在htb_init时,rate2quantum值默认为1
- //设置quantum值,该值用于在进行带宽租借时的单位量
- cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;
- if (!hopt->quantum && cl->un.leaf.quantum < 1000)
- cl->un.leaf.quantum = 1000;
- if (!hopt->quantum && cl->un.leaf.quantum > 200000)
- cl->un.leaf.quantum = 200000;
- cl->un.leaf.prio = hopt->prio; //0
- cl->quantum = cl->un.leaf.quantum;
- cl->prio = cl->un.leaf.prio;
- cl->buffer = hopt->buffer; //正常速率下单包峰值
- cl->cbuffer = hopt->cbuffer; //借用速率下单包峰值
- //设置当前类的最小、最大速率表
- cl->rate = rtab;
- cl->ceil = ctab;
- //发送netlink消息,告知添加成功
- tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
- 四、创建ID为20的分类
- 1、用户层分析
- //初始化,获取每纳秒对应多少TICKET
- tc_core_init();
- fp = fopen("/proc/net/psched", "r");
- fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
- fclose(fp);
- if (clock_res == 1000000000)
- t2us = us2t;
- clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
- tick_in_usec = (double)t2us / us2t * clock_factor;
- //创建一个ROUTE类型的netlink套接口
- rtnl_open(&rth, 0)
- rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
- rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
- setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
- setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
- rth->local.nl_family = AF_NETLINK;
- rth->local.nl_groups = subscriptions; //0
- bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
- rth->seq = time(NULL);
- do_cmd(argc-1, argv+1);
- if (matches(*argv, "class") == 0)
- do_class(argc-1, argv+1);
- //创建新的类
- tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1,
- argv+1);
- req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
- req.n.nlmsg_flags = NLM_F_REQUEST|flags;
- req.n.nlmsg_type = cmd; //RTM_NEWTCLASS
- req.t.tcm_family = AF_UNSPEC;
- while (argc > 0)
- if (strcmp(*argv, "dev") == 0)
- NEXT_ARG();
- strncpy(d, *argv, sizeof(d)-1); //eth0
- else if (strcmp(*argv, "parent") == 0)
- NEXT_ARG();
- get_tc_classid(&handle, *argv)
- req.t.tcm_parent = handle; //0x00010001
- else if (strcmp(*argv, "classid") == 0)
- NEXT_ARG();
- get_tc_classid(&handle, *argv)
- req.t.tcm_handle = handle; //0x00010014 (0x14 = 20)
- else
- //如果有/usr/lib/tc/htb.so动态库中则从中获取htb_qdisc_util符
- //号结构,否则检测当前tc程序是否有htb_qdisc_util符号结构则
- //从中获取,否则返回q 为空。
- q = get_qdisc_kind(k);
- //添加KIND属性项,当前值为“htb”
- addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
- //使用当前扩展排队规则的parse_copt回调去解析后续命令字符,当前
- //htb的回调为htb_parse_class_opt
- q->parse_copt(q, argc, argv, &req.n)
- htb_parse_class_opt
- mtu = 1600;
- while (argc > 0)
- if (strcmp(*argv, "rate") == 0)
- NEXT_ARG();
- get_rate64(&rate64, *argv) //3 * 1000000 / 8
- else if (matches(*argv, "burst") == 0)
- NEXT_ARG();
- //buffer = 15 * 1024
- //cell_log = -1
- get_size_and_cell(&buffer, &cell_log, *argv)
- else if (strcmp(*argv, "ceil") == 0)
- NEXT_ARG();
- get_rate64(&ceil64, *argv); //6 * 1000000 / 8
- //超出32位最大值,则转换为全1?
- opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
- opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;
- //如果cbuffer为设置,则取值为当前mtu值加上最大速率下
- //每ticket单位的比特数
- if (!cbuffer)
- cbuffer = ceil64 / get_hz() + mtu;
- opt.ceil.overhead = overhead; //0
- opt.rate.overhead = overhead; //0
- opt.ceil.mpu = mpu; //0
- opt.rate.mpu = mpu; //0
- //计算最小速率表
- //cell_log = -1
- //mtu = 1600
- //linklayer = LINKLAYER_ETHERNET
- tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer)
- //根据最大传输单元计算需要多少槽我理解是不可能每个
- //字节都有准确速率,所以划定字节范围,从多少字节到
- //多少字节的速率相同。
- if (cell_log < 0)
- cell_log = 0;
- while ((mtu >> cell_log) > 255)
- cell_log++;
- for (i=0; i<256; i++)
- //校正当前槽位的字节大小。这个算法比较简单,当前
- //链路类型为以太网,则包根据原值处理,不会影响包
- //大小。Mpu为最小包大小,如果槽位字节小于mpu,
- //则校正为mpu的值。
- sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
- //根据当前槽位字节大小,及用户配置的速率,计算当
- //前槽位所需ticket时间
- rtab[i] = tc_calc_xmittime(bps, sz);
- r->cell_align=-1;
- r->cell_log=cell_log;
- r->linklayer = (linklayer & TC_LINKLAYER_MASK);
- //计算单包正常速率下的传送峰值,这里通过包大小转换成所
- //需要的ticket时间
- opt.buffer = tc_calc_xmittime(rate64, buffer);
- //计算最大速率表,仅在发包速率大于最小速率后,租借模式下
- //有效。
- tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer)
- //计算单包在租借模速率作用下,传送的峰值。
- opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);
- //添加扩展属性OPTIONS,标记后面都是htb的选项
- addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
- //超出32位,则添加扩展属性HTB_RATE64
- //超出32位,则添加扩展属性HTB_CEIL64
- if (rate64 >= (1ULL << 32))
- addattr_l(n, 1124, TCA_HTB_RATE64, &rate64,
- sizeof(rate64));
- if (ceil64 >= (1ULL << 32))
- addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64,
- sizeof(ceil64));
- //添加扩展属性HTB_PARMS
- addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
- //添加扩展属性HTB_RTAB
- addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
- //添加扩展属性HTB_CTAB
- addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
- //根据接口名获取接口索引
- if (d[0])
- idx = ll_name_to_index(d)
- req.t.tcm_ifindex = idx;
- //给内核发送该netlink消息
- rtnl_talk(&rth, &req.n, 0, 0, NULL)
- rtnl_close(&rth);
- 2、内核层分析
- 用户侧发出RTM_NEWTCLASS套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tclass,该函数是在pktsched_init中初始化的。
- tc_ctl_tclass
- pid = tcm->tcm_parent //父类ID 0x00010001
- clid = tcm->tcm_handle; //创建类ID 0x00010014
- qid = TC_H_MAJ(clid); //队列ID 0x00010000
- //eth0设备对象
- dev = __dev_get_by_index(tcm->tcm_ifindex))
- if (pid != TC_H_ROOT)
- pid = TC_H_MAKE(qid, pid); //0x00010001
- //从当前设备的qdisc_list链表中找到已经创建的排队规则,当前q为之前创建的HTB
- q = qdisc_lookup(dev, qid)
- //HTB的回调组为 cops = htb_class_ops
- cops = q->ops->cl_ops;
- clid = TC_H_MAKE(qid, clid); //0x00010014
- //get的回调函数为htb_get
- cl = cops->get(q, clid);
- htb_get
- htb_class *cl = htb_find(classid, sch);
- //使用类ID做HASH KEY在当前队列的HASH链表中查找已经创建的类
- hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)
- if (cl->classid == handle)
- return cl;
- return NULL;
- //如果找到则增加引用计数
- if (cl)
- cl->refcnt++;
- return (unsigned long)cl;
- //当前环境还没有该类存在,进行新类的属性设置
- new_cl = cl;
- //当前回调为 htb_change_class
- cops->change(q, clid, pid, tca, &new_cl);/
- htb_change_class
- //opt指向扩展属性基值索引,后续rtattr_parse_nested进行属性查找都从该
- //基值索引之外进行查找。
- rtattr *opt = tca[TCA_OPTIONS - 1];
- //查看枚举定义,TCA_HTB_RTAB值是最后一个,所以rtattr_parse_nested
- //函数会把用户设置的所有扩展参数存储到临时变量tb中。
- //enum
- //{
- // TCA_HTB_UNSPEC,
- // TCA_HTB_PARMS,
- // TCA_HTB_INIT,
- // TCA_HTB_CTAB,
- // TCA_HTB_RTAB,
- // __TCA_HTB_MAX,
- //};
- rtattr_parse_nested(tb, TCA_HTB_RTAB, opt)
- //parentid = 0x00010001
- //以类ID为HASH KEY向当前队规则的hash链表中查找父类,之前已经创建。
- parent = htb_find(parentid, sch);
- htb_sched *q = qdisc_priv(sch);
- hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)
- if (cl->classid == handle)
- return cl;
- return NULL;
- //取用户配置工具设置的HTB参数属性
- hopt = RTA_DATA(tb[TCA_HTB_PARMS - 1]);
- //将最小速率表加入到全局qdisc_rtab_list链表中
- rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB - 1]);
- //将最大速率表加入到全局qdisc_rtab_list链表中
- ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB - 1]);
- //当前为新类进行创建
- if (!cl)
- cl = kzalloc(sizeof(*cl), GFP_KERNEL)
- cl->refcnt = 1;
- INIT_LIST_HEAD(&cl->sibling);
- INIT_HLIST_NODE(&cl->hlist);
- INIT_LIST_HEAD(&cl->children);
- INIT_LIST_HEAD(&cl->un.leaf.drop_list);
- RB_CLEAR_NODE(&cl->pq_node);
- for (prio = 0; prio < TC_HTB_NUMPRIO; prio++)
- RB_CLEAR_NODE(&cl->node[prio]);
- //创建默认的pfifo排队规则,同时将该排队规则的父亲设置为当前类
- //sch->parent = parentid; 0x00010014
- new_q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops, classid);
- //当前存在父类,该父类还未有孩子节点
- if (parent && !parent->level)
- //保留当前父类中排队规则的队列长度,在HTB队列机制仅叶子类
- //含有队列,所以当父类有孩子类后,父类中的队列就要删除。
- qlen = parent->un.leaf.q->q.qlen;
- //调用父类中排队规则的reset回调,当前父类的排队规则为默认的
- //pfifo,则回调为qdisc_reset_queue
- qdisc_reset(parent->un.leaf.q);
- ops = qdisc->ops;
- ops->reset(qdisc);
- //该函数的实现仅仅是删除队列中所有skb报文
- qdisc_reset_queue
- //通知父排队规则,队列长度减少。
- qdisc_tree_decrease_qlen(parent->un.leaf.q, qlen);
- while ((parentid = sch->parent))
- //当前排队规则为父类的pfifo规则,其中parent为父类
- //的ID,TC_H_MAJ(parentid)对应的就是父类所在的排
- //队规则,当前父类所在的排队规则即为最早创建的根
- //排队规则HTB。
- sch = __qdisc_lookup(sch->dev, TC_H_MAJ(parentid));
- cops = sch->ops->cl_ops;
- //htb_get
- //在排队规则中获取指定ID的类对象,并增加引用计数
- cl = cops->get(sch, parentid);
- htb_get(sch, parentid)
- //htb_qlen_notify
- cops->qlen_notify(sch, cl);
- htb_qlen_notify(sch, cl)
- //如果当前父类中排队规则的队列长度为0,则进行
- //去激活
- if (cl->un.leaf.q->q.qlen == 0)
- htb_deactivate(qdisc_priv(sch), cl);
- htb_deactivate_prios(q, cl);
- mask = cl->prio_activity;
- while (cl->cmode ==
- HTB_MAY_BORROW
- && p && mask)
- //当前还未有激活项,不处理
- if (cl->cmode == HTB_CAN_SEND
- && mask)
- //当前还未有激活项,不处理
- cl->prio_activity = 0;
- list_del_init(&cl->un.leaf.drop_list);
- //htb_put
- //减小类引用计数,如果没有其它对象引用该类,则调用
- //htb_destroy_class进行类的销毁
- cops->put(sch, cl);
- htb_put(sch, cl);
- //子排队规则中队列长度减少,则父排队规则中的队列长度
- //相应减少。
- sch->q.qlen -= n;
- //销毁父类中排队规则,当前父类的排队规则为pfifo类型
- qdisc_destroy(parent->un.leaf.q);
- list_del(&qdisc->list);
- //qdisc_reset_queue
- ops->reset(qdisc);
- //删除队列中skb链表
- qdisc_reset_queue
- //递减相关对象引用计数,并释放排队规则占用的分配内存块
- module_put(ops->owner);
- dev_put(qdisc->dev);
- call_rcu(&qdisc->q_rcu, __qdisc_destroy);
- //父类中还有未激活项则去激活
- if (parent->prio_activity)
- htb_deactivate(q, parent);
- //根类等级深度为最大值
- parent->level = TC_HTB_MAXDEPTH) - 1; //7
- memset(&parent->un.inner, 0, sizeof(parent->un.inner));
- //当前新建的类的默认排队规则设置为pfifo
- cl->un.leaf.q = new_q;
- cl->classid = classid; //0x00010014
- cl->parent = parent; //指向当前ID为1:1的父类
- cl->tokens = hopt->buffer; //正常速率下单包峰值
- cl->ctokens = hopt->cbuffer; //借用速率下单包峰值
- cl->mbuffer = PSCHED_JIFFIE2US(HZ * 60);
- PSCHED_GET_TIME(cl->t_c);
- cl->cmode = HTB_CAN_SEND; //初始时可以发送报文
- //当新建的类加入到根排队规则的hash表中
- hlist_add_head(&cl->hlist, q->hash + htb_hash(classid));
- //将新建的类关联到当前父类上
- list_add_tail(&cl->sibling, &parent->children);
- if (!cl->level)
- //在htb_init时,rate2quantum值默认为1
- //设置quantum值,该值用于在进行带宽租借时的单位量
- cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;
- if (!hopt->quantum && cl->un.leaf.quantum < 1000)
- cl->un.leaf.quantum = 1000;
- if (!hopt->quantum && cl->un.leaf.quantum > 200000)
- cl->un.leaf.quantum = 200000;
- cl->un.leaf.prio = hopt->prio; //0
- cl->quantum = cl->un.leaf.quantum;
- cl->prio = cl->un.leaf.prio;
- cl->buffer = hopt->buffer; //正常速率下单包峰值
- cl->cbuffer = hopt->cbuffer; //借用速率下单包峰值
- //设置当前类的最小、最大速率表
- cl->rate = rtab;
- cl->ceil = ctab;
- //发送netlink消息,告知添加成功
- tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
- 五、创建一个U32分类器
- 1、用户层分析
- //初始化,获取每纳秒对应多少TICKET
- tc_core_init();
- fp = fopen("/proc/net/psched", "r");
- fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
- fclose(fp);
- if (clock_res == 1000000000)
- t2us = us2t;
- clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
- tick_in_usec = (double)t2us / us2t * clock_factor;
- //创建一个ROUTE类型的netlink套接口
- rtnl_open(&rth, 0)
- rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
- rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
- setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
- setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
- rth->local.nl_family = AF_NETLINK;
- rth->local.nl_groups = subscriptions; //0
- bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
- rth->seq = time(NULL);
- do_cmd(argc-1, argv+1);
- if (matches(*argv, "filter") == 0)
- do_filter(argc-1, argv+1);
- tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE,
- argc-1, argv+1);
- req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
- req.n.nlmsg_flags = NLM_F_REQUEST|flags;
- req.n.nlmsg_type = cmd; //RTM_NEWTFILTER
- req.t.tcm_family = AF_UNSPEC;
- while (argc > 0)
- if (strcmp(*argv, "dev") == 0)
- NEXT_ARG();
- strncpy(d, *argv, sizeof(d)-1); //eth0
- else if (matches(*argv, "protocol") == 0)
- NEXT_ARG();
- ll_proto_a2n(&id, *argv)
- protocol = id; //ETH_P_IP
- else if (strcmp(*argv, "parent") == 0)
- NEXT_ARG();
- get_tc_classid(&handle, *argv)
- req.t.tcm_parent = handle; //0x00010000
- else if (matches(*argv, "priority") == 0)
- NEXT_ARG();
- get_u32(&prio, *argv, 0) //1
- Else
- //如果有/usr/lib/tc/f_u32.so动态库中则从中获取u32_filter_util符 //号结构,否则检测当前tc程序是否有u32_filter_util符号结构则
- //从中获取,否则返回q 为空。
- q = get_filter_kind(k);
- //prio = 1,左移到前两个字节
- //protocol = ETH_P_IP,在低两个字节
- req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
- //增加KIND扩展属性,值为u32
- addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
- //使用u32扩展过滤器解析后续规则,当前回调为u32_parse_opt
- q->parse_fopt(q, fhandle, argc, argv, &req.n)
- u32_parse_opt
- //添加OPTIONS扩展属性,标识后续都属于u32的扩展项
- addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
- while (argc > 0)
- if (matches(*argv, "match") == 0)
- NEXT_ARG();
- parse_selector(&argc, &argv, &sel.sel, n)
- else if (matches(*argv, "ip") == 0)
- NEXT_ARG();
- parse_ip(&argc, &argv, sel);
- else if (strcmp(*argv, "dport") == 0)
- NEXT_ARG();
- //sel->keys[hwm].val = key; htos(80)
- //sel->keys[hwm].mask = mask;全1
- //sel->keys[hwm].off = off; 20
- //sel->keys[hwm].offmask=offmask; 0
- //sel->nkeys++; 1
- parse_u16(&argc, &argv, sel, 22,0);
- sel_ok++;
- else if (strcmp(*argv, "flowid") == 0)
- NEXT_ARG();
- //0x0001000A(0xA=10)
- get_tc_classid(&handle, *argv)
- //添加U32_CLASSID扩展属性
- addattr_l(n, MAX_MSG, TCA_U32_CLASSID, &handle, 4);
- //添加终止标记TC_U32_TERMINAL,内核代码会根据
- //此标记判断当前处理完成。
- sel.sel.flags |= TC_U32_TERMINAL;
- if (sel_ok)
- //添加U32_SEL扩展属性
- addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel,
- sizeof(sel.sel)+sel.sel.nkeys*sizeof(struct tc_u32_key));
- //根据接口名获取接口索引
- if (d[0])
- idx = ll_name_to_index(d)
- req.t.tcm_ifindex = idx;
- //给内核发送该netlink消息
- rtnl_talk(&rth, &req.n, 0, 0, NULL)
- rtnl_close(&rth);
- 2、内核层分析
- 用户侧发出RTM_NEWTFILTER套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tfilter,该函数是在tc_filter_init中初始化的。
- protocol = TC_H_MIN(t->tcm_info); //ETH_P_IP
- prio = TC_H_MAJ(t->tcm_info); //1
- nprio = prio;
- parent = t->tcm_parent; //0x00010000
- dev = __dev_get_by_index(t->tcm_ifindex) //eth0设备对象
- //从设备的qdisc_list列表中查找排队规则,之前HTB排队规则已经加入到链表中,所以
- //这里的q就等于HTB排队规则。
- q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))
- //前2个字节为排队规则索引,后2个字节为类索引,当前命令设置基于排队规则之上,后
- //2个字节设置为0,该条件不满足,此时cl变量取值为初始的0。
- if (TC_H_MIN(parent))
- //不走此流程
- //HTB排队规则中对应的回调为htb_find_tcf
- //获取该排队规则中的过滤链表。
- chain = cops->tcf_chain(q, cl);
- htb_find_tcf
- htb_sched *q = qdisc_priv(sch);
- htb_class *cl = (struct htb_class *)arg;
- //当前cl为0,则取的是排队规则中的过滤链表
- tcf_proto **fl = cl ? &cl->filter_list : &q->filter_list;
- return fl;
- //查找待插入的位置,优先级的值越小表示越高。
- for (back = chain; (tp=*back) != NULL; back = &tp->next)
- if (tp->prio >= prio)
- if (tp->prio == prio)
- if (!nprio || (tp->protocol != protocol && protocol))
- goto errout;
- else
- tp = NULL;
- break;
- //新建过滤协议项
- if (tp == NULL)
- //从tcf_proto_base链表中查找u32分类器,当前tp_ops为cls_u32_ops
- tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);
- tp->ops = tp_ops; //cls_u32_ops
- tp->protocol = protocol; //ETH_P_IP
- tp->prio = nprio; //1
- tp->q = q; //指向根HTB类型排队规则
- tp->classify = tp_ops->classify; //u32_classify
- tp->classid = parent; //0x00010000
- //当前u32类的初始化回调为fw_ini。
- tp_ops->init(tp)
- u32_init
- //查找u32_list链表中是否存在相同的排队规则对象的过滤通用项
- for (tp_c = u32_list; tp_c; tp_c = tp_c->next)
- if (tp_c->q == tp->q)
- break;
- root_ht = kzalloc(sizeof(*root_ht), GFP_KERNEL);
- root_ht->divisor = 0;
- root_ht->refcnt++;
- //当前在u32_list链表中还未存在相同的排队规则对象的过滤通用项,所以
- //tp_c不为真。
- root_ht->handle = tp_c ? gen_new_htid(tp_c) : 0x80000000;
- root_ht->prio = tp->prio; //1
- //当前tp_c为空,创建一个并加入到u32_list链表中。
- if (tp_c == NULL)
- tp_c = kzalloc(sizeof(*tp_c), GFP_KERNEL);
- tp_c->q = tp->q;
- tp_c->next = u32_list;
- u32_list = tp_c;
- //将新建的root_ht与tc_c互相关联
- tp_c->refcnt++;
- root_ht->next = tp_c->hlist;
- tp_c->hlist = root_ht;
- root_ht->tp_c = tp_c;
- //当前过滤对象的root指向新分配HASH节点对象,同时data指
- //向新分配的过滤通用项
- tp->root = root_ht;
- tp->data = tp_c;
- //将当前过滤器加入到当前排队规则的过滤链表中
- tp->next = *back;
- *back = tp;
- //u32分类器的get回调为u32_get,当前在命令行里没有handle值,所以这里返回为0
- fh = tp->ops->get(tp, t->tcm_handle);
- //u32分类器的change回调为u32_change
- tp->ops->change(tp, cl, t->tcm_handle, tca, &fh);
- u32_change
- //把用户配置的后续所有扩展属性提取到临时变量opt中
- rtattr_parse_nested(tb, TCA_U32_MAX, opt)
- if ((n = (struct tc_u_knode*)*arg) != NULL)
- //当前在调用u32_get后,还不存在对应的hash节点项,所以不走此流程。
- //如果命令接口设置了divisor,则表示创建一个新的hash表对象,该hash表含有
- //divisor个数量的列表项,主要用于规则数量很大时,加快匹配速度。
- if (tb[TCA_U32_DIVISOR-1])
- unsigned divisor = *(unsigned*)RTA_DATA(tb[TCA_U32_DIVISOR-1]);
- ht = kzalloc(sizeof(*ht) + divisor*sizeof(void*), GFP_KERNEL);
- ht->tp_c = tp_c; //当前hash表与过滤通用项关联
- ht->refcnt = 0;
- ht->divisor = divisor;
- ht->handle = handle;
- ht->prio = tp->prio;
- //通用项中的hlist指向第一hash表,这里将新建的hash插入到通用项的hlist
- //链头。
- ht->next = tp_c->hlist;
- tp_c->hlist = ht;
- *arg = (unsigned long)ht;
- return 0;
- //当用户在命令接口指定了hash表的ID,则查找对应的hash表,否则如果在命令
- //接口没有指定hash表ID,则使用过滤协议项中root指向的第一个hash链表。
- if (tb[TCA_U32_HASH-1])
- htid = *(unsigned*)RTA_DATA(tb[TCA_U32_HASH-1]);
- if (TC_U32_HTID(htid) == TC_U32_ROOT)
- ht = tp->root;
- htid = ht->handle;
- else
- //tp->data指向过滤通用项,这里使用过滤通用项中hlist链表来进行hash
- //表查找。
- ht = u32_lookup_ht(tp->data, TC_U32_HTID(htid));
- else
- ht = tp->root;
- htid = ht->handle; //0x80000000,该值在上面u32_init中初始化
- //当前命令行未设置handle,使用gen_new_kid生成hash节点id
- if (handle)
- //不走此流程
- else
- handle = gen_new_kid(ht, htid);
- unsigned i = 0x7FF;
- //handle = 0x80000000
- //TC_U32_HASH(handle) = 0
- //当前ht->ht[0]还未有值,所有这里返回值为 0x80000800
- for (n=ht->ht[TC_U32_HASH(handle)]; n; n = n->next)
- if (i < TC_U32_NODE(n->handle))
- i = TC_U32_NODE(n->handle);
- i++;
- return handle|(i>0xFFF ? 0xFFF : i);
- //指向选择项属性值
- s = RTA_DATA(tb[TCA_U32_SEL-1]);
- //当前用户配置接口在当前规则中仅设置了一个匹配项,则nkeys为1
- //这里分配内存空间为一个struct tc_u_knode大小,加上一个struct tc_u32_key大小
- n = kzalloc(sizeof(*n) + s->nkeys*sizeof(struct tc_u32_key), GFP_KERNEL);
- //把用户配置设置匹配规则复制到当前新的hash关键节点的sel中
- memcpy(&n->sel, s, sizeof(*s) + s->nkeys*sizeof(struct tc_u32_key));
- //当前hash关键节点的ht_up指向当前使用的hash表
- n->ht_up = ht;
- n->handle = handle; // 0x80000800
- //当前s->hmask为0,所以i最后为0
- u8 i = 0;
- u32 mask = s->hmask
- if (mask)
- ......
- n->fshift = i;
- //过滤节点参数设置
- u32_set_parms(tp, base, ht, n, tb, tca[TCA_RATE-1]);
- //当前命令行没有输入action或police,所以无扩展属性
- tcf_exts_validate(tp, tb, est, &e, &u32_ext_map);
- //如果用户接口输入了link,则进行链接处理,这里链接是指在当前过滤项
- //匹配成功后,可以跳到其它hash表项中去处理,如果在其它hash表项中
- //分类成功,则直接返回成功,否则在其它hash表项中分类失败,还可以
- //回来原来的hash表项继续处理。
- if (tb[TCA_U32_LINK-1])
- u32 handle = *(u32*)RTA_DATA(tb[TCA_U32_LINK-1]);
- //根据用户接口设置的link索引,查找当前通用项中hlist对应的hash
- //表是否含有对应id的hash项。
- ht_down = u32_lookup_ht(ht->tp_c, handle);
- //没找到返回错误,找到则增加引用计数
- if (ht_down == NULL)
- goto errout;
- ht_down->refcnt++;
- //将hash关键节点的ht_down指向希望链接的hash表项,在进行分类处
- //理里,如果当前hash关键节点匹配成功,会检测如果存在ht_down,则
- //使用该hash表项的各hash关键节点项继续匹配处理。
- ht_down = xchg(&n->ht_down, ht_down);
- //如果之前ht_down已经关联了其它hash表项,当前已经不使用,则
- //递减引用计数。
- if (ht_down)
- ht_down->refcnt--;
- if (tb[TCA_U32_CLASSID-1])
- //0x0001000A
- n->res.classid = *(u32*)RTA_DATA(tb[TCA_U32_CLASSID-1]);
- tcf_bind_filter(tp, &n->res, base);
- //当前回调为htb_bind_filter
- //查找绑定该hash关键节点对象的类
- cl = tp->q->ops->cl_ops->bind_tcf(tp->q, base, r->classid);
- htb_bind_filter
- htb_sched *q = qdisc_priv(sch);
- //当前classid为0x000100A
- htb_class *cl = htb_find(classid, sch);
- //找到匹配的类,则类的过滤计数递增
- if (cl)
- cl->filter_cnt++;
- else
- q->filter_cnt++;
- return (unsigned long)cl;
- //将找到的类对象关联到hash关键节点对象中,同时返回hash关键
- //节点对象中原有的关联类,当前没有老的关联类,所以old_cl为空
- cl = cls_set_class(tp, &r->class, cl);
- old_cl = __cls_set_class(clp, cl);
- old_cl = *clp;
- *clp = cl;
- return old_cl;
- return old_cl;
- //当前hash关键节点对象是新建的,没有老的关联类,不执行此流程。
- if (cl)
- tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
- //扩展参数设置,当前命令行没有输入action或police,所以无扩展属性。
- tcf_exts_change(tp, &n->exts, &e);
- //把新建的hash关键节点加入到hash对象的ht链表中
- for (ins = &ht->ht[TC_U32_HASH(handle)]; *ins; ins = &(*ins)->next)
- if (TC_U32_NODE(handle) < TC_U32_NODE((*ins)->handle))
- break;
- n->next = *ins;
- wmb();
- *ins = n;
- //告知添加成功
- tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
- 六、发送报文
- 当上层发出报文,调用dev_queue_xmit,这里仅分析和出口排队规则相关的代码,其它详细
- 的代码分析可以参见《接口设备发包》
- dev_queue_xmit
- ......
- //如果当前使用出口排队规则
- q = rcu_dereference(dev->qdisc);
- if (q->enqueue)
- spin_lock(&dev->queue_lock);
- q = dev->qdisc;
- if (q->enqueue)
- //调用当前排队规则的入队回调,当前HTB类型排队规则回调为
- //htb_enqueue
- rc = q->enqueue(skb, q);
- htb_enqueue
- htb_sched *q = qdisc_priv(sch);
- //分类处理
- htb_class *cl = htb_classify(skb, sch, &ret);
- //直报文的优先级字段与当前队列的句柄相同,则返回直接处理
- if (skb->priority == sch->handle)
- return HTB_DIRECT;
- //使用报文的优先级字段做为类ID,在htb_find函数中,从排队
- //规则的hash表中查找指定类ID的类对象,找到指定类对象后,
- //根据cl->level判断当前类是否为叶子(0为叶子),如果是叶子
- //类则返回。
- if ((cl = htb_find(skb->priority, sch)) != NULL && cl->level == 0)
- return cl;
- *qerr = NET_XMIT_BYPASS;
- tcf = q->filter_list;
- //使用过滤对象进行分类,tc_classify函数在下面单独分析。
- while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0)
- switch (result)
- //这三种处理结果都会返回空类,在htb_enqueue对返回类
- //为空的情况都会将报文丢弃。
- case TC_ACT_QUEUED:
- case TC_ACT_STOLEN:
- *qerr = NET_XMIT_SUCCESS;
- case TC_ACT_SHOT:
- return NULL;
- //如果返回结果的类不存在,则继续尝试
- if ((cl = (void *)res.class) == NULL)
- //如果返回结果就classid为排队规则句柄,也就是说之
- //前用户在命令接口配置过滤规则时,最后匹配的流ID
- //为排队规则句柄,则返回直接处理。
- if (res.classid == sch->handle)
- return HTB_DIRECT;
- //此时根据用户在命令接口配置过滤规则时指定的
- //流ID查找当前排队规则是否存在该类,如果不存
- //在,则直接跳出当前while循环,使用之前创建
- //HTB排队规则时设置的默认的类
- if ((cl = htb_find(res.classid, sch)) == NULL)
- break;
- //如果找到了类,并且该类是叶子类,则返回,否则继续使
- //用下一个过滤器进行查找。
- if (!cl->level)
- return cl;
- //使用非叶子类中的过滤器进行分类
- tcf = cl->filter_list;
- //到达这里,表明所有过滤器都已经遍历完了,但还没有找到
- //对应的叶子类。这里则使用之前创建HTB排队规则时设置的
- //默认类ID进行查找。
- cl = htb_find(TC_H_MAKE(TC_H_MAJ(sch->handle), q->defcls),
- sch);
- //如果使用默认类ID,还没找到对应的类,或者找到的类是非
- //叶子类型的,则返回直接处理。
- if (!cl || cl->level)
- return HTB_DIRECT;
- //返回找到的默认类。
- return cl;
- //当前返回结果为直接处理
- if (cl == HTB_DIRECT)
- //如果当前直接发送队列还未达到直接发送限额,则将报文放入
- //直接发送队列中,否则将报文丢弃处理。
- if (q->direct_queue.qlen < q->direct_qlen)
- __skb_queue_tail(&q->direct_queue, skb);
- q->direct_pkts++;
- else
- kfree_skb(skb);
- sch->qstats.drops++;
- return NET_XMIT_DROP;
- //使用叶子类中的排队规则的enqueue回调,将报文压入到该排队规
- //则中,如果压入失败,则进行丢包统计。
- else if (cl->un.leaf.q->enqueue(skb, cl->un.leaf.q) !=net_xmit_success)
- sch->qstats.drops++;
- cl->qstats.drops++;
- return NET_XMIT_DROP;
- else
- //包压入指定叶子类下的排队规则后,统计成功项,同时激活对
- //应叶子类。
- cl->bstats.packets++;
- cl->bstats.bytes += skb->len;
- //激活叶子类,当前HTB的机制在对激活的类做了划分。首先
- //分了8层,叶子类为第0层,根类为第7层,中间类在父类的
- //层数上减1,在进行出队处理时从第0层开始优先进行处理。
- //同时每层又分为8个优先级,0为第高优先级,当被激活的类
- //层次相同时,则按优先级最高的优先进行处理。当被激活的类
- //层次也相同,优先级也相同,则被根据类ID为对比值放入一
- //棵红黑树中,类ID越小优先级越高。激活类的数据结构大概
- //如下图所示:
- <img src="https://img-blog.csdn.net/20141214040619778?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
- htb_activate(q, cl);
- //设置当前类中被激活的优先级位图,这里cl->un.leaf.prio
- //是在类被用户接口创建时设置的,如果在用户接口未输入
- //prio则默认为0。
- cl->prio_activity = 1 << (cl->un.leaf.aprio = cl->un.leaf.prio);
- //根据类中层次及优先级位图激活类
- htb_activate_prios(q, cl);
- p = cl->parent; //当前类的父类
- mask = cl->prio_activity; //当前类的优先级位图
- //当前类如果已经进到了租借模式,同时该类存在父类
- //则进行处理。这里会对父类进行激活,同时将当前类
- //挂接到父类的feed供给树下。
- while (cl->cmode == HTB_MAY_BORROW && p &&
- mask)
- m = mask;
- while (m)
- //将优先级位图取反,之后使用ffz查找第一
- //为0的位为表示优先级索引。
- int prio = ffz(~m);
- //当前位处理后就去除。
- m &= ~(1 << prio);
- //当前父类如果已经在供给,则表明之前已经
- //激活,则在mask中去除当前优先级位,否
- //则下面会将cl变量指向父类,并对父类进行
- //激活。
- if (p->un.inner.feed[prio].rb_node)
- mask &= ~(1 << prio);
- //这里将当前子类加入到父类的供给树中
- //(un.inner.feed),表明当前父类在给哪些
- //子类供给。
- htb_add_to_id_tree(p->un.inner.feed + prio, cl,
- prio);
- //父类可能租借给多个子类使用,这里在父类中保
- //存子类的优先级。开始对父类准备激活处理。
- p->prio_activity |= mask;
- cl = p;
- p = cl->parent;
- //如果当前类为可发送模式,则在HTB排队规则中激
- //活该优先级类。
- if (cl->cmode == HTB_CAN_SEND && mask)
- htb_add_class_to_row(q, cl, mask);
- //记载当前层次哪些优先级被激活
- q->row_mask[cl->level] |= mask;
- //遍历所有待处理的优先级位,将当前类插入
- //到排队规则row指定位置的树中。插入位置
- //的实现可以参考上图激活类的数据结构来
- //加深理解。
- while (mask)
- int prio = ffz(~mask);
- mask &= ~(1 << prio);
- htb_add_to_id_tree(q->row[cl->level] +
- prio, cl, prio);
- //将当前叶子类插入到HTB排队规则的drops对应优先级的
- //列表中。
- list_add_tail(&cl->un.leaf.drop_list,q->drops +
- cl->un.leaf.aprio);
- //更新统计,返回分类成功。
- sch->q.qlen++;
- sch->bstats.packets++;
- sch->bstats.bytes += skb->len;
- return NET_XMIT_SUCCESS;
- //队列处理
- qdisc_run(dev);
- //如果队列发送没有关闭,并且队列调度还没有运行,则运行队列调度
- if (!netif_queue_stopped(dev)
- &&!test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))
- __qdisc_run(dev);
- //如果队列为noop_qdisc类型,则表明接口还未启动,则直接退
- //出队列调度
- if (unlikely(dev->qdisc == &noop_qdisc))
- goto out;
- //当还有待处理的数据,并且发送队列没有关闭,则循环从队列
- //中取出数据,使用网卡驱动进行发送数据。该函数具体实现不
- //详细列出。之前在《接口设备发包》中已经分析过,可以参考
- //那节的分析。这里主要关注,从队列中取出数据是使用的队列
- //的dequeue回调,当前HTB类型的排队规则回调为
- //htb_dequeue,htb_dequeue在下面单独分析。
- while (qdisc_restart(dev) < 0 && !netif_queue_stopped(dev))
- ;
- out:
- //去除正在进行队列调度标记
- clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);
- spin_unlock(&dev->queue_lock);
- rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;
- goto out;
- spin_unlock(&dev->queue_lock);
- ---------------------------------------------------------------------------------------------------------------------
- //使用过滤器进行分类
- tc_classify
- protocol = skb->protocol;
- //遍历所有过滤器
- for ( ; tp; tp = tp->next)
- //首先检测当前报文的协议与过滤器中的协议匹配,或者过滤器匹配任何协议类
- //型,则进一步使用当前过滤器的classify回调进行分类处理,当前u32的回调
- //函数为u32_classify,u32_classify在下面单独分析。
- if ((tp->protocol == protocol ||tp->protocol == __constant_htons(ETH_P_ALL)) &&
- (err = tp->classify(skb, tp, res)) >= 0)
- //返回处理结果
- return err;
- //返回-1,表明分类失败。
- return -1;
- ---------------------------------------------------------------------------------------------------------------------
- 使用u32分类器进行分类处理
- u32_classify
- //过滤协议对象的root,即首hash链表对象
- tc_u_hnode *ht = (struct tc_u_hnode*)tp->root;
- u8 *ptr = skb->nh.raw; //报文的IP头位置
- int sel = 0;
- next_ht:
- n = ht->ht[sel];
- next_knode:
- //判断当前hash关键节点是否存在
- if (n)
- struct tc_u32_key *key = n->sel.keys; //hash关键节点对象的匹配项
- //遍历当前hash关键节点的所有匹配项,如果有任何一项匹配失败,则使用
- //goto进入下一个hash关键节点进行匹配。这里匹配项中的off就是对应项
- //(比如dport)在报文IP文件位置的偏移。
- for (i = n->sel.nkeys; i>0; i--, key++)
- if ((*(u32*)(ptr+key->off+(off2&key->offmask))^key->val)&key->mask)
- n = n->next;
- goto next_knode;
- //ht_down和命令行中link配置相关,用于多层嵌套,可以实现跳转到其它hash链
- //节点进行查找,当前测试实例没有配置嵌套,这里ht_down 为NULL。
- if (n->ht_down == NULL)
- check_terminal:
- //在用户侧命令接口已经对当前过滤选择项设置了U32_TERMINAL
- if (n->sel.flags&TC_U32_TERMINAL)
- *res = n->res; //返回该过滤选择项的结果,这里含有绑定的类
- //进行扩展属性处理,处理失败尝试下一个关键匹配点,当前命令接口
- //没有设置相关扩展项,所以这里返回为0
- r = tcf_exts_exec(skb, &n->exts, res);
- if (r < 0)
- n = n->next;
- goto next_knode;
- //当前匹配完成,返回结果。
- return r;
- //如果hash关键节点匹配项匹配完成,但不含U32_TERMINAL处理结束的标
- //记,则尝试下一个hash关键节点。
- n = n->next;
- goto next_knode;
- //n->ht_down不为空,表明有嵌套匹配处理
- //先将当前hash关键节点及匹配指针压入临时变量stack栈中
- stack[sdepth].knode = n;
- stack[sdepth].ptr = ptr;
- sdepth++;
- //取出待跳转的hash链对象
- ht = n->ht_down;
- sel = 0;
- //如果当前hash链对象含有divisor值,表明该hash链对象是之前通过命令接口
- //使用divisor关键字段创建的含有多个列表项的hash链对象,则这里根据之
- //前命令接口设置的散列因子相关的参数(比如根据源IP地址最后1个字节进行散
- //列因子处理)进行计算,来确定当前的报文应该在当前hash链对象的哪个列表
- //项中进行处理。
- if (ht->divisor)
- sel = ht->divisor&u32_hash_fold(*(u32*)(ptr+n->sel.hoff), &n->sel,n->fshift);
- //如果当前hash关键节点的选择项中不含有任何偏移属性,则直接跳转到下一个
- //hash链节点中进行查找。
- if (!(n->sel.flags&(TC_U32_VAROFFSET|TC_U32_OFFSET|TC_U32_EAT)))
- goto next_ht;
- //走到此流程,表明当前hash关键节点中含有相关的偏移属性,则根据用户接口
- //配置进行对应的偏移处理,如果处理完后报文合法,则跳转到下一个hash链节
- //点中进行查找。
- if (n->sel.flags&(TC_U32_OFFSET|TC_U32_VAROFFSET))
- off2 = n->sel.off + 3;
- if (n->sel.flags&TC_U32_VAROFFSET)
- off2 += ntohs(n->sel.offmask & *(u16*)(ptr+n->sel.offoff)) >>n->sel.offshift;
- off2 &= ~3;
- if (n->sel.flags&TC_U32_EAT)
- ptr += off2;
- off2 = 0;
- if (ptr < skb->tail)
- goto next_ht;
- //用户接口含有link关键字时,则进行嵌套处理,每进行一次嵌套则将前一次处理结果
- //存储到临时变量stack中,当最后的嵌套处理没有匹配成功时,会走到此流程,如果
- //当前确定是嵌套处理,则取出前一次的hash链对象继续进行处理。
- if (sdepth--)
- n = stack[sdepth].knode;
- ht = n->ht_up;
- ptr = stack[sdepth].ptr;
- goto check_terminal;
- //分类失败
- return -1;
- ---------------------------------------------------------------------------------------------------------------------
- //HTB数据包出队处理
- htb_dequeue
- q->jiffies = jiffies; //记载当前滴答
- //如果当前直接发送队列含有报文,则优先进行处理,直接将报文取出。
- skb = __skb_dequeue(&q->direct_queue);
- if (skb != NULL)
- sch->flags &= ~TCQ_F_THROTTLED;
- sch->q.qlen--;
- return skb;
- //队列无数据则返回为NULL
- if (!sch->q.qlen)
- goto fin;
- //获取当前系统时间
- PSCHED_GET_TIME(q->now);
- min_delay = LONG_MAX;
- q->nwc_hit = 0;
- //从第0层开始遍历处理,第0层为叶子类层
- for (level = 0; level < TC_HTB_MAXDEPTH; level++)
- //当前流逝时间已经超过最近事件的缓冲时间
- if (time_after_eq(q->jiffies, q->near_ev_cache[level]))
- delay = htb_do_events(q, level);
- //最多处理500个被阻塞的报文
- for (i = 0; i < 500; i++)
- //取当前层中第1个被阻塞的树节点
- struct rb_node *p = rb_first(&q->wait_pq[level]);
- //如果当前没有阻塞的报文,则返回0延时。
- if (!p)
- return 0;
- //获取包含该树节点的HTB类对象
- cl = rb_entry(p, struct htb_class, pq_node);
- //如果当前类的阻塞时间还未到,则继续阻塞,返回还有多长时间会
- //到的时间差。
- if (time_after(cl->pq_key, q->jiffies))
- return cl->pq_key - q->jiffies;
- //当前类的阻塞时间已到。
- //将树节点从阻塞树中删除。
- htb_safe_rb_erase(p, q->wait_pq + level);
- //计算当前类最后一次处理到当前的时间差,该时间差不能超过
- //mbuffer值(在创建类时该值为1分钟)。
- diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32) cl->mbuffer);
- //进行类的模式改变
- htb_change_class_mode(q, cl, &diff);
- new_mode = htb_class_mode(cl, diff);
- //这里ctokens是租借模式下令牌数,经过一段时间后令牌
- //数就会进行补充。待补充后仍然小于低水位线,则状态变
- //为HTB_CANT_SEND(不能进行发送),这里htb_lowater
- //低水位线根据当前类的模式不同而不同,如果当前类模式
- //为HTB_CANT_SEND,则低水位线的值为-cl->cbuffer,也
- //就是租借模式下单包最大可传达数据所需要的ticket,其它
- //模块下低水位线的值为0。
- if ((toks = (cl->ctokens + *diff)) < htb_lowater(cl))
- *diff = -toks; //返回还需要多少令牌
- return HTB_CANT_SEND;
- //这里tokens是非租借模式下令牌数,经过一段时间补充后,
- //如果高于高水位线,则状态变为HTB_CAN_SEND
- //(可以发送)
- if ((toks = (cl->tokens + *diff)) >= htb_hiwater(cl))
- return HTB_CAN_SEND;
- //状态变为可租借,返回还需要多少令牌
- *diff = -toks;
- return HTB_MAY_BORROW;
- //当前模式相同则直接返回。
- if (new_mode == cl->cmode)
- reutrn;
- //如果当前类有激活,则根据新、老模式进行类的激活、去激活
- //处理(之前为非不可发送模式,则将当前类去激活,该类后
- //续暂时不能发包,当前新的模式为可发送模式,则将当前类
- //激活,该类后续可以继续发包,当前新的模式为租借模式,则
- //激活父类,并将当前类挂接到父类的feed供给树下)。
- //否则当前类没有激活,则仅修改当前模式。
- if (cl->prio_activity)
- if (cl->cmode != HTB_CANT_SEND)
- htb_deactivate_prios(q, cl);
- cl->cmode = new_mode;
- if (new_mode != HTB_CANT_SEND)
- htb_activate_prios(q, cl);
- else
- cl->cmode = new_mode;
- //如果当前模式不可发送,则将类插入到q->wait_pq[level]等待树中。
- //同时更新类的cl->pq_key等待关键字的值为当前jitter加上diff,其
- //中diff是在上面htb_change_class_mode中计算得到。
- if (cl->cmode != HTB_CAN_SEND)
- htb_add_to_wait_tree(q, cl, diff);
- //在上面等待树中最多处理500个等待报文,超过则放到下轮再来处理,
- //同时返回延时时间为100ms。
- return HZ / 10;
- //更新最近事件处理时间,如果没有等待处理的包,则delay为0,下一次事件
- //处理则加长一些(1秒),否则还有等待处理的包,则下一次事件处理的时间
- //设置为上面的delay值。
- q->near_ev_cache[level] =q->jiffies + (delay ? delay : HZ);
- else
- //如果当前流逝时间还未到达下一次事件处理时间,则delay取值为下一次事
- //件处理所需要的时间点。
- delay = q->near_ev_cache[level] - q->jiffies;
- //因为最终在下面进行延时处理是使用min_delay的值,这里对该值进行校正
- if (delay && min_delay > delay)
- min_delay = delay;
- //row_mask[level]为当前层已经激活的类,这里取反是为了方便下面使用ffz进行
- //查找第一个为0的位所在索引。
- m = ~q->row_mask[level];
- //优先级激活位图如果取反为全为1,则表明当前层的类都没有被激活。
- while (m != (int)(-1))
- //优先取最低优先级位进行处理。
- int prio = ffz(m);
- m |= 1 << prio; //每处理一次,当前优先级位就清除,以免二次进入。
- //指定层次、指定优先级的树中取出待发送的skb报文。
- skb = htb_dequeue_tree(q, prio, level);
- //q->row记载着激活的位图,仅类被激活后才可以发送报文,当子类未激
- //活时,从父类中租借代宽,父类就会激活。
- //q->ptr记载了最后处理的的类的树节点。
- //q->last_ptr_id记载了最后处理的类ID。
- //这里根据DDR算法,查找出可调度的HTB类。
- //htb_lookup_leaf函数比较复杂,放在下面单独分析。
- start = cl = htb_lookup_leaf(q->row[level] + prio, prio,q->ptr[level] + prio,
- q->last_ptr_id[level] + prio);
- do
- next:
- //由于代宽受限等原因导致当前没有可出队的报文。
- if (!cl)
- return NULL;
- //如果当前类的队列为空
- if (unlikely(cl->un.leaf.q->q.qlen == 0))
- //该类去激活
- htb_deactivate(q, cl);
- //检查如果当前层、当前优先级已经没有激活的类,则返回为
- //NULL,让上面函数继续下一层、下一优先级类的处理。
- if ((q->row_mask[level] & (1 << prio)) == 0)
- return NULL;
- //当前调度的类没有队列,并且当前层、当前优先级下还有激活
- //的类,则再次调用htb_lookup_leaf查找可调度的类。同时更新
- //临时变量start、cl。
- next = htb_lookup_leaf(q->row[level] + prio,
- prio, q->ptr[level] + prio,q->last_ptr_id[level] + prio);
- if (cl == start)
- start = next;
- cl = next;
- goto next;
- //使用当前类下的排队规则的dequeue回调进行将报文出队,这里
- //dequeue回调实现取决于用户侧给该类下绑定什么排队规则,如
- //果启用未设置,则默认HTB类下的排队规则为pfifo_qdisc,则
- //对应的回调函数为qdisc_dequeue_head,该函数比较简单,仅仅
- //是仅出双向链表中第一个节点的报文。
- skb = cl->un.leaf.q->dequeue(cl->un.leaf.q);
- //报文取出后直接跳出do while循环,返回。
- if (likely(skb != NULL))
- break;
- //当该类下标明有队列,但取出失败,则标记告警。
- if (!cl->warned)
- cl->warned = 1;
- q->nwc_hit++; //统计不工作的错误类计数
- //将之前保存记录的ptr(最后处理的的类的树节点)更新为下一个节
- //点。
- htb_next_rb_node((level ? cl->parent->un.inner.ptr : q->ptr[0]) + prio);
- //再次调用htb_lookup_leaf查找可调度的类
- cl = htb_lookup_leaf(q->row[level] + prio, prio,q->ptr[level] + prio,
- q->last_ptr_id[level] + prio);
- //走到这里,表明刚才调度类在出队报文时异常,又重新选择了新的调度
- //类。重新进行出队处理。
- while (cl != start)
- //报文成功出队
- if (likely(skb != null))
- //htb_next_rb_node用来修改下一次使用htb_lookup_leaf选择调度
- //类时先从哪个类进行处理,这里根据quantum值的限额来进行
- //控制,可以防止htb_lookup_leaf在一段时间内始终使用相同的类
- //进行调度。
- if ((cl->un.leaf.deficit[level] -= skb->len) < 0)
- cl->un.leaf.deficit[level] += cl->un.leaf.quantum;
- htb_next_rb_node((level ? cl->parent->un.inner.ptr : q->ptr[0]) +
- prio);
- //当前类下的排队规则中已经没有报文,则将该类去激活。
- if (!cl->un.leaf.q->q.qlen)
- htb_deactivate(q, cl);
- //对该类进行收费计算。
- htb_charge_class(q, cl, level, skb->len);
- //临时定义了一个计帐功能宏,这里T可以是tokens或ctokens,
- //B可以是buffer或cbuffer,R可以是rate或ceil。
- //T表示租借模式或非租借模式下的令牌数。
- //B表示租借模式或非租借模式下基于对应速率的单包峰值。
- //R表示租借模式或非租借模式下不同速率。
- //首先将根据流逝时间来对所剩的令牌数进行补充。
- #define HTB_ACCNT(T,B,R) toks = diff + cl->T; \
- //对令牌数进行上限峰值限制
- if (toks > cl->B) toks = cl->B; \
- //根据当前报文字节数,在速率表中查找符合当前字节范围
- //之内所需消耗的单位值,之后从令牌总数中扣除消耗的单
- //位值。
- toks -= L2T(cl, cl->R, bytes); \
- //如果令牌数小于最大负等待峰值时间(1分钟),则对令牌
- //数修正为最大负等待峰值时间。
- if (toks <= -cl->mbuffer) toks = 1-cl->mbuffer; \
- //将剩余的令牌数保留,供下一次使用。
- cl->T = toks
- while (cl)
- //计算当前HTB类最后一个工作到当前已经流逝的时间,
- //不能超过mbuffer上限。
- diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32)
- cl->mbuffer);
- //该函数第一次处理的cl类都是叶子类,之后在while
- //循环中cl类依次改变为当前类的父类,而level在循环
- //中是始终保持不变的。如果当前level是0,表明叶子一直
- //未超过最低速率,始终是可发送的。如果当前level非0,
- //表明叶子已经在借用父类的带宽。当cl->level >= level,则
- //对当前类及父类都进行tokens计费处理,当
- //cl->level < level则表明当前类是在对父类进行租借,不
- //再对当前类进行tokens计费处理,仅对提供租借的父类
- //进行tokens计费处理。但不管当前类是否在租借都会
- //对ctokens进行计费处理。这里要理解tokens与ctokens
- //的差别,tokens是当前类的速率限制,ctokens是可向父类
- //借用的速率限制。如果使用当前类发送报文时小于tokens,
- //则当前类速率还比较正常,可以继续处理,如果当前发送
- //报文时大于tokens但小于ctokens,则表明当前类已经超出
- //自己的速率限制,但还可以从父类借用带宽,如果当前发
- //送报文时大于ctokens,则表明都超出了可借用速率,此时
- //该类的模式会修改为不可发送模式,当前类暂时已经不能
- //在发送任何报文。
- if (cl->level >= level)
- if (cl->level == level)
- cl->xstats.lends++;
- HTB_ACCNT(tokens, buffer, rate);
- else
- cl->xstats.borrows++;
- cl->tokens += diff;
- HTB_ACCNT(ctokens, cbuffer, ceil);
- //记载当前类最后处理时间,用于下一个时间点来统计已经
- //流逝的时间。
- cl->t_c = q->now;
- //根据当前类中tokens、ctokens剩余令牌数,使用
- //htb_change_class_mode进行HTB类的模式切换处理。
- //同时根据是否还是可发送模式而确定是否将类插入到
- //HTB排队规则的等待树中。
- old_mode = cl->cmode;
- diff = 0;
- htb_change_class_mode(q, cl, &diff);
- if (old_mode != cl->cmode)
- if (old_mode != HTB_CAN_SEND)
- htb_safe_rb_erase(&cl->pq_node, q->wait_pq +
- cl->level);
- if (cl->cmode != HTB_CAN_SEND)
- htb_add_to_wait_tree(q, cl, diff);
- //非叶子类的统计。
- if (cl->level)
- cl->bstats.bytes += bytes;
- cl->bstats.packets++;
- //将cl修改为当前类的父类,在while循环中依次对父类
- //进行处理。
- cl = cl->parent;
- //如果成功取出报文,则HTB排队规则的队列个数递减,同时去除受限标记,
- //将当前skb返回。
- if (likely(skb != NULL))
- sch->q.qlen--;
- sch->flags &= ~TCQ_F_THROTTLED;
- goto fin;
- //当前HTB排队规则中有报文,但遍历完所有层、所有优先级的类后,没有成功取出
- //报文,则进行延时处理。
- htb_delay_by(sch, min_delay > 5 * HZ ? 5 * HZ : min_delay);
- //修改当前HTB排队规则的timer定时器时间,该定时器是在创建HTB排队规则
- //时,通过htb_init函数创建,该定时器的超时处理函数为htb_timer,该函数比较
- //简单,仅仅是去除TCQ_F_THROTTLED受限标记,之后调用netif_schedule进行
- //发送队列的重新调度。
- mod_timer(&q->timer, q->jiffies + delay);
- //加上受限标记,更新overlimits超出限速的统计。
- sch->flags |= TCQ_F_THROTTLED;
- sch->qstats.overlimits++;
- -----------------------------------------------------------------------------------------------------------------
- 查找可调度的叶子类(虽然代码不多,但这里是整个HTB排队规则中最难的一部分)
- htb_lookup_leaf(struct rb_root *tree, int prio,struct rb_node **pptr, u32 * pid)
- //定义了8个元素的数组,初始sp指向第0个元素。
- struct {
- struct rb_node *root;
- struct rb_node **pptr;
- u32 *pid;
- } stk[TC_HTB_MAXDEPTH], *sp = stk;
- //当前第0个元素的初始值由传入的参数提供,root指向当前层次、当前优先级下待处
- //理的HTB类所在的树节点。pptr指向HTB排队规则中记载的最后处理的树节点,
- //注意这里使用了双层指针,在当前函数处理时就可以通过*pptr来对传入参数进行修
- //改,更新HTB排队规则中记载的最后处理树节点,以方便下一次报文处理时使用。
- //pid指向HTB排队规则中记载的最后处理的类ID,同样,这里可以通过*pid来对传
- //入参数进行修改。
- sp->root = tree->rb_node;
- sp->pptr = pptr;
- sp->pid = pid;
- //尝试65535次处理
- for (i = 0; i < 65535; i++)
- //当树节点不存在,但类ID存在,则使用类ID尝试在当前待处理的树中进行查
- //找树节点。通过查看代码,发现有一种情景会走此流程,如果一个HTB叶子
- //类为租借模式,当其发送报文后,队列中已经没有数据,则触发htb_deactivate_prios
- //函数,该函数会判断当前类为租借模式,并且正好匹配父类中un.inner.ptr
- //记载的最后处理树节点,则将父类的un.inner.ptr设置为空,同时父类的
- //un.inner.last_ptr_id保留此叶子类的ID,并将该类的树节点从父类的供应树中
- //删除。之后立即有一个新的报文使用htb_enqueue函数对该叶子类又进行入队
- //操作,则触发htb_activate_prios函数将此叶子类又加入到父类的供应树中。
- //那么当使用htb_lookup_leaf查找可调度类时,如果q->ptr队列的最后树节点
- //处理正好是这个父类,在此函数中会将sp->root指向父类的供应树,sp->pptr
- //指向父类的un.inner.ptr,sp->pid指向父类的un.inner.last_ptr_id,此时正好满足
- //当前IF的条件判断。
- if (!*sp->pptr && *sp->pid)
- *sp->pptr = htb_id_find_next_upper(prio, sp->root, *sp->pid);
- //清除pid,保证上面的条件判断仅触发一次。
- *sp->pid = 0;
- //如果当前树节点不存在
- if (!*sp->pptr)
- //将树节点指向当前待处理的树根节点。一定要留意,使用了*sp也就意味着
- //将sp->pptr之前指向的对象内容做了修改,即如果之前对象是指针类型,则
- //把原对象的指针指向进行了修改。
- *sp->pptr = sp->root;
- //将pptr指向当前树中最小类ID的节点(生成该树时就是以类ID为索引进行
- //的红黑树排序)。
- while ((*sp->pptr)->rb_left)
- *sp->pptr = (*sp->pptr)->rb_left;
- //这里stk就是上面的临时数组,也相当于数组的第0元素指针,刚进入该
- //函数时,sp是指向数组的第0个元素的,当下面发现找到的调度类
- //非叶子类时,sp就会递增,同时sp将指向该类的供给类(也就是该
- //类的孩子类)。如果出现这种情况,则sp--来退到当前类的父类,同时
- //对父类的sp->pptr最后使用的树节点进行更新,htb_next_rb_node函数
- //是将sp->pptr值设置为比sp->pptr更大一点的节点(如果有右子树,则
- //取右子树中最小的树节点;否则如果有父亲则取父亲中最小的树节点;
- //否则如果没有父子节点则取值为NULL)。
- if (sp > stk)
- sp--;
- if (!*sp->pptr)
- return NULL;
- htb_next_rb_node(sp->pptr);
- else
- //pptr找到了树节点,则取出包含该树节点的类对象
- cl = rb_entry(*sp->pptr, struct htb_class, node[prio]);
- //检测如果当前是叶子类则表示查找成功,返回该叶子类。
- if (!cl->level)
- return cl;
- //走到此流程表明找到的类是非叶子类型,此时递增sp,使其指向stk的下一
- //个数组元素,同时将该类的相关信息存储到这个新的数组元素中。root指向
- //当前类供应的子类树节点,pptr指向当前类的最后处理树节点,pid指向当前
- //类的最后处理类ID。
- (++sp)->root = cl->un.inner.feed[prio].rb_node;
- sp->pptr = cl->un.inner.ptr + prio;
- sp->pid = cl->un.inner.last_ptr_id + prio;
- //经过这么多次查找,仍然没找到可调度的叶子类,则查找失败,返回NULL。
- return NULL;
- ---------------------------------------------------------------------------------------------------------------------
- (注意: 以下分析仅考虑htb_lookup_leaf函数配合类激活、去激活的流程分析,并未考虑数据报发送成功后,在其它条件下也会使用htb_next_rb_node进行下一个待处理的树节点修改的情况)。
- 一、到这里已经对htb_lookup_leaf怎样进行查找可调度的叶子类分析完成,但可能还是云里雾里,这里用一个实例来描述htb_lookup_leaf函数的晦涩算法机制。在如下实例中,我们假设仅创建了三个类(根父类、子类1、子类2),这里根类的层次为最高层7,叶子类的层次为最低层0,同时假设这三个类的优先级都为0级,所以可以看到下图中子类1与子类2在同层次、同优先级情况下构建了一棵树,并且假设子类1的ID比较小,所以子类1在红黑树的左侧。
- <img src="https://img-blog.csdn.net/20141214041248546?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
- 1、最初时,子类1、与子类2都为可发送模式,并且随着报文进入对应类的队列,类分别被激活。
- 2、在出队时,根据第0层优先、0优先级优先的原则,此时会找到子类2的树对象上。
- 3、之后使用htb_lookup_leaf函数进行查找可调度的叶子类。
- 4、最初HTB排队规则的q->ptr最后处理的树节点为NULL,所以会走到if (!*sp->pptr)流程,在该流程中会更新q->ptr,使其指向子类2的树节点,之后又遍历该树中最小的类ID节点,所以q->ptr又被更新为子类1树节点。此时子类1是叶子类,查找成功,返回当前调度类为子类1。
- 5、后续由于q->ptr最后处理树节点指向了子类1,所有只要子类1一直有待发送的数据,则始终使用子类1进行调度。
- 二、当子类1不断发包,已经超出其带宽限制后。
- <img src="https://img-blog.csdn.net/20141214041507730?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" height="194" width="304" alt="" />
- 1、子类1的模式由可发送模式变为租借模式,在模式变更时触发了两个关键动作,首先子类1进行去激活处理,在去激活处理过程中q->ptr指向了同层次、同优先级、比子类1略大一点的子类2上。其次,父类被激活,同时父类的供给树根节点指向了子类1的树节点。
- 2、在后续出队时,优先处理第0层、第0优先级的类,当前q->ptr已经指向子类2,所以后续始终用子类2进行调度。
- 三、当子类2不断发包,已经超出其带宽限制后。
- <img src="https://img-blog.csdn.net/20141214041400431?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
- 1、子类2的模式由可发送模式变为租借模式,在模式变更时触发了两个关键动作,首先子类2进行去激活处理,在去激活处理过程中由于子类2已经是只含1个节点的树了,q->ptr指向了NULL。其次,子类2也被加入到了父类的供给树中,由于和子类1的优先级相同,所以子类2和子类1又成为一棵树上的节点。
- 2、此时第0层的类已经没有激活的,所以只能从当前只激活的第7层类中进行处理。首先检测当前q->ptr为NULL,则将q->ptr指向父类的树节点。(注意当前父类的树仅有一个节点,图中画的连线并非父节点的子节点,也就是说父类在HTB排队规则中指定层次、指定优先级的那棵树下仅自己一个节点,而父类自身有一个feed供给树,用来标识当前父类都给哪些孩子类提供带宽借用。)
- 3、之后判断当前父类并非叶子类,则将sp指向stk数组的第1个元素,第1个元素的
- root指向当前父类feed供给树的根节点(子类1),第1个元素的pptr指向父类的cl->un.inner.ptr,第1个元素的pid指向父类的cl->un.inner.last_ptr_id。
- 4、由于最终父类的cl->un.inner.ptr还为NULL值,所有又走到if (!*sp->pptr)条件里去,在这里将父类的cl->un.inner.ptr指向了父类的供给树根节点(子类1)。
- 5、sp此时指向stk数组的第1个元素,所有if (sp > stk)条件成立,将sp指针递减回第0个数组元素,第0个数组元素在第2步中已经将q->ptr指向父类的树节点,由于父类树节点仅有一个树节点,所有在执行htb_next_rb_node(sp->pptr)时返回NULL,导致q->ptr又变成NULL值。
- 6、继续for循环处理,又将q->ptr指向父类,之后判断当前父类并非叶子类,则将sp指向stk数组的第1个元素,第1个元素的root指向当前父类feed供给树的根节点(子类1),第1个元素的pptr指向父类的cl->un.inner.ptr,第1个元素的pid指向父类的cl->un.inner.last_ptr_id。
- 7、此时父类的cl->un.inner.ptr已经指向子类1,所以判断子类1为叶子类,则对子类1进行调度。
- 8、后续只要子类1中始终有报文,则重复第6、7步对子类1进行调度处理。