之前通过《默认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;      //0opt.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;                    //NULLcl->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;      //0cl->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;      //0opt.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;   //7memset(&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;      //0cl->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;             //0x00010000dev = __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 = tokswhile (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进行调度处理。

HTB分层令牌桶排队规则分析相关推荐

  1. 简单分析Guava中RateLimiter中的令牌桶算法的实现

    为什么80%的码农都做不了架构师?>>>    令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法.典型情况下,令 ...

  2. java令牌桶_简单分析Guava中RateLimiter中的令牌桶算法的实现

    令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法.典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送. ...

  3. 基于令牌桶算法的限流器RateLimiter分析

    RateLimiter限流源码分析 主要从以下几个方面进行分析 RateLimiter工作原理的总体介绍 RateLimiter的继承关系(基类与子类的关系图) 关键属性字段分析 核心方法分析(创建- ...

  4. 分析常见限流算法及手写三种(计数器、漏斗、令牌桶)代码实现

    常见的限流算法分析 限流在我们日常生活中经常见到,如火车站门口的栏杆.一些景点的门票只出售一定的数量 等等.在我们的开发中也用到了这种思想. 为什么要限流 在保证可用的情况下尽可能多增加进入的人数,其 ...

  5. sentinel 时间窗口_Sentinel使用令牌桶实现预热【原理源码】

    前言 Sentinel的QPS流控效果有快速失败.预热模式.排队等待.预热+排队等待模式,本文主要分析预热模式中是如何使用令牌桶算法限流的. 一.流控效果源码结构 在FlowRule更新缓存时,根据配 ...

  6. 令牌桶算法和漏桶算法有什么区别_高并发之限流,到底限的什么鬼 (精品长文)...

    你可能知道高并发系统需要限流这个东西,但具体是限制的什么,该如何去做,还是模凌两可.我们接下来系统性的给它归个小类,希望对你有所帮助. google guava中提供了一个限流实现: RateLimi ...

  7. CIR,CBS,EBS,PIR,PBS傻傻分不清楚?看这里!—-揭秘令牌桶

    概述 春暖花开的时候,大家都开着汽车外出旅游欣赏美丽的风景,却被堵在高速公路上,你是否为此感到痛苦?但如果有一种机制可以评估高速公路上的车流量.控制车流情况,确保进入高速公路的汽车都能在路上安全畅行, ...

  8. 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 作者:雄 ...

  9. rateLimiter令牌桶限流算法

    RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...

  10. 脸书令牌怎么使用_网工知识角|QOS技术令牌桶算法一分钟速记,考试无忧

    点上方蓝字关注公众号,坚持每天技术打卡 学网络,就在IE-LAB 国内最著名的高端网络工程师培养基地 QOS是网络服务质量技术,是所有考试中都会有的考点,主要在SP的课程中详述,是高中低级的网工都有必 ...

最新文章

  1. 整合vs2005sp1到vs2005安装文件中
  2. 取消默认html打开文档,怎么取消mac默认打开文档方式
  3. PHP之factory
  4. linux ssh服务端下载文件,Linux SSH服务端配置文件设置
  5. 链表之判断一个链表是否为回文结构(三)
  6. java看图_看图吧,Java
  7. mod_python模块安装
  8. python3获取用户输入_python3.4控制用户输入与输出
  9. monkeyrunner API接口文档内容
  10. 力扣231.2的幂 C语言
  11. 侯捷——STL源码剖析 笔记
  12. 蓝牙调试工具如何使用_更新:使用此有价值的工具改进您的蓝牙项目
  13. JS修改链接地址实现页面动态跳转的方法
  14. Fite-C02-1班成员学习进度记录
  15. Redis的那些事儿:关系型和非关系型数据库,非关系型数据库的类型,redis数据类型、编码格式、高性能、可以做什么、分布式锁失效的原因,string为采用sds数据类型,为什么是二进制安全的,
  16. Vue路由管理(菜单列表)
  17. Python给定两个有序整数数组nums1和nums2,合并nums1和nums2为数组nums1
  18. 2018.6清北学堂day6考试
  19. 服务器系统raid设置,服务器RAID配置详解
  20. 【猪八戒】- 2017年在线笔试“叠字问题”

热门文章

  1. Easy Mobile插件之Advertising
  2. 七缸发动机预热,docker swarm + .net core 高速飙车成功
  3. php gd ttf,linux gd php TTF字体
  4. 《编程之道》(转载) 全文
  5. 用MicroPython开发ESP32-文件传输工具-ampy
  6. 【方向盘】2020年感悟关键词:科比、裁员、管理层、活着
  7. Blue Screen Of Death ( BSOD ) 错误信息解析解释
  8. 一家椰子鸡店,凭什么让客户去了一趟又一趟?到底有什么样的魔力?
  9. 业务性管理(business continuity management)
  10. 大二Git-Branching学习