HTB分层令牌桶排队规则分析--iproute-tc分析

来源:互联网 发布:学英语软件有哪些 编辑:程序博客网 时间:2024/05/13 07:21
[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 之前通过《默认FIFO_FAST出口排队规则分析》、《ingress入口排队规则分析》分析,已经对排队规则的基础架框有了简单的了解。那两种排队规则都是无类的,这里选出可以分类的HTB排队规则进行分析。  
  2.   
  3.   
  4. <img src="http://img.blog.csdn.net/20141214040026609?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />  
  5. 当前实例分析的基本对象关联图  
  6.   
  7. 一、当前分析的配置范例  
  8. //在eth0设备上创建一个根HTB排队规则,当未匹配任何过滤器时,将报文放入ID为20  
  9. //的分类中  
  10. tc qdisc add dev eth0 root handle 1: htb default 20  
  11.   
  12. //在根HTB排队规则上创建ID为1的分类  
  13. tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k  
  14.   
  15. //在ID为1的分类上分别创建两个ID为10、ID为20的分类  
  16. tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k  
  17. tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k  
  18.   
  19. //创建两个U32分类器  
  20. U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"  
  21. $U32 match ip dport 80 0xffff flowid 1:10  
  22. $U32 match ip sport 25 0xffff flowid 1:20  
  23.   
  24. 二、创建根HTB排队规则  
  25. 1、用户层分析  
  26. //初始化,获取每纳秒对应多少TICKET  
  27. tc_core_init();  
  28. fp = fopen("/proc/net/psched""r");  
  29. fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);  
  30. fclose(fp);  
  31.   
  32. if (clock_res == 1000000000)  
  33. t2us = us2t;  
  34.   
  35. clock_factor  = (double)clock_res / TIME_UNITS_PER_SEC;  
  36. tick_in_usec = (double)t2us / us2t * clock_factor;  
  37.   
  38. //创建一个ROUTE类型的netlink套接口  
  39. rtnl_open(&rth, 0)  
  40. rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);  
  41. rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);  
  42. setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))  
  43. setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))  
  44. rth->local.nl_family = AF_NETLINK;  
  45. rth->local.nl_groups = subscriptions;    //0  
  46. bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))  
  47. rth->seq = time(NULL);  
  48.   
  49. do_cmd(argc-1, argv+1);  
  50. if (matches(*argv, "qdisc") == 0)  
  51. //执行设置排队规则的命令  
  52. do_qdisc(argc-1, argv+1);  
  53. if (matches(*argv, "add") == 0)  
  54. tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE,   
  55. argc-1, argv+1);  
  56. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));  
  57. req.n.nlmsg_flags = NLM_F_REQUEST|flags;  
  58. req.n.nlmsg_type = cmd; //RTM_NEWQDISC  
  59. req.t.tcm_family = AF_UNSPEC;  
  60.   
  61. while (argc > 0)  
  62. if (strcmp(*argv, "dev") == 0)  
  63. NEXT_ARG();  
  64. strncpy(d, *argv, sizeof(d)-1);     //eth0  
  65. else if (strcmp(*argv, "root") == 0)  
  66. req.t.tcm_parent = TC_H_ROOT;  
  67. else if (strcmp(*argv, "handle") == 0)  
  68. NEXT_ARG();  
  69. get_qdisc_handle(&handle, *argv)  
  70. req.t.tcm_handle = handle;  //0x00010000  
  71. else  
  72. //如果有/usr/lib/tc/htb.so动态库中则从中获  
  73. //取htb_qdisc_util符号结构,否则检测当前tc  
  74. //程序是否有htb_qdisc_util符号结构则从中获取  
  75. //,否则返回q 为空。  
  76. q = get_qdisc_kind(k);    
  77.   
  78. //在消息尾部追加KIND属性  
  79. //rta->rta_type = type;  //TCA_KIND  
  80. //rta->rta_len = len;          
  81. //属性值为 “htb”  
  82. addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);  
  83.   
  84. //当前q为htb_qdisc_util,使用其中parse_qopt回调进行其它参数  
  85. //解析,当前回调函数为htb_parse_opt  
  86. q->parse_qopt(q, argc, argv, &req.n)  
  87. htb_parse_opt  
  88. //默认参数  
  89. opt.rate2quantum = 10;  
  90. opt.version = 3;  
  91.   
  92. while (argc > 0)  
  93. if (matches(*argv, "default") == 0)  
  94. NEXT_ARG();  
  95. get_u32(&opt.defcls, *argv, 16) //20  
  96.   
  97. //添加扩展属性OPTIONS,标记后面都是htb的选项  
  98. addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);  
  99.   
  100. //添加扩展属性HTB_INIT  
  101. addattr_l(n, 2024, TCA_HTB_INIT, &opt,   
  102. NLMSG_ALIGN(sizeof(opt)));  
  103.   
  104. //根据接口名获取接口索引  
  105. if (d[0])  
  106. idx = ll_name_to_index(d)  
  107. req.t.tcm_ifindex = idx;  
  108.   
  109. //给内核发送该netlink消息  
  110. rtnl_talk(&rth, &req.n, 0, 0, NULL)  
  111.   
  112. rtnl_close(&rth);  
  113.   
  114. 2、内核层分析  
  115. 用户侧发出RTM_NEWQDISC套接口消息后,在内核侧对应的处理回调函数为tc_modify_qdisc,该函数是在pktsched_init中初始化的。  
  116.   
  117. tc_modify_qdisc  
  118. tcm = NLMSG_DATA(n);  
  119. clid = tcm->tcm_parent;  //当前用户侧传入值为  TC_H_ROOT  
  120.   
  121. //根据设备索引获取设备对象,上面用户侧传入设备名为eth0  
  122. dev = __dev_get_by_index(tcm->tcm_ifindex)  
  123.   
  124. if (clid)  
  125. if (clid != TC_H_ROOT)  
  126. //当前为根排队规则,不走此流程  
  127. else  
  128. q = dev->qdisc_sleeping;  
  129.   
  130. //当前设备存储的是默认的排队规则,则忽略  
  131. if (q && q->handle == 0)  
  132. q = NULL;  
  133.   
  134. if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle)  
  135. if (tcm->tcm_handle) //用户侧传入为特定的0x1  
  136. //当前设备的qdisc_list排队规则链表中不含有此规则,进行创建  
  137. if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)  
  138. goto create_n_graft;  
  139.   
  140. create_n_graft:  
  141.   
  142. //创建排队规则  
  143. q = qdisc_create(dev, tcm->tcm_handle, tca, &err);  
  144. //从已经注册到qdisc_base链表中获取匹配排队规则,当前htb已经注册  
  145. //,则ops = htb_qdisc_ops  
  146. ops = qdisc_lookup_ops(kind);  
  147.   
  148. sch = qdisc_alloc(dev, ops);  
  149. INIT_LIST_HEAD(&sch->list);  
  150. skb_queue_head_init(&sch->q);    //初始化规则中的SKB队列  
  151. sch->ops = ops;  //htb_qdisc_ops  
  152. sch->enqueue = ops->enqueue;  //ingress_enqueue  
  153. sch->dequeue = ops->dequeue;  //ingress_dequeue  
  154. sch->dev = dev;              //eth0设备对象  
  155. dev_hold(dev);              //设备对象引用递增  
  156. sch->stats_lock = &dev->queue_lock;  
  157. atomic_set(&sch->refcnt, 1);  
  158.   
  159. sch->handle = handle;                //0x00010000  
  160.   
  161. //使用排队规则中的初始化回调进行初始化,当前htb的回调函数为  
  162. //htb_init  
  163. ops->init(sch, tca[TCA_OPTIONS-1])  
  164. htb_init(tca[TCA_OPTIONS-1])  
  165. htb_sched *q = qdisc_priv(sch);  
  166.   
  167. //HTB_INIT属性  
  168. gopt = RTA_DATA(tb[TCA_HTB_INIT - 1]);  
  169.   
  170. //初始化根类链表  
  171. INIT_LIST_HEAD(&q->root);  
  172.   
  173. for (i = 0; i < HTB_HSIZE; i++)  
  174. INIT_HLIST_HEAD(q->hash + i);  
  175.   
  176. for (i = 0; i < TC_HTB_NUMPRIO; i++)  
  177. INIT_LIST_HEAD(q->drops + i);  
  178.   
  179. init_timer(&q->timer);  
  180. skb_queue_head_init(&q->direct_queue);  
  181. q->direct_qlen = sch->dev->tx_queue_len;  
  182. if (q->direct_qlen < 2)  
  183. q->direct_qlen = 2;  
  184. q->timer.function = htb_timer;  
  185. q->timer.data = (unsigned long)sch;  
  186.   
  187. //启动了速率定时器,每秒触发一下,其中htb_rate_timer函数在每秒  
  188. //触发会都会根据q->recmp_bucket索引来获取q->hash中的一个  
  189. //HASH链表,对该HASH链表所有条目进行速率计算,之后递增  
  190. //q->recmp_bucket索引,准备下一秒后对下一个HASH链表进行速  
  191. //率计算。当前计算方法也比较简单  
  192. //#define RT_GEN(D,R) R+=D-(R/HTB_EWMAC);D=0  
  193. //RT_GEN(cl->sum_bytes, cl->rate_bytes);  
  194. //RT_GEN(cl->sum_packets, cl->rate_packets);  
  195. init_timer(&q->rttim);  
  196. q->rttim.function = htb_rate_timer;  
  197. q->rttim.data = (unsigned long)sch;  
  198. q->rttim.expires = jiffies + HZ;  
  199. add_timer(&q->rttim);  
  200.   
  201. if ((q->rate2quantum = gopt->rate2quantum) < 1)    //用户默认值为10  
  202. q->rate2quantum = 1;  
  203.   
  204. q->defcls = gopt->defcls;                 //20  
  205.   
  206. //将当前排队规则加入到设备的qdisc_list链表中  
  207. qdisc_lock_tree(dev);  
  208. list_add_tail(&sch->list, &dev->qdisc_list);  
  209. qdisc_unlock_tree(dev);  
  210.   
  211. //排队规则嫁接处理  
  212. qdisc_graft(dev, p, clid, q, &old_q);  
  213. if (parent == NULL) //当前为根排队规则,未有父类  
  214. dev_graft_qdisc(dev, new);  
  215. //设备激活的情况下,先去激活  
  216. if (dev->flags & IFF_UP)  
  217. dev_deactivate(dev);  
  218.   
  219. oqdisc = dev->qdisc_sleeping;  
  220.   
  221. //假设当前仅使用的默认的fifo_fast排队规则,则当前这个老的排队规则  
  222. //已经存在,需要将老的排队规则进行复位,这里fifo_fast的reset回调函  
  223. //数为pfifo_fast_reset  
  224. if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)  
  225. qdisc_reset(oqdisc);  
  226. pfifo_fast_reset  
  227. //丢弃每个频道队列中的所有报文  
  228. for (prio = 0; prio < PFIFO_FAST_BANDS; prio++)  
  229. __qdisc_reset_queue(qdisc, list + prio);  
  230.   
  231. qdisc->qstats.backlog = 0;  
  232. qdisc->q.qlen = 0;  
  233.   
  234. //将新建的排队规则设置到qdisc_sleeping,qdisc的当前规则指向空规则  
  235. //noop_qdisc  
  236. dev->qdisc_sleeping = qdisc;  
  237. dev->qdisc = &noop_qdisc;  
  238.   
  239. if (dev->flags & IFF_UP)  
  240. //设备激活  
  241. dev_activate(dev);  
  242. if (dev->qdisc_sleeping == &noop_qdisc)  
  243. //当前已经有根排队规则,不走此流程  
  244.   
  245. //没有载波则直接返回  
  246. if (!netif_carrier_ok(dev))  
  247. return;  
  248.   
  249. //当前使用的排队规则设置为已经选择的根排队规则  
  250. //启动看门狗。  
  251. rcu_assign_pointer(dev->qdisc, dev->qdisc_sleeping);  
  252. if (dev->qdisc != &noqueue_qdisc)  
  253. dev->trans_start = jiffies;  
  254. dev_watchdog_up(dev);  
  255.   
  256. //发送netlink消息,告知添加成功,并且老的已经删除  
  257. qdisc_notify(skb, n, clid, old_q, q);  
  258.   
  259. //将老的排队规则去除  
  260. qdisc_destroy(old_q);  
  261. //属于内建规则,或者还有其它模块引用,则不进行去除。  
  262. if (qdisc->flags & TCQ_F_BUILTIN || !atomic_dec_and_test(&qdisc->refcnt))  
  263. return;  
  264.   
  265. //从设备的排队规则链表中去除  
  266. list_del(&qdisc->list);  
  267.   
  268. //如果老的排队规则有reset、destroy回调,则进行处理  
  269. if (ops->reset)  
  270. ops->reset(qdisc);  
  271. if (ops->destroy)  
  272. ops->destroy(qdisc);  
  273.   
  274. //资源销毁  
  275. module_put(ops->owner);  
  276. dev_put(qdisc->dev);  
  277. call_rcu(&qdisc->q_rcu, __qdisc_destroy);  
  278.   
  279. 三、创建ID为1的分类  
  280. 1、用户层分析  
  281. //初始化,获取每纳秒对应多少TICKET  
  282. tc_core_init();  
  283. fp = fopen("/proc/net/psched""r");  
  284. fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);  
  285. fclose(fp);  
  286.   
  287. if (clock_res == 1000000000)  
  288. t2us = us2t;  
  289.   
  290. clock_factor  = (double)clock_res / TIME_UNITS_PER_SEC;  
  291. tick_in_usec = (double)t2us / us2t * clock_factor;  
  292.   
  293. //创建一个ROUTE类型的netlink套接口  
  294. rtnl_open(&rth, 0)  
  295. rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);  
  296. rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);  
  297. setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))  
  298. setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))  
  299. rth->local.nl_family = AF_NETLINK;  
  300. rth->local.nl_groups = subscriptions;    //0  
  301. bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))  
  302. rth->seq = time(NULL);  
  303.   
  304. do_cmd(argc-1, argv+1);  
  305. if (matches(*argv, "class") == 0)  
  306. do_class(argc-1, argv+1);  
  307. //创建新的类  
  308. tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1,   
  309. argv+1);  
  310. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));  
  311. req.n.nlmsg_flags = NLM_F_REQUEST|flags;  
  312. req.n.nlmsg_type = cmd;     //RTM_NEWTCLASS  
  313. req.t.tcm_family = AF_UNSPEC;  
  314.   
  315. while (argc > 0)  
  316. if (strcmp(*argv, "dev") == 0)  
  317. NEXT_ARG();  
  318. strncpy(d, *argv, sizeof(d)-1);         //eth0  
  319. else if (strcmp(*argv, "parent") == 0)  
  320. NEXT_ARG();  
  321. get_tc_classid(&handle, *argv)  
  322. req.t.tcm_parent = handle;          //0x00010000  
  323. else if (strcmp(*argv, "classid") == 0)  
  324. NEXT_ARG();  
  325. get_tc_classid(&handle, *argv)  
  326. req.t.tcm_handle = handle;          //0x00010001  
  327. else  
  328. //如果有/usr/lib/tc/htb.so动态库中则从中获取htb_qdisc_util符  
  329. //号结构,否则检测当前tc程序是否有htb_qdisc_util符号结构则  
  330. //从中获取,否则返回q 为空。  
  331. q = get_qdisc_kind(k);  
  332.   
  333. //添加KIND属性项,当前值为“htb”  
  334. addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);  
  335.   
  336. //使用当前扩展排队规则的parse_copt回调去解析后续命令字符,当前  
  337. //htb的回调为htb_parse_class_opt  
  338. q->parse_copt(q, argc, argv, &req.n)  
  339. htb_parse_class_opt  
  340. mtu = 1600;  
  341.   
  342. while (argc > 0)  
  343. if (strcmp(*argv, "rate") == 0)  
  344. NEXT_ARG();  
  345. get_rate64(&rate64, *argv)  //6 * 1000000 / 8  
  346. else if (matches(*argv, "burst") == 0)  
  347. NEXT_ARG();  
  348. //buffer = 15 * 1024  
  349. //cell_log = -1  
  350. get_size_and_cell(&buffer, &cell_log, *argv)  
  351. if (!ceil64)  
  352. ceil64 = rate64;  
  353.   
  354. //超出32位最大值,则转换为全1?  
  355. opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;  
  356. opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;  
  357.   
  358. //如果cbuffer为设置,则取值为当前mtu值加上最大速率下  
  359. //每ticket单位的比特数  
  360. if (!cbuffer)  
  361. cbuffer = ceil64 / get_hz() + mtu;  
  362.   
  363. opt.ceil.overhead = overhead;       //0  
  364. opt.rate.overhead = overhead;       //0  
  365.   
  366. opt.ceil.mpu = mpu; //0  
  367. opt.rate.mpu = mpu; //0  
  368.   
  369. //计算最小速率表  
  370. //cell_log = -1  
  371. //mtu = 1600  
  372. //linklayer = LINKLAYER_ETHERNET  
  373. tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer)  
  374. //根据最大传输单元计算需要多少槽我理解是不可能每个  
  375. //字节都有准确速率,所以划定字节范围,从多少字节到  
  376. //多少字节的速率相同。  
  377. if (cell_log < 0)  
  378. cell_log = 0;  
  379. while ((mtu >> cell_log) > 255)  
  380. cell_log++;  
  381.   
  382. for (i=0; i<256; i++)  
  383. //校正当前槽位的字节大小。这个算法比较简单,当前  
  384. //链路类型为以太网,则包根据原值处理,不会影响包  
  385. //大小。Mpu为最小包大小,如果槽位字节小于mpu,  
  386. //则校正为mpu的值。  
  387. sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);  
  388.   
  389. //根据当前槽位字节大小,及用户配置的速率,计算当  
  390. //前槽位所需ticket时间  
  391. rtab[i] = tc_calc_xmittime(bps, sz);  
  392.   
  393. r->cell_align=-1;  
  394. r->cell_log=cell_log;  
  395. r->linklayer = (linklayer & TC_LINKLAYER_MASK);  
  396.   
  397. //计算单包正常速率下的传送峰值,这里通过包大小转换成所  
  398. //需要的ticket时间  
  399. opt.buffer = tc_calc_xmittime(rate64, buffer);  
  400.   
  401. //计算最大速率表,仅在发包速率大于最小速率后,租借模式下  
  402. //有效。  
  403. tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer)  
  404.   
  405. //计算单包在租借模速率作用下,传送的峰值。  
  406. opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);  
  407.   
  408. //添加扩展属性OPTIONS,标记后面都是htb的选项  
  409. addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);  
  410.   
  411. //超出32位,则添加扩展属性HTB_RATE64  
  412. //超出32位,则添加扩展属性HTB_CEIL64  
  413. if (rate64 >= (1ULL << 32))  
  414. addattr_l(n, 1124, TCA_HTB_RATE64, &rate64,   
  415. sizeof(rate64));  
  416. if (ceil64 >= (1ULL << 32))  
  417. addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64,   
  418. sizeof(ceil64));  
  419.   
  420. //添加扩展属性HTB_PARMS  
  421. addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));  
  422.   
  423. //添加扩展属性HTB_RTAB  
  424. addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);  
  425.   
  426. //添加扩展属性HTB_CTAB  
  427. addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);  
  428.   
  429. //根据接口名获取接口索引  
  430. if (d[0])  
  431. idx = ll_name_to_index(d)  
  432. req.t.tcm_ifindex = idx;  
  433.   
  434. //给内核发送该netlink消息  
  435. rtnl_talk(&rth, &req.n, 0, 0, NULL)  
  436.   
  437. rtnl_close(&rth);  
  438.   
  439. 2、内核层分析  
  440. 用户侧发出RTM_NEWTCLASS套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tclass,该函数是在pktsched_init中初始化的。  
  441.   
  442. tc_ctl_tclass  
  443. pid = tcm->tcm_parent            //父类ID          0x00010000  
  444. clid = tcm->tcm_handle;          //创建类ID     0x00010001  
  445. qid = TC_H_MAJ(clid);           //队列ID          0x00010000  
  446.   
  447. //eth0设备对象  
  448. dev = __dev_get_by_index(tcm->tcm_ifindex))  
  449.   
  450. if (pid != TC_H_ROOT)  
  451. pid = TC_H_MAKE(qid, pid);  //0x00010000  
  452.   
  453. //从当前设备的qdisc_list链表中找到已经创建的排队规则,当前q为之前创建的HTB  
  454. q = qdisc_lookup(dev, qid)  
  455.   
  456. //HTB的回调组为 cops = htb_class_ops  
  457. cops = q->ops->cl_ops;  
  458.   
  459. clid = TC_H_MAKE(qid, clid);        //0x00010001  
  460.   
  461. //get的回调函数为htb_get  
  462. cl = cops->get(q, clid);  
  463. htb_get  
  464. htb_class *cl = htb_find(classid, sch);  
  465. //使用类ID做HASH KEY在当前队列的HASH链表中查找已经创建的类  
  466. hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)  
  467. if (cl->classid == handle)  
  468. return cl;  
  469. return NULL;  
  470.   
  471. //如果找到则增加引用计数  
  472. if (cl)  
  473. cl->refcnt++;  
  474. return (unsigned long)cl;  
  475.   
  476. //当前环境还没有该类存在,进行新类的属性设置  
  477. new_cl = cl;  
  478. //当前回调为 htb_change_class  
  479. cops->change(q, clid, pid, tca, &new_cl);/  
  480. htb_change_class  
  481. //opt指向扩展属性基值索引,后续rtattr_parse_nested进行属性查找都从该  
  482. //基值索引之外进行查找。  
  483. rtattr *opt = tca[TCA_OPTIONS - 1];  
  484.   
  485. //查看枚举定义,TCA_HTB_RTAB值是最后一个,所以rtattr_parse_nested  
  486. //函数会把用户设置的所有扩展参数存储到临时变量tb中。  
  487. //enum  
  488. //{  
  489.     //  TCA_HTB_UNSPEC,  
  490.     //  TCA_HTB_PARMS,  
  491.     //  TCA_HTB_INIT,  
  492.     //  TCA_HTB_CTAB,  
  493. //  TCA_HTB_RTAB,  
  494.     //  __TCA_HTB_MAX,  
  495. //};  
  496. rtattr_parse_nested(tb, TCA_HTB_RTAB, opt)  
  497.   
  498. //parentid = 0x00010000  
  499. //以类ID为HASH KEY向当前队规则的hash链表中查找父类,当前还未存在。  
  500. parent = htb_find(parentid, sch);  
  501. htb_sched *q = qdisc_priv(sch);  
  502. hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)  
  503. if (cl->classid == handle)  
  504. return cl;  
  505. return NULL;  
  506.   
  507. //取用户配置工具设置的HTB参数属性  
  508. hopt = RTA_DATA(tb[TCA_HTB_PARMS - 1]);  
  509.   
  510. //将最小速率表加入到全局qdisc_rtab_list链表中  
  511. rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB - 1]);  
  512.   
  513. //将最大速率表加入到全局qdisc_rtab_list链表中  
  514. ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB - 1]);  
  515.   
  516. //当前为新类进行创建  
  517. if (!cl)  
  518. cl = kzalloc(sizeof(*cl), GFP_KERNEL)  
  519. cl->refcnt = 1;  
  520. INIT_LIST_HEAD(&cl->sibling);  
  521. INIT_HLIST_NODE(&cl->hlist);  
  522. INIT_LIST_HEAD(&cl->children);  
  523. INIT_LIST_HEAD(&cl->un.leaf.drop_list);  
  524. RB_CLEAR_NODE(&cl->pq_node);  
  525.   
  526. for (prio = 0; prio < TC_HTB_NUMPRIO; prio++)  
  527. RB_CLEAR_NODE(&cl->node[prio]);  
  528.   
  529. //创建默认的pfifo排队规则,同时将该排队规则的父亲设置为当前类  
  530. //sch->parent = parentid;        0x00010001  
  531. new_q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops, classid);  
  532.   
  533. //当前新建的类的默认排队规则设置为pfifo  
  534. cl->un.leaf.q = new_q;  
  535.   
  536. cl->classid = classid;               //0x00010001  
  537. cl->parent = parent;                 //NULL  
  538.   
  539. cl->tokens = hopt->buffer;            //正常速率下单包峰值  
  540. cl->ctokens = hopt->cbuffer;          //借用速率下单包峰值  
  541. cl->mbuffer = PSCHED_JIFFIE2US(HZ * 60);  
  542. PSCHED_GET_TIME(cl->t_c);  
  543. cl->cmode = HTB_CAN_SEND;     //初始时可以发送报文  
  544.   
  545. //当新建的类加入到根排队规则的hash表中  
  546. hlist_add_head(&cl->hlist, q->hash + htb_hash(classid));  
  547.   
  548. //将新建的类加入到根排队规则的root链表中  
  549. list_add_tail(&cl->sibling, &q->root);  
  550.   
  551. if (!cl->level)  
  552. //在htb_init时,rate2quantum值默认为1  
  553. //设置quantum值,该值用于在进行带宽租借时的单位量  
  554. cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;  
  555. if (!hopt->quantum && cl->un.leaf.quantum < 1000)  
  556. cl->un.leaf.quantum = 1000;  
  557. if (!hopt->quantum && cl->un.leaf.quantum > 200000)  
  558. cl->un.leaf.quantum = 200000;  
  559.   
  560. cl->un.leaf.prio = hopt->prio;        //0  
  561.   
  562. cl->quantum = cl->un.leaf.quantum;  
  563. cl->prio = cl->un.leaf.prio;  
  564.   
  565. cl->buffer = hopt->buffer;                //正常速率下单包峰值  
  566. cl->cbuffer = hopt->cbuffer;              //借用速率下单包峰值  
  567.   
  568. //设置当前类的最小、最大速率表  
  569. cl->rate = rtab;  
  570. cl->ceil = ctab;  
  571.   
  572. //发送netlink消息,告知添加成功  
  573. tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);  
  574.   
  575. 四、创建ID为20的分类  
  576. 1、用户层分析  
  577. //初始化,获取每纳秒对应多少TICKET  
  578. tc_core_init();  
  579. fp = fopen("/proc/net/psched""r");  
  580. fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);  
  581. fclose(fp);  
  582.   
  583. if (clock_res == 1000000000)  
  584. t2us = us2t;  
  585.   
  586. clock_factor  = (double)clock_res / TIME_UNITS_PER_SEC;  
  587. tick_in_usec = (double)t2us / us2t * clock_factor;  
  588.   
  589. //创建一个ROUTE类型的netlink套接口  
  590. rtnl_open(&rth, 0)  
  591. rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);  
  592. rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);  
  593. setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))  
  594. setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))  
  595. rth->local.nl_family = AF_NETLINK;  
  596. rth->local.nl_groups = subscriptions;    //0  
  597. bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))  
  598. rth->seq = time(NULL);  
  599.   
  600. do_cmd(argc-1, argv+1);  
  601. if (matches(*argv, "class") == 0)  
  602. do_class(argc-1, argv+1);  
  603. //创建新的类  
  604. tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1,   
  605. argv+1);  
  606. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));  
  607. req.n.nlmsg_flags = NLM_F_REQUEST|flags;  
  608. req.n.nlmsg_type = cmd;     //RTM_NEWTCLASS  
  609. req.t.tcm_family = AF_UNSPEC;  
  610.   
  611. while (argc > 0)  
  612. if (strcmp(*argv, "dev") == 0)  
  613. NEXT_ARG();  
  614. strncpy(d, *argv, sizeof(d)-1);         //eth0  
  615. else if (strcmp(*argv, "parent") == 0)  
  616. NEXT_ARG();  
  617. get_tc_classid(&handle, *argv)  
  618. req.t.tcm_parent = handle;          //0x00010001  
  619. else if (strcmp(*argv, "classid") == 0)  
  620. NEXT_ARG();  
  621. get_tc_classid(&handle, *argv)  
  622. req.t.tcm_handle = handle;          //0x00010014 (0x14 = 20)  
  623. else  
  624. //如果有/usr/lib/tc/htb.so动态库中则从中获取htb_qdisc_util符  
  625. //号结构,否则检测当前tc程序是否有htb_qdisc_util符号结构则  
  626. //从中获取,否则返回q 为空。  
  627. q = get_qdisc_kind(k);  
  628.   
  629. //添加KIND属性项,当前值为“htb”  
  630. addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);  
  631.   
  632. //使用当前扩展排队规则的parse_copt回调去解析后续命令字符,当前  
  633. //htb的回调为htb_parse_class_opt  
  634. q->parse_copt(q, argc, argv, &req.n)  
  635. htb_parse_class_opt  
  636. mtu = 1600;  
  637.   
  638. while (argc > 0)  
  639. if (strcmp(*argv, "rate") == 0)  
  640. NEXT_ARG();  
  641. get_rate64(&rate64, *argv)  //3 * 1000000 / 8  
  642. else if (matches(*argv, "burst") == 0)  
  643. NEXT_ARG();  
  644. //buffer = 15 * 1024  
  645. //cell_log = -1  
  646. get_size_and_cell(&buffer, &cell_log, *argv)  
  647. else if (strcmp(*argv, "ceil") == 0)  
  648. NEXT_ARG();  
  649. get_rate64(&ceil64, *argv); //6 * 1000000 / 8  
  650.   
  651. //超出32位最大值,则转换为全1?  
  652. opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;  
  653. opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;  
  654.   
  655. //如果cbuffer为设置,则取值为当前mtu值加上最大速率下  
  656. //每ticket单位的比特数  
  657. if (!cbuffer)  
  658. cbuffer = ceil64 / get_hz() + mtu;  
  659.   
  660. opt.ceil.overhead = overhead;       //0  
  661. opt.rate.overhead = overhead;       //0  
  662.   
  663. opt.ceil.mpu = mpu; //0  
  664. opt.rate.mpu = mpu; //0  
  665.   
  666. //计算最小速率表  
  667. //cell_log = -1  
  668. //mtu = 1600  
  669. //linklayer = LINKLAYER_ETHERNET  
  670. tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer)  
  671. //根据最大传输单元计算需要多少槽我理解是不可能每个  
  672. //字节都有准确速率,所以划定字节范围,从多少字节到  
  673. //多少字节的速率相同。  
  674. if (cell_log < 0)  
  675. cell_log = 0;  
  676. while ((mtu >> cell_log) > 255)  
  677. cell_log++;  
  678.   
  679. for (i=0; i<256; i++)  
  680. //校正当前槽位的字节大小。这个算法比较简单,当前  
  681. //链路类型为以太网,则包根据原值处理,不会影响包  
  682. //大小。Mpu为最小包大小,如果槽位字节小于mpu,  
  683. //则校正为mpu的值。  
  684. sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);  
  685.   
  686. //根据当前槽位字节大小,及用户配置的速率,计算当  
  687. //前槽位所需ticket时间  
  688. rtab[i] = tc_calc_xmittime(bps, sz);  
  689.   
  690. r->cell_align=-1;  
  691. r->cell_log=cell_log;  
  692. r->linklayer = (linklayer & TC_LINKLAYER_MASK);  
  693.   
  694. //计算单包正常速率下的传送峰值,这里通过包大小转换成所  
  695. //需要的ticket时间  
  696. opt.buffer = tc_calc_xmittime(rate64, buffer);  
  697.   
  698. //计算最大速率表,仅在发包速率大于最小速率后,租借模式下  
  699. //有效。  
  700. tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer)  
  701.   
  702. //计算单包在租借模速率作用下,传送的峰值。  
  703. opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);  
  704.   
  705. //添加扩展属性OPTIONS,标记后面都是htb的选项  
  706. addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);  
  707.   
  708. //超出32位,则添加扩展属性HTB_RATE64  
  709. //超出32位,则添加扩展属性HTB_CEIL64  
  710. if (rate64 >= (1ULL << 32))  
  711. addattr_l(n, 1124, TCA_HTB_RATE64, &rate64,   
  712. sizeof(rate64));  
  713. if (ceil64 >= (1ULL << 32))  
  714. addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64,   
  715. sizeof(ceil64));  
  716.   
  717. //添加扩展属性HTB_PARMS  
  718. addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));  
  719.   
  720. //添加扩展属性HTB_RTAB  
  721. addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);  
  722.   
  723. //添加扩展属性HTB_CTAB  
  724. addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);  
  725.   
  726. //根据接口名获取接口索引  
  727. if (d[0])  
  728. idx = ll_name_to_index(d)  
  729. req.t.tcm_ifindex = idx;  
  730.   
  731. //给内核发送该netlink消息  
  732. rtnl_talk(&rth, &req.n, 0, 0, NULL)  
  733.   
  734. rtnl_close(&rth);  
  735.   
  736. 2、内核层分析  
  737. 用户侧发出RTM_NEWTCLASS套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tclass,该函数是在pktsched_init中初始化的。  
  738.   
  739. tc_ctl_tclass  
  740. pid = tcm->tcm_parent            //父类ID          0x00010001  
  741. clid = tcm->tcm_handle;          //创建类ID     0x00010014  
  742. qid = TC_H_MAJ(clid);           //队列ID          0x00010000  
  743.   
  744. //eth0设备对象  
  745. dev = __dev_get_by_index(tcm->tcm_ifindex))  
  746.   
  747. if (pid != TC_H_ROOT)  
  748. pid = TC_H_MAKE(qid, pid);  //0x00010001  
  749.   
  750. //从当前设备的qdisc_list链表中找到已经创建的排队规则,当前q为之前创建的HTB  
  751. q = qdisc_lookup(dev, qid)  
  752.   
  753. //HTB的回调组为 cops = htb_class_ops  
  754. cops = q->ops->cl_ops;  
  755.   
  756. clid = TC_H_MAKE(qid, clid);        //0x00010014  
  757.   
  758. //get的回调函数为htb_get  
  759. cl = cops->get(q, clid);  
  760. htb_get  
  761. htb_class *cl = htb_find(classid, sch);  
  762. //使用类ID做HASH KEY在当前队列的HASH链表中查找已经创建的类  
  763. hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)  
  764. if (cl->classid == handle)  
  765. return cl;  
  766. return NULL;  
  767.   
  768. //如果找到则增加引用计数  
  769. if (cl)  
  770. cl->refcnt++;  
  771. return (unsigned long)cl;  
  772.   
  773. //当前环境还没有该类存在,进行新类的属性设置  
  774. new_cl = cl;  
  775. //当前回调为 htb_change_class  
  776. cops->change(q, clid, pid, tca, &new_cl);/  
  777. htb_change_class  
  778. //opt指向扩展属性基值索引,后续rtattr_parse_nested进行属性查找都从该  
  779. //基值索引之外进行查找。  
  780. rtattr *opt = tca[TCA_OPTIONS - 1];  
  781.   
  782. //查看枚举定义,TCA_HTB_RTAB值是最后一个,所以rtattr_parse_nested  
  783. //函数会把用户设置的所有扩展参数存储到临时变量tb中。  
  784. //enum  
  785. //{  
  786.     //  TCA_HTB_UNSPEC,  
  787.     //  TCA_HTB_PARMS,  
  788.     //  TCA_HTB_INIT,  
  789.     //  TCA_HTB_CTAB,  
  790. //  TCA_HTB_RTAB,  
  791.     //  __TCA_HTB_MAX,  
  792. //};  
  793. rtattr_parse_nested(tb, TCA_HTB_RTAB, opt)  
  794.   
  795. //parentid = 0x00010001  
  796. //以类ID为HASH KEY向当前队规则的hash链表中查找父类,之前已经创建。  
  797. parent = htb_find(parentid, sch);  
  798. htb_sched *q = qdisc_priv(sch);  
  799. hlist_for_each_entry(cl, p, q->hash + htb_hash(handle), hlist)  
  800. if (cl->classid == handle)  
  801. return cl;  
  802. return NULL;  
  803.   
  804. //取用户配置工具设置的HTB参数属性  
  805. hopt = RTA_DATA(tb[TCA_HTB_PARMS - 1]);  
  806.   
  807. //将最小速率表加入到全局qdisc_rtab_list链表中  
  808. rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB - 1]);  
  809.   
  810. //将最大速率表加入到全局qdisc_rtab_list链表中  
  811. ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB - 1]);  
  812.   
  813. //当前为新类进行创建  
  814. if (!cl)  
  815. cl = kzalloc(sizeof(*cl), GFP_KERNEL)  
  816. cl->refcnt = 1;  
  817. INIT_LIST_HEAD(&cl->sibling);  
  818. INIT_HLIST_NODE(&cl->hlist);  
  819. INIT_LIST_HEAD(&cl->children);  
  820. INIT_LIST_HEAD(&cl->un.leaf.drop_list);  
  821. RB_CLEAR_NODE(&cl->pq_node);  
  822.   
  823. for (prio = 0; prio < TC_HTB_NUMPRIO; prio++)  
  824. RB_CLEAR_NODE(&cl->node[prio]);  
  825.   
  826. //创建默认的pfifo排队规则,同时将该排队规则的父亲设置为当前类  
  827. //sch->parent = parentid;        0x00010014  
  828. new_q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops, classid);  
  829.   
  830. //当前存在父类,该父类还未有孩子节点  
  831. if (parent && !parent->level)  
  832. //保留当前父类中排队规则的队列长度,在HTB队列机制仅叶子类  
  833. //含有队列,所以当父类有孩子类后,父类中的队列就要删除。  
  834. qlen = parent->un.leaf.q->q.qlen;  
  835.   
  836. //调用父类中排队规则的reset回调,当前父类的排队规则为默认的  
  837. //pfifo,则回调为qdisc_reset_queue  
  838. qdisc_reset(parent->un.leaf.q);  
  839. ops = qdisc->ops;  
  840. ops->reset(qdisc);  
  841. //该函数的实现仅仅是删除队列中所有skb报文   
  842. qdisc_reset_queue  
  843.   
  844. //通知父排队规则,队列长度减少。  
  845. qdisc_tree_decrease_qlen(parent->un.leaf.q, qlen);  
  846. while ((parentid = sch->parent))  
  847. //当前排队规则为父类的pfifo规则,其中parent为父类  
  848. //的ID,TC_H_MAJ(parentid)对应的就是父类所在的排  
  849. //队规则,当前父类所在的排队规则即为最早创建的根  
  850. //排队规则HTB。  
  851. sch = __qdisc_lookup(sch->dev, TC_H_MAJ(parentid));  
  852.   
  853. cops = sch->ops->cl_ops;  
  854.   
  855. //htb_get  
  856. //在排队规则中获取指定ID的类对象,并增加引用计数  
  857. cl = cops->get(sch, parentid);  
  858. htb_get(sch, parentid)  
  859.   
  860. //htb_qlen_notify  
  861. cops->qlen_notify(sch, cl);  
  862. htb_qlen_notify(sch, cl)  
  863. //如果当前父类中排队规则的队列长度为0,则进行  
  864. //去激活  
  865. if (cl->un.leaf.q->q.qlen == 0)  
  866. htb_deactivate(qdisc_priv(sch), cl);  
  867. htb_deactivate_prios(q, cl);  
  868. mask = cl->prio_activity;  
  869.   
  870. while (cl->cmode ==   
  871. HTB_MAY_BORROW   
  872. && p && mask)  
  873. //当前还未有激活项,不处理  
  874.   
  875. if (cl->cmode == HTB_CAN_SEND   
  876. && mask)  
  877. //当前还未有激活项,不处理  
  878.   
  879. cl->prio_activity = 0;  
  880. list_del_init(&cl->un.leaf.drop_list);  
  881.   
  882. //htb_put  
  883. //减小类引用计数,如果没有其它对象引用该类,则调用  
  884. //htb_destroy_class进行类的销毁  
  885. cops->put(sch, cl);  
  886. htb_put(sch, cl);  
  887.   
  888. //子排队规则中队列长度减少,则父排队规则中的队列长度  
  889. //相应减少。  
  890. sch->q.qlen -= n;  
  891.   
  892. //销毁父类中排队规则,当前父类的排队规则为pfifo类型  
  893. qdisc_destroy(parent->un.leaf.q);  
  894. list_del(&qdisc->list);  
  895.   
  896. //qdisc_reset_queue  
  897. ops->reset(qdisc);  
  898. //删除队列中skb链表  
  899. qdisc_reset_queue  
  900.   
  901. //递减相关对象引用计数,并释放排队规则占用的分配内存块  
  902. module_put(ops->owner);  
  903. dev_put(qdisc->dev);  
  904. call_rcu(&qdisc->q_rcu, __qdisc_destroy);  
  905.   
  906. //父类中还有未激活项则去激活  
  907. if (parent->prio_activity)  
  908. htb_deactivate(q, parent);  
  909.   
  910. //根类等级深度为最大值  
  911. parent->level = TC_HTB_MAXDEPTH) - 1;    //7  
  912.   
  913. memset(&parent->un.inner, 0, sizeof(parent->un.inner));  
  914.   
  915. //当前新建的类的默认排队规则设置为pfifo  
  916. cl->un.leaf.q = new_q;  
  917.   
  918. cl->classid = classid;               //0x00010014  
  919. cl->parent = parent;                 //指向当前ID为1:1的父类  
  920.   
  921. cl->tokens = hopt->buffer;            //正常速率下单包峰值  
  922. cl->ctokens = hopt->cbuffer;          //借用速率下单包峰值  
  923. cl->mbuffer = PSCHED_JIFFIE2US(HZ * 60);  
  924. PSCHED_GET_TIME(cl->t_c);  
  925. cl->cmode = HTB_CAN_SEND;     //初始时可以发送报文  
  926.   
  927. //当新建的类加入到根排队规则的hash表中  
  928. hlist_add_head(&cl->hlist, q->hash + htb_hash(classid));  
  929.   
  930. //将新建的类关联到当前父类上  
  931. list_add_tail(&cl->sibling, &parent->children);  
  932.   
  933. if (!cl->level)  
  934. //在htb_init时,rate2quantum值默认为1  
  935. //设置quantum值,该值用于在进行带宽租借时的单位量  
  936. cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;  
  937. if (!hopt->quantum && cl->un.leaf.quantum < 1000)  
  938. cl->un.leaf.quantum = 1000;  
  939. if (!hopt->quantum && cl->un.leaf.quantum > 200000)  
  940. cl->un.leaf.quantum = 200000;  
  941.   
  942. cl->un.leaf.prio = hopt->prio;        //0  
  943.   
  944. cl->quantum = cl->un.leaf.quantum;  
  945. cl->prio = cl->un.leaf.prio;  
  946.   
  947. cl->buffer = hopt->buffer;                //正常速率下单包峰值  
  948. cl->cbuffer = hopt->cbuffer;              //借用速率下单包峰值  
  949.   
  950. //设置当前类的最小、最大速率表  
  951. cl->rate = rtab;  
  952. cl->ceil = ctab;  
  953.   
  954. //发送netlink消息,告知添加成功  
  955. tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);  
  956.   
  957. 五、创建一个U32分类器  
  958. 1、用户层分析  
  959. //初始化,获取每纳秒对应多少TICKET  
  960. tc_core_init();  
  961. fp = fopen("/proc/net/psched""r");  
  962. fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);  
  963. fclose(fp);  
  964.   
  965. if (clock_res == 1000000000)  
  966. t2us = us2t;  
  967.   
  968. clock_factor  = (double)clock_res / TIME_UNITS_PER_SEC;  
  969. tick_in_usec = (double)t2us / us2t * clock_factor;  
  970.   
  971. //创建一个ROUTE类型的netlink套接口  
  972. rtnl_open(&rth, 0)  
  973. rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);  
  974. rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);  
  975. setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))  
  976. setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))  
  977. rth->local.nl_family = AF_NETLINK;  
  978. rth->local.nl_groups = subscriptions;    //0  
  979. bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))  
  980. rth->seq = time(NULL);  
  981.   
  982. do_cmd(argc-1, argv+1);  
  983. if (matches(*argv, "filter") == 0)  
  984. do_filter(argc-1, argv+1);  
  985. tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE,   
  986. argc-1, argv+1);  
  987. req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));  
  988. req.n.nlmsg_flags = NLM_F_REQUEST|flags;  
  989. req.n.nlmsg_type = cmd;     //RTM_NEWTFILTER  
  990. req.t.tcm_family = AF_UNSPEC;  
  991.   
  992. while (argc > 0)  
  993. if (strcmp(*argv, "dev") == 0)  
  994. NEXT_ARG();  
  995. strncpy(d, *argv, sizeof(d)-1);     //eth0  
  996. else if (matches(*argv, "protocol") == 0)  
  997. NEXT_ARG();  
  998. ll_proto_a2n(&id, *argv)  
  999. protocol = id;                  //ETH_P_IP  
  1000. else if (strcmp(*argv, "parent") == 0)  
  1001. NEXT_ARG();  
  1002. get_tc_classid(&handle, *argv)  
  1003. req.t.tcm_parent = handle;      //0x00010000  
  1004. else if (matches(*argv, "priority") == 0)  
  1005. NEXT_ARG();  
  1006. get_u32(&prio, *argv, 0)            //1  
  1007. Else  
  1008. //如果有/usr/lib/tc/f_u32.so动态库中则从中获取u32_filter_util符  //号结构,否则检测当前tc程序是否有u32_filter_util符号结构则  
  1009. //从中获取,否则返回q 为空。  
  1010. q = get_filter_kind(k);  
  1011.   
  1012. //prio = 1,左移到前两个字节  
  1013. //protocol = ETH_P_IP,在低两个字节  
  1014. req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);  
  1015.   
  1016. //增加KIND扩展属性,值为u32  
  1017. addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);  
  1018.   
  1019. //使用u32扩展过滤器解析后续规则,当前回调为u32_parse_opt  
  1020. q->parse_fopt(q, fhandle, argc, argv, &req.n)  
  1021. u32_parse_opt  
  1022. //添加OPTIONS扩展属性,标识后续都属于u32的扩展项  
  1023. addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);  
  1024.   
  1025. while (argc > 0)  
  1026. if (matches(*argv, "match") == 0)  
  1027. NEXT_ARG();  
  1028. parse_selector(&argc, &argv, &sel.sel, n)  
  1029. else if (matches(*argv, "ip") == 0)  
  1030. NEXT_ARG();  
  1031. parse_ip(&argc, &argv, sel);  
  1032. else if (strcmp(*argv, "dport") == 0)  
  1033. NEXT_ARG();  
  1034.   
  1035. //sel->keys[hwm].val = key; htos(80)  
  1036. //sel->keys[hwm].mask = mask;全1  
  1037. //sel->keys[hwm].off = off; 20  
  1038. //sel->keys[hwm].offmask=offmask; 0  
  1039. //sel->nkeys++;              1  
  1040. parse_u16(&argc, &argv, sel, 22,0);  
  1041.   
  1042. sel_ok++;  
  1043. else if (strcmp(*argv, "flowid") == 0)  
  1044. NEXT_ARG();  
  1045. //0x0001000A(0xA=10)  
  1046. get_tc_classid(&handle, *argv)  
  1047.   
  1048. //添加U32_CLASSID扩展属性  
  1049. addattr_l(n, MAX_MSG, TCA_U32_CLASSID, &handle, 4);  
  1050. //添加终止标记TC_U32_TERMINAL,内核代码会根据  
  1051. //此标记判断当前处理完成。  
  1052. sel.sel.flags |= TC_U32_TERMINAL;  
  1053.   
  1054. if (sel_ok)  
  1055. //添加U32_SEL扩展属性  
  1056. addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel,  
  1057. sizeof(sel.sel)+sel.sel.nkeys*sizeof(struct tc_u32_key));  
  1058.   
  1059. //根据接口名获取接口索引  
  1060. if (d[0])  
  1061. idx = ll_name_to_index(d)  
  1062. req.t.tcm_ifindex = idx;  
  1063.   
  1064. //给内核发送该netlink消息  
  1065. rtnl_talk(&rth, &req.n, 0, 0, NULL)  
  1066.   
  1067. rtnl_close(&rth);  
  1068.   
  1069. 2、内核层分析  
  1070. 用户侧发出RTM_NEWTFILTER套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tfilter,该函数是在tc_filter_init中初始化的。  
  1071.   
  1072. protocol = TC_H_MIN(t->tcm_info);    //ETH_P_IP  
  1073. prio = TC_H_MAJ(t->tcm_info);        //1  
  1074. nprio = prio;  
  1075. parent = t->tcm_parent;              //0x00010000  
  1076.   
  1077. dev = __dev_get_by_index(t->tcm_ifindex) //eth0设备对象  
  1078.   
  1079. //从设备的qdisc_list列表中查找排队规则,之前HTB排队规则已经加入到链表中,所以  
  1080. //这里的q就等于HTB排队规则。  
  1081. q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))  
  1082.   
  1083. //前2个字节为排队规则索引,后2个字节为类索引,当前命令设置基于排队规则之上,后  
  1084. //2个字节设置为0,该条件不满足,此时cl变量取值为初始的0。  
  1085. if (TC_H_MIN(parent))  
  1086. //不走此流程  
  1087.   
  1088. //HTB排队规则中对应的回调为htb_find_tcf  
  1089. //获取该排队规则中的过滤链表。  
  1090. chain = cops->tcf_chain(q, cl);  
  1091. htb_find_tcf  
  1092. htb_sched *q = qdisc_priv(sch);  
  1093. htb_class *cl = (struct htb_class *)arg;  
  1094. //当前cl为0,则取的是排队规则中的过滤链表  
  1095. tcf_proto **fl = cl ? &cl->filter_list : &q->filter_list;  
  1096. return fl;  
  1097.   
  1098. //查找待插入的位置,优先级的值越小表示越高。  
  1099. for (back = chain; (tp=*back) != NULL; back = &tp->next)  
  1100. if (tp->prio >= prio)  
  1101. if (tp->prio == prio)  
  1102. if (!nprio || (tp->protocol != protocol && protocol))  
  1103. goto errout;  
  1104. else  
  1105. tp = NULL;  
  1106. break;  
  1107.   
  1108. //新建过滤协议项  
  1109. if (tp == NULL)  
  1110. //从tcf_proto_base链表中查找u32分类器,当前tp_ops为cls_u32_ops  
  1111. tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);  
  1112.   
  1113. tp->ops = tp_ops;            //cls_u32_ops  
  1114. tp->protocol = protocol;     //ETH_P_IP  
  1115. tp->prio = nprio;            //1  
  1116. tp->q = q;               //指向根HTB类型排队规则  
  1117. tp->classify = tp_ops->classify;  //u32_classify  
  1118. tp->classid = parent;            //0x00010000  
  1119.   
  1120. //当前u32类的初始化回调为fw_ini。  
  1121. tp_ops->init(tp)  
  1122. u32_init  
  1123. //查找u32_list链表中是否存在相同的排队规则对象的过滤通用项  
  1124. for (tp_c = u32_list; tp_c; tp_c = tp_c->next)  
  1125. if (tp_c->q == tp->q)  
  1126. break;  
  1127.   
  1128. root_ht = kzalloc(sizeof(*root_ht), GFP_KERNEL);  
  1129. root_ht->divisor = 0;  
  1130. root_ht->refcnt++;  
  1131. //当前在u32_list链表中还未存在相同的排队规则对象的过滤通用项,所以  
  1132. //tp_c不为真。  
  1133. root_ht->handle = tp_c ? gen_new_htid(tp_c) : 0x80000000;  
  1134. root_ht->prio = tp->prio;         //1  
  1135.   
  1136. //当前tp_c为空,创建一个并加入到u32_list链表中。  
  1137. if (tp_c == NULL)  
  1138. tp_c = kzalloc(sizeof(*tp_c), GFP_KERNEL);  
  1139. tp_c->q = tp->q;  
  1140. tp_c->next = u32_list;  
  1141. u32_list = tp_c;  
  1142.   
  1143. //将新建的root_ht与tc_c互相关联  
  1144. tp_c->refcnt++;  
  1145. root_ht->next = tp_c->hlist;  
  1146. tp_c->hlist = root_ht;  
  1147. root_ht->tp_c = tp_c;  
  1148.   
  1149. //当前过滤对象的root指向新分配HASH节点对象,同时data指  
  1150. //向新分配的过滤通用项  
  1151. tp->root = root_ht;  
  1152. tp->data = tp_c;  
  1153.   
  1154. //将当前过滤器加入到当前排队规则的过滤链表中  
  1155. tp->next = *back;  
  1156. *back = tp;  
  1157.   
  1158. //u32分类器的get回调为u32_get,当前在命令行里没有handle值,所以这里返回为0  
  1159. fh = tp->ops->get(tp, t->tcm_handle);  
  1160.   
  1161. //u32分类器的change回调为u32_change  
  1162. tp->ops->change(tp, cl, t->tcm_handle, tca, &fh);  
  1163. u32_change  
  1164. //把用户配置的后续所有扩展属性提取到临时变量opt中  
  1165. rtattr_parse_nested(tb, TCA_U32_MAX, opt)  
  1166.   
  1167. if ((n = (struct tc_u_knode*)*arg) != NULL)  
  1168. //当前在调用u32_get后,还不存在对应的hash节点项,所以不走此流程。  
  1169.   
  1170. //如果命令接口设置了divisor,则表示创建一个新的hash表对象,该hash表含有  
  1171. //divisor个数量的列表项,主要用于规则数量很大时,加快匹配速度。  
  1172. if (tb[TCA_U32_DIVISOR-1])  
  1173. unsigned divisor = *(unsigned*)RTA_DATA(tb[TCA_U32_DIVISOR-1]);  
  1174.   
  1175. ht = kzalloc(sizeof(*ht) + divisor*sizeof(void*), GFP_KERNEL);  
  1176. ht->tp_c = tp_c;     //当前hash表与过滤通用项关联  
  1177. ht->refcnt = 0;  
  1178. ht->divisor = divisor;  
  1179. ht->handle = handle;  
  1180. ht->prio = tp->prio;  
  1181. //通用项中的hlist指向第一hash表,这里将新建的hash插入到通用项的hlist  
  1182. //链头。  
  1183. ht->next = tp_c->hlist;  
  1184. tp_c->hlist = ht;  
  1185.   
  1186. *arg = (unsigned long)ht;  
  1187. return 0;  
  1188.   
  1189. //当用户在命令接口指定了hash表的ID,则查找对应的hash表,否则如果在命令  
  1190. //接口没有指定hash表ID,则使用过滤协议项中root指向的第一个hash链表。  
  1191. if (tb[TCA_U32_HASH-1])  
  1192. htid = *(unsigned*)RTA_DATA(tb[TCA_U32_HASH-1]);  
  1193.   
  1194. if (TC_U32_HTID(htid) == TC_U32_ROOT)  
  1195. ht = tp->root;  
  1196. htid = ht->handle;  
  1197. else  
  1198. //tp->data指向过滤通用项,这里使用过滤通用项中hlist链表来进行hash  
  1199. //表查找。  
  1200. ht = u32_lookup_ht(tp->data, TC_U32_HTID(htid));  
  1201. else  
  1202. ht = tp->root;  
  1203. htid = ht->handle;   //0x80000000,该值在上面u32_init中初始化  
  1204.   
  1205. //当前命令行未设置handle,使用gen_new_kid生成hash节点id  
  1206. if (handle)  
  1207. //不走此流程  
  1208. else  
  1209. handle = gen_new_kid(ht, htid);  
  1210. unsigned i = 0x7FF;  
  1211.   
  1212. //handle = 0x80000000  
  1213. //TC_U32_HASH(handle) = 0  
  1214. //当前ht->ht[0]还未有值,所有这里返回值为 0x80000800  
  1215. for (n=ht->ht[TC_U32_HASH(handle)]; n; n = n->next)  
  1216. if (i < TC_U32_NODE(n->handle))  
  1217. i = TC_U32_NODE(n->handle);  
  1218. i++;  
  1219. return handle|(i>0xFFF ? 0xFFF : i);  
  1220.   
  1221. //指向选择项属性值  
  1222. s = RTA_DATA(tb[TCA_U32_SEL-1]);  
  1223.   
  1224. //当前用户配置接口在当前规则中仅设置了一个匹配项,则nkeys为1  
  1225. //这里分配内存空间为一个struct tc_u_knode大小,加上一个struct tc_u32_key大小  
  1226. n = kzalloc(sizeof(*n) + s->nkeys*sizeof(struct tc_u32_key), GFP_KERNEL);  
  1227.   
  1228. //把用户配置设置匹配规则复制到当前新的hash关键节点的sel中  
  1229. memcpy(&n->sel, s, sizeof(*s) + s->nkeys*sizeof(struct tc_u32_key));  
  1230.   
  1231. //当前hash关键节点的ht_up指向当前使用的hash表  
  1232. n->ht_up = ht;  
  1233. n->handle = handle;  // 0x80000800  
  1234.   
  1235. //当前s->hmask为0,所以i最后为0  
  1236. u8 i = 0;  
  1237. u32 mask = s->hmask  
  1238. if (mask)  
  1239. ......  
  1240. n->fshift = i;  
  1241.   
  1242. //过滤节点参数设置  
  1243. u32_set_parms(tp, base, ht, n, tb, tca[TCA_RATE-1]);  
  1244. //当前命令行没有输入action或police,所以无扩展属性  
  1245. tcf_exts_validate(tp, tb, est, &e, &u32_ext_map);  
  1246.   
  1247. //如果用户接口输入了link,则进行链接处理,这里链接是指在当前过滤项  
  1248. //匹配成功后,可以跳到其它hash表项中去处理,如果在其它hash表项中  
  1249. //分类成功,则直接返回成功,否则在其它hash表项中分类失败,还可以  
  1250. //回来原来的hash表项继续处理。  
  1251. if (tb[TCA_U32_LINK-1])  
  1252. u32 handle = *(u32*)RTA_DATA(tb[TCA_U32_LINK-1]);  
  1253.   
  1254. //根据用户接口设置的link索引,查找当前通用项中hlist对应的hash  
  1255. //表是否含有对应id的hash项。  
  1256. ht_down = u32_lookup_ht(ht->tp_c, handle);  
  1257.   
  1258. //没找到返回错误,找到则增加引用计数  
  1259. if (ht_down == NULL)  
  1260. goto errout;  
  1261. ht_down->refcnt++;  
  1262.   
  1263. //将hash关键节点的ht_down指向希望链接的hash表项,在进行分类处  
  1264. //理里,如果当前hash关键节点匹配成功,会检测如果存在ht_down,则  
  1265. //使用该hash表项的各hash关键节点项继续匹配处理。  
  1266. ht_down = xchg(&n->ht_down, ht_down);  
  1267.   
  1268. //如果之前ht_down已经关联了其它hash表项,当前已经不使用,则  
  1269. //递减引用计数。  
  1270. if (ht_down)  
  1271. ht_down->refcnt--;  
  1272.   
  1273. if (tb[TCA_U32_CLASSID-1])  
  1274. //0x0001000A  
  1275. n->res.classid = *(u32*)RTA_DATA(tb[TCA_U32_CLASSID-1]);  
  1276.   
  1277. tcf_bind_filter(tp, &n->res, base);  
  1278. //当前回调为htb_bind_filter  
  1279. //查找绑定该hash关键节点对象的类  
  1280. cl = tp->q->ops->cl_ops->bind_tcf(tp->q, base, r->classid);  
  1281. htb_bind_filter  
  1282. htb_sched *q = qdisc_priv(sch);  
  1283.   
  1284. //当前classid为0x000100A  
  1285. htb_class *cl = htb_find(classid, sch);  
  1286.   
  1287. //找到匹配的类,则类的过滤计数递增  
  1288. if (cl)  
  1289. cl->filter_cnt++;  
  1290. else  
  1291. q->filter_cnt++;  
  1292. return (unsigned long)cl;  
  1293.   
  1294. //将找到的类对象关联到hash关键节点对象中,同时返回hash关键  
  1295. //节点对象中原有的关联类,当前没有老的关联类,所以old_cl为空  
  1296. cl = cls_set_class(tp, &r->class, cl);  
  1297. old_cl = __cls_set_class(clp, cl);  
  1298. old_cl = *clp;  
  1299. *clp = cl;  
  1300. return old_cl;  
  1301. return old_cl;  
  1302.   
  1303. //当前hash关键节点对象是新建的,没有老的关联类,不执行此流程。  
  1304. if (cl)  
  1305. tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);  
  1306.   
  1307. //扩展参数设置,当前命令行没有输入action或police,所以无扩展属性。  
  1308. tcf_exts_change(tp, &n->exts, &e);  
  1309.   
  1310. //把新建的hash关键节点加入到hash对象的ht链表中  
  1311. for (ins = &ht->ht[TC_U32_HASH(handle)]; *ins; ins = &(*ins)->next)  
  1312. if (TC_U32_NODE(handle) < TC_U32_NODE((*ins)->handle))  
  1313. break;  
  1314.   
  1315. n->next = *ins;  
  1316. wmb();  
  1317. *ins = n;  
  1318.   
  1319. //告知添加成功  
  1320. tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);  
  1321.   
  1322. 六、发送报文  
  1323. 当上层发出报文,调用dev_queue_xmit,这里仅分析和出口排队规则相关的代码,其它详细  
  1324. 的代码分析可以参见《接口设备发包》  
  1325. dev_queue_xmit  
  1326. ......  
  1327.   
  1328. //如果当前使用出口排队规则  
  1329. q = rcu_dereference(dev->qdisc);  
  1330. if (q->enqueue)  
  1331. spin_lock(&dev->queue_lock);  
  1332. q = dev->qdisc;  
  1333.   
  1334. if (q->enqueue)  
  1335. //调用当前排队规则的入队回调,当前HTB类型排队规则回调为  
  1336. //htb_enqueue  
  1337. rc = q->enqueue(skb, q);  
  1338. htb_enqueue  
  1339. htb_sched *q = qdisc_priv(sch);  
  1340.   
  1341. //分类处理  
  1342. htb_class *cl = htb_classify(skb, sch, &ret);  
  1343. //直报文的优先级字段与当前队列的句柄相同,则返回直接处理  
  1344. if (skb->priority == sch->handle)  
  1345. return HTB_DIRECT;  
  1346.   
  1347. //使用报文的优先级字段做为类ID,在htb_find函数中,从排队  
  1348. //规则的hash表中查找指定类ID的类对象,找到指定类对象后,  
  1349. //根据cl->level判断当前类是否为叶子(0为叶子),如果是叶子  
  1350. //类则返回。  
  1351. if ((cl = htb_find(skb->priority, sch)) != NULL && cl->level == 0)  
  1352. return cl;  
  1353.   
  1354.   
  1355. *qerr = NET_XMIT_BYPASS;  
  1356.   
  1357. tcf = q->filter_list;  
  1358.   
  1359. //使用过滤对象进行分类,tc_classify函数在下面单独分析。  
  1360. while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0)  
  1361. switch (result)  
  1362. //这三种处理结果都会返回空类,在htb_enqueue对返回类  
  1363. //为空的情况都会将报文丢弃。  
  1364. case TC_ACT_QUEUED:  
  1365. case TC_ACT_STOLEN:  
  1366. *qerr = NET_XMIT_SUCCESS;  
  1367. case TC_ACT_SHOT:  
  1368. return NULL;  
  1369.   
  1370. //如果返回结果的类不存在,则继续尝试  
  1371. if ((cl = (void *)res.class) == NULL)  
  1372. //如果返回结果就classid为排队规则句柄,也就是说之  
  1373. //前用户在命令接口配置过滤规则时,最后匹配的流ID  
  1374. //为排队规则句柄,则返回直接处理。  
  1375. if (res.classid == sch->handle)  
  1376. return HTB_DIRECT;  
  1377.   
  1378. //此时根据用户在命令接口配置过滤规则时指定的  
  1379. //流ID查找当前排队规则是否存在该类,如果不存  
  1380. //在,则直接跳出当前while循环,使用之前创建  
  1381. //HTB排队规则时设置的默认的类  
  1382. if ((cl = htb_find(res.classid, sch)) == NULL)  
  1383. break;  
  1384.   
  1385. //如果找到了类,并且该类是叶子类,则返回,否则继续使  
  1386. //用下一个过滤器进行查找。  
  1387. if (!cl->level)  
  1388. return cl;  
  1389.   
  1390. //使用非叶子类中的过滤器进行分类  
  1391. tcf = cl->filter_list;  
  1392.   
  1393. //到达这里,表明所有过滤器都已经遍历完了,但还没有找到  
  1394. //对应的叶子类。这里则使用之前创建HTB排队规则时设置的  
  1395. //默认类ID进行查找。  
  1396. cl = htb_find(TC_H_MAKE(TC_H_MAJ(sch->handle), q->defcls),   
  1397. sch);  
  1398.   
  1399. //如果使用默认类ID,还没找到对应的类,或者找到的类是非  
  1400. //叶子类型的,则返回直接处理。  
  1401. if (!cl || cl->level)  
  1402. return HTB_DIRECT;  
  1403.   
  1404. //返回找到的默认类。  
  1405. return cl;  
  1406.   
  1407. //当前返回结果为直接处理  
  1408. if (cl == HTB_DIRECT)  
  1409. //如果当前直接发送队列还未达到直接发送限额,则将报文放入  
  1410. //直接发送队列中,否则将报文丢弃处理。  
  1411. if (q->direct_queue.qlen < q->direct_qlen)  
  1412. __skb_queue_tail(&q->direct_queue, skb);  
  1413. q->direct_pkts++;  
  1414. else  
  1415. kfree_skb(skb);  
  1416. sch->qstats.drops++;  
  1417. return NET_XMIT_DROP;  
  1418. //使用叶子类中的排队规则的enqueue回调,将报文压入到该排队规  
  1419. //则中,如果压入失败,则进行丢包统计。  
  1420. else if (cl->un.leaf.q->enqueue(skb, cl->un.leaf.q) !=net_xmit_success)  
  1421. sch->qstats.drops++;  
  1422. cl->qstats.drops++;  
  1423. return NET_XMIT_DROP;  
  1424. else   
  1425. //包压入指定叶子类下的排队规则后,统计成功项,同时激活对  
  1426. //应叶子类。  
  1427. cl->bstats.packets++;  
  1428. cl->bstats.bytes += skb->len;  
  1429.   
  1430. //激活叶子类,当前HTB的机制在对激活的类做了划分。首先  
  1431. //分了8层,叶子类为第0层,根类为第7层,中间类在父类的  
  1432. //层数上减1,在进行出队处理时从第0层开始优先进行处理。  
  1433. //同时每层又分为8个优先级,0为第高优先级,当被激活的类  
  1434. //层次相同时,则按优先级最高的优先进行处理。当被激活的类  
  1435. //层次也相同,优先级也相同,则被根据类ID为对比值放入一  
  1436. //棵红黑树中,类ID越小优先级越高。激活类的数据结构大概  
  1437. //如下图所示:  
  1438.   
  1439. <img src="http://img.blog.csdn.net/20141214040619778?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />  
  1440. htb_activate(q, cl);  
  1441. //设置当前类中被激活的优先级位图,这里cl->un.leaf.prio  
  1442. //是在类被用户接口创建时设置的,如果在用户接口未输入  
  1443. //prio则默认为0。  
  1444. cl->prio_activity = 1 << (cl->un.leaf.aprio = cl->un.leaf.prio);  
  1445.   
  1446. //根据类中层次及优先级位图激活类  
  1447. htb_activate_prios(q, cl);  
  1448. p = cl->parent;      //当前类的父类  
  1449. mask = cl->prio_activity;    //当前类的优先级位图  
  1450.   
  1451. //当前类如果已经进到了租借模式,同时该类存在父类  
  1452. //则进行处理。这里会对父类进行激活,同时将当前类  
  1453. //挂接到父类的feed供给树下。  
  1454. while (cl->cmode == HTB_MAY_BORROW && p &&   
  1455. mask)  
  1456. m = mask;  
  1457.   
  1458. while (m)  
  1459. //将优先级位图取反,之后使用ffz查找第一  
  1460. //为0的位为表示优先级索引。  
  1461. int prio = ffz(~m);  
  1462.   
  1463. //当前位处理后就去除。  
  1464. m &= ~(1 << prio);  
  1465.   
  1466. //当前父类如果已经在供给,则表明之前已经  
  1467. //激活,则在mask中去除当前优先级位,否  
  1468. //则下面会将cl变量指向父类,并对父类进行  
  1469. //激活。  
  1470. if (p->un.inner.feed[prio].rb_node)  
  1471. mask &= ~(1 << prio);  
  1472.   
  1473. //这里将当前子类加入到父类的供给树中  
  1474. //(un.inner.feed),表明当前父类在给哪些  
  1475. //子类供给。  
  1476. htb_add_to_id_tree(p->un.inner.feed + prio, cl,   
  1477. prio);  
  1478.   
  1479. //父类可能租借给多个子类使用,这里在父类中保  
  1480. //存子类的优先级。开始对父类准备激活处理。  
  1481. p->prio_activity |= mask;  
  1482. cl = p;  
  1483. p = cl->parent;  
  1484.   
  1485. //如果当前类为可发送模式,则在HTB排队规则中激  
  1486. //活该优先级类。  
  1487. if (cl->cmode == HTB_CAN_SEND && mask)  
  1488. htb_add_class_to_row(q, cl, mask);  
  1489. //记载当前层次哪些优先级被激活  
  1490. q->row_mask[cl->level] |= mask;  
  1491.   
  1492. //遍历所有待处理的优先级位,将当前类插入  
  1493. //到排队规则row指定位置的树中。插入位置  
  1494. //的实现可以参考上图激活类的数据结构来  
  1495. //加深理解。  
  1496. while (mask)  
  1497. int prio = ffz(~mask);  
  1498. mask &= ~(1 << prio);  
  1499. htb_add_to_id_tree(q->row[cl->level] +   
  1500. prio, cl, prio);  
  1501.   
  1502. //将当前叶子类插入到HTB排队规则的drops对应优先级的  
  1503. //列表中。  
  1504. list_add_tail(&cl->un.leaf.drop_list,q->drops +   
  1505. cl->un.leaf.aprio);  
  1506.   
  1507. //更新统计,返回分类成功。  
  1508. sch->q.qlen++;  
  1509. sch->bstats.packets++;  
  1510. sch->bstats.bytes += skb->len;  
  1511. return NET_XMIT_SUCCESS;  
  1512.   
  1513. //队列处理  
  1514. qdisc_run(dev);  
  1515. //如果队列发送没有关闭,并且队列调度还没有运行,则运行队列调度  
  1516. if (!netif_queue_stopped(dev)   
  1517. &&!test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))  
  1518. __qdisc_run(dev);  
  1519. //如果队列为noop_qdisc类型,则表明接口还未启动,则直接退  
  1520. //出队列调度  
  1521. if (unlikely(dev->qdisc == &noop_qdisc))  
  1522. goto out;  
  1523.   
  1524. //当还有待处理的数据,并且发送队列没有关闭,则循环从队列  
  1525. //中取出数据,使用网卡驱动进行发送数据。该函数具体实现不  
  1526. //详细列出。之前在《接口设备发包》中已经分析过,可以参考  
  1527. //那节的分析。这里主要关注,从队列中取出数据是使用的队列  
  1528. //的dequeue回调,当前HTB类型的排队规则回调为  
  1529. //htb_dequeue,htb_dequeue在下面单独分析。  
  1530. while (qdisc_restart(dev) < 0 && !netif_queue_stopped(dev))  
  1531. ;  
  1532.   
  1533. out:  
  1534. //去除正在进行队列调度标记  
  1535. clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);  
  1536.   
  1537. spin_unlock(&dev->queue_lock);  
  1538.   
  1539. rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;  
  1540. goto out;  
  1541.   
  1542. spin_unlock(&dev->queue_lock);  
  1543.   
  1544. ---------------------------------------------------------------------------------------------------------------------  
  1545. //使用过滤器进行分类  
  1546. tc_classify  
  1547. protocol = skb->protocol;  
  1548.   
  1549. //遍历所有过滤器  
  1550. for ( ; tp; tp = tp->next)  
  1551. //首先检测当前报文的协议与过滤器中的协议匹配,或者过滤器匹配任何协议类  
  1552. //型,则进一步使用当前过滤器的classify回调进行分类处理,当前u32的回调  
  1553. //函数为u32_classify,u32_classify在下面单独分析。  
  1554. if ((tp->protocol == protocol ||tp->protocol == __constant_htons(ETH_P_ALL)) &&  
  1555. (err = tp->classify(skb, tp, res)) >= 0)  
  1556. //返回处理结果  
  1557. return err;  
  1558.   
  1559. //返回-1,表明分类失败。  
  1560. return -1;  
  1561.   
  1562. ---------------------------------------------------------------------------------------------------------------------  
  1563. 使用u32分类器进行分类处理  
  1564. u32_classify  
  1565. //过滤协议对象的root,即首hash链表对象  
  1566. tc_u_hnode *ht = (struct tc_u_hnode*)tp->root;  
  1567. u8 *ptr = skb->nh.raw;       //报文的IP头位置  
  1568. int sel = 0;  
  1569.   
  1570. next_ht:  
  1571.   
  1572. n = ht->ht[sel];  
  1573.   
  1574. next_knode:  
  1575.   
  1576. //判断当前hash关键节点是否存在  
  1577. if (n)  
  1578. struct tc_u32_key *key = n->sel.keys;    //hash关键节点对象的匹配项  
  1579.   
  1580. //遍历当前hash关键节点的所有匹配项,如果有任何一项匹配失败,则使用  
  1581. //goto进入下一个hash关键节点进行匹配。这里匹配项中的off就是对应项  
  1582. //(比如dport)在报文IP文件位置的偏移。  
  1583. for (i = n->sel.nkeys; i>0; i--, key++)  
  1584. if ((*(u32*)(ptr+key->off+(off2&key->offmask))^key->val)&key->mask)  
  1585. n = n->next;  
  1586. goto next_knode;  
  1587.   
  1588. //ht_down和命令行中link配置相关,用于多层嵌套,可以实现跳转到其它hash链  
  1589. //节点进行查找,当前测试实例没有配置嵌套,这里ht_down 为NULL。  
  1590. if (n->ht_down == NULL)  
  1591. check_terminal:  
  1592. //在用户侧命令接口已经对当前过滤选择项设置了U32_TERMINAL  
  1593. if (n->sel.flags&TC_U32_TERMINAL)  
  1594. *res = n->res;       //返回该过滤选择项的结果,这里含有绑定的类  
  1595.   
  1596. //进行扩展属性处理,处理失败尝试下一个关键匹配点,当前命令接口  
  1597. //没有设置相关扩展项,所以这里返回为0  
  1598. r = tcf_exts_exec(skb, &n->exts, res);  
  1599. if (r < 0)  
  1600. n = n->next;  
  1601. goto next_knode;  
  1602.   
  1603. //当前匹配完成,返回结果。  
  1604. return r;  
  1605.   
  1606. //如果hash关键节点匹配项匹配完成,但不含U32_TERMINAL处理结束的标  
  1607. //记,则尝试下一个hash关键节点。  
  1608. n = n->next;  
  1609. goto next_knode;  
  1610.   
  1611. //n->ht_down不为空,表明有嵌套匹配处理  
  1612.   
  1613. //先将当前hash关键节点及匹配指针压入临时变量stack栈中  
  1614. stack[sdepth].knode = n;  
  1615. stack[sdepth].ptr = ptr;  
  1616. sdepth++;  
  1617.   
  1618. //取出待跳转的hash链对象  
  1619. ht = n->ht_down;  
  1620.   
  1621. sel = 0;  
  1622. //如果当前hash链对象含有divisor值,表明该hash链对象是之前通过命令接口  
  1623. //使用divisor关键字段创建的含有多个列表项的hash链对象,则这里根据之  
  1624. //前命令接口设置的散列因子相关的参数(比如根据源IP地址最后1个字节进行散  
  1625. //列因子处理)进行计算,来确定当前的报文应该在当前hash链对象的哪个列表  
  1626. //项中进行处理。  
  1627. if (ht->divisor)  
  1628. sel = ht->divisor&u32_hash_fold(*(u32*)(ptr+n->sel.hoff), &n->sel,n->fshift);  
  1629.   
  1630. //如果当前hash关键节点的选择项中不含有任何偏移属性,则直接跳转到下一个  
  1631. //hash链节点中进行查找。  
  1632. if (!(n->sel.flags&(TC_U32_VAROFFSET|TC_U32_OFFSET|TC_U32_EAT)))  
  1633. goto next_ht;  
  1634.   
  1635.   
  1636. //走到此流程,表明当前hash关键节点中含有相关的偏移属性,则根据用户接口  
  1637. //配置进行对应的偏移处理,如果处理完后报文合法,则跳转到下一个hash链节  
  1638. //点中进行查找。  
  1639. if (n->sel.flags&(TC_U32_OFFSET|TC_U32_VAROFFSET))  
  1640. off2 = n->sel.off + 3;  
  1641. if (n->sel.flags&TC_U32_VAROFFSET)  
  1642. off2 += ntohs(n->sel.offmask & *(u16*)(ptr+n->sel.offoff)) >>n->sel.offshift;  
  1643. off2 &= ~3;  
  1644. if (n->sel.flags&TC_U32_EAT)  
  1645. ptr += off2;  
  1646. off2 = 0;  
  1647. if (ptr < skb->tail)  
  1648. goto next_ht;  
  1649.   
  1650. //用户接口含有link关键字时,则进行嵌套处理,每进行一次嵌套则将前一次处理结果  
  1651. //存储到临时变量stack中,当最后的嵌套处理没有匹配成功时,会走到此流程,如果  
  1652. //当前确定是嵌套处理,则取出前一次的hash链对象继续进行处理。  
  1653. if (sdepth--)  
  1654. n = stack[sdepth].knode;  
  1655. ht = n->ht_up;  
  1656. ptr = stack[sdepth].ptr;  
  1657. goto check_terminal;  
  1658.   
  1659. //分类失败  
  1660. return -1;  
  1661.   
  1662. ---------------------------------------------------------------------------------------------------------------------  
  1663. //HTB数据包出队处理  
  1664. htb_dequeue  
  1665. q->jiffies = jiffies;            //记载当前滴答  
  1666.   
  1667. //如果当前直接发送队列含有报文,则优先进行处理,直接将报文取出。  
  1668. skb = __skb_dequeue(&q->direct_queue);  
  1669. if (skb != NULL)  
  1670. sch->flags &= ~TCQ_F_THROTTLED;  
  1671. sch->q.qlen--;  
  1672. return skb;  
  1673.   
  1674. //队列无数据则返回为NULL  
  1675. if (!sch->q.qlen)  
  1676. goto fin;  
  1677.   
  1678. //获取当前系统时间  
  1679. PSCHED_GET_TIME(q->now);  
  1680.   
  1681. min_delay = LONG_MAX;  
  1682.   
  1683. q->nwc_hit = 0;  
  1684.   
  1685. //从第0层开始遍历处理,第0层为叶子类层  
  1686. for (level = 0; level < TC_HTB_MAXDEPTH; level++)  
  1687. //当前流逝时间已经超过最近事件的缓冲时间  
  1688. if (time_after_eq(q->jiffies, q->near_ev_cache[level]))  
  1689. delay = htb_do_events(q, level);  
  1690. //最多处理500个被阻塞的报文  
  1691. for (i = 0; i < 500; i++)  
  1692. //取当前层中第1个被阻塞的树节点  
  1693. struct rb_node *p = rb_first(&q->wait_pq[level]);  
  1694.   
  1695. //如果当前没有阻塞的报文,则返回0延时。  
  1696. if (!p)  
  1697. return 0;  
  1698.   
  1699. //获取包含该树节点的HTB类对象  
  1700. cl = rb_entry(p, struct htb_class, pq_node);  
  1701.   
  1702. //如果当前类的阻塞时间还未到,则继续阻塞,返回还有多长时间会  
  1703. //到的时间差。  
  1704. if (time_after(cl->pq_key, q->jiffies))  
  1705. return cl->pq_key - q->jiffies;  
  1706.   
  1707. //当前类的阻塞时间已到。  
  1708.   
  1709. //将树节点从阻塞树中删除。  
  1710. htb_safe_rb_erase(p, q->wait_pq + level);  
  1711.   
  1712. //计算当前类最后一次处理到当前的时间差,该时间差不能超过  
  1713. //mbuffer值(在创建类时该值为1分钟)。  
  1714. diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32) cl->mbuffer);  
  1715.   
  1716. //进行类的模式改变  
  1717. htb_change_class_mode(q, cl, &diff);  
  1718. new_mode = htb_class_mode(cl, diff);  
  1719. //这里ctokens是租借模式下令牌数,经过一段时间后令牌  
  1720. //数就会进行补充。待补充后仍然小于低水位线,则状态变  
  1721. //为HTB_CANT_SEND(不能进行发送),这里htb_lowater  
  1722. //低水位线根据当前类的模式不同而不同,如果当前类模式  
  1723. //为HTB_CANT_SEND,则低水位线的值为-cl->cbuffer,也  
  1724. //就是租借模式下单包最大可传达数据所需要的ticket,其它  
  1725. //模块下低水位线的值为0。  
  1726. if ((toks = (cl->ctokens + *diff)) < htb_lowater(cl))  
  1727. *diff = -toks;  //返回还需要多少令牌  
  1728. return HTB_CANT_SEND;  
  1729.   
  1730. //这里tokens是非租借模式下令牌数,经过一段时间补充后,  
  1731. //如果高于高水位线,则状态变为HTB_CAN_SEND  
  1732. //(可以发送)  
  1733. if ((toks = (cl->tokens + *diff)) >= htb_hiwater(cl))  
  1734. return HTB_CAN_SEND;  
  1735.   
  1736. //状态变为可租借,返回还需要多少令牌  
  1737. *diff = -toks;  
  1738. return HTB_MAY_BORROW;  
  1739.   
  1740. //当前模式相同则直接返回。  
  1741. if (new_mode == cl->cmode)  
  1742. reutrn;  
  1743.   
  1744. //如果当前类有激活,则根据新、老模式进行类的激活、去激活  
  1745. //处理(之前为非不可发送模式,则将当前类去激活,该类后  
  1746. //续暂时不能发包,当前新的模式为可发送模式,则将当前类  
  1747. //激活,该类后续可以继续发包,当前新的模式为租借模式,则  
  1748. //激活父类,并将当前类挂接到父类的feed供给树下)。  
  1749. //否则当前类没有激活,则仅修改当前模式。  
  1750. if (cl->prio_activity)  
  1751. if (cl->cmode != HTB_CANT_SEND)  
  1752. htb_deactivate_prios(q, cl);  
  1753. cl->cmode = new_mode;  
  1754. if (new_mode != HTB_CANT_SEND)  
  1755. htb_activate_prios(q, cl);  
  1756. else  
  1757. cl->cmode = new_mode;  
  1758.   
  1759. //如果当前模式不可发送,则将类插入到q->wait_pq[level]等待树中。  
  1760. //同时更新类的cl->pq_key等待关键字的值为当前jitter加上diff,其  
  1761. //中diff是在上面htb_change_class_mode中计算得到。  
  1762. if (cl->cmode != HTB_CAN_SEND)  
  1763. htb_add_to_wait_tree(q, cl, diff);  
  1764.   
  1765. //在上面等待树中最多处理500个等待报文,超过则放到下轮再来处理,  
  1766. //同时返回延时时间为100ms。  
  1767. return HZ / 10;  
  1768.   
  1769. //更新最近事件处理时间,如果没有等待处理的包,则delay为0,下一次事件  
  1770. //处理则加长一些(1秒),否则还有等待处理的包,则下一次事件处理的时间  
  1771. //设置为上面的delay值。  
  1772. q->near_ev_cache[level] =q->jiffies + (delay ? delay : HZ);  
  1773. else  
  1774. //如果当前流逝时间还未到达下一次事件处理时间,则delay取值为下一次事  
  1775. //件处理所需要的时间点。  
  1776. delay = q->near_ev_cache[level] - q->jiffies;  
  1777.   
  1778. //因为最终在下面进行延时处理是使用min_delay的值,这里对该值进行校正  
  1779. if (delay && min_delay > delay)  
  1780. min_delay = delay;  
  1781.   
  1782. //row_mask[level]为当前层已经激活的类,这里取反是为了方便下面使用ffz进行  
  1783. //查找第一个为0的位所在索引。  
  1784. m = ~q->row_mask[level];  
  1785.   
  1786. //优先级激活位图如果取反为全为1,则表明当前层的类都没有被激活。  
  1787. while (m != (int)(-1))  
  1788. //优先取最低优先级位进行处理。  
  1789. int prio = ffz(m);  
  1790.   
  1791. m |= 1 << prio;   //每处理一次,当前优先级位就清除,以免二次进入。  
  1792.   
  1793. //指定层次、指定优先级的树中取出待发送的skb报文。  
  1794. skb = htb_dequeue_tree(q, prio, level);  
  1795. //q->row记载着激活的位图,仅类被激活后才可以发送报文,当子类未激  
  1796. //活时,从父类中租借代宽,父类就会激活。  
  1797. //q->ptr记载了最后处理的的类的树节点。  
  1798. //q->last_ptr_id记载了最后处理的类ID。  
  1799. //这里根据DDR算法,查找出可调度的HTB类。  
  1800. //htb_lookup_leaf函数比较复杂,放在下面单独分析。  
  1801.   
  1802. start = cl = htb_lookup_leaf(q->row[level] + prio, prio,q->ptr[level] + prio,  
  1803.                          q->last_ptr_id[level] + prio);  
  1804.   
  1805. do  
  1806. next:  
  1807. //由于代宽受限等原因导致当前没有可出队的报文。  
  1808. if (!cl)  
  1809. return NULL;  
  1810.   
  1811. //如果当前类的队列为空  
  1812. if (unlikely(cl->un.leaf.q->q.qlen == 0))  
  1813. //该类去激活  
  1814. htb_deactivate(q, cl);  
  1815. //检查如果当前层、当前优先级已经没有激活的类,则返回为  
  1816. //NULL,让上面函数继续下一层、下一优先级类的处理。  
  1817. if ((q->row_mask[level] & (1 << prio)) == 0)  
  1818. return NULL;  
  1819.   
  1820. //当前调度的类没有队列,并且当前层、当前优先级下还有激活  
  1821. //的类,则再次调用htb_lookup_leaf查找可调度的类。同时更新  
  1822. //临时变量start、cl。  
  1823. next = htb_lookup_leaf(q->row[level] + prio,  
  1824. prio, q->ptr[level] + prio,q->last_ptr_id[level] + prio);  
  1825.   
  1826. if (cl == start)  
  1827. start = next;  
  1828. cl = next;  
  1829. goto next;  
  1830.   
  1831. //使用当前类下的排队规则的dequeue回调进行将报文出队,这里  
  1832. //dequeue回调实现取决于用户侧给该类下绑定什么排队规则,如  
  1833. //果启用未设置,则默认HTB类下的排队规则为pfifo_qdisc,则  
  1834. //对应的回调函数为qdisc_dequeue_head,该函数比较简单,仅仅  
  1835. //是仅出双向链表中第一个节点的报文。  
  1836. skb = cl->un.leaf.q->dequeue(cl->un.leaf.q);  
  1837.   
  1838. //报文取出后直接跳出do while循环,返回。  
  1839. if (likely(skb != NULL))  
  1840. break;  
  1841.   
  1842. //当该类下标明有队列,但取出失败,则标记告警。  
  1843. if (!cl->warned)  
  1844. cl->warned = 1;  
  1845.   
  1846. q->nwc_hit++;    //统计不工作的错误类计数  
  1847. //将之前保存记录的ptr(最后处理的的类的树节点)更新为下一个节  
  1848. //点。  
  1849. htb_next_rb_node((level ? cl->parent->un.inner.ptr : q->ptr[0]) + prio);  
  1850.   
  1851. //再次调用htb_lookup_leaf查找可调度的类  
  1852. cl = htb_lookup_leaf(q->row[level] + prio, prio,q->ptr[level] + prio,  
  1853. q->last_ptr_id[level] + prio);  
  1854.   
  1855. //走到这里,表明刚才调度类在出队报文时异常,又重新选择了新的调度  
  1856. //类。重新进行出队处理。  
  1857. while (cl != start)  
  1858.   
  1859. //报文成功出队  
  1860. if (likely(skb != null))  
  1861. //htb_next_rb_node用来修改下一次使用htb_lookup_leaf选择调度  
  1862. //类时先从哪个类进行处理,这里根据quantum值的限额来进行  
  1863. //控制,可以防止htb_lookup_leaf在一段时间内始终使用相同的类  
  1864. //进行调度。  
  1865. if ((cl->un.leaf.deficit[level] -= skb->len) < 0)  
  1866. cl->un.leaf.deficit[level] += cl->un.leaf.quantum;  
  1867. htb_next_rb_node((level ? cl->parent->un.inner.ptr : q->ptr[0]) +   
  1868. prio);  
  1869.   
  1870. //当前类下的排队规则中已经没有报文,则将该类去激活。  
  1871. if (!cl->un.leaf.q->q.qlen)  
  1872. htb_deactivate(q, cl);  
  1873.   
  1874. //对该类进行收费计算。  
  1875. htb_charge_class(q, cl, level, skb->len);  
  1876. //临时定义了一个计帐功能宏,这里T可以是tokens或ctokens,  
  1877. //B可以是buffer或cbuffer,R可以是rate或ceil。  
  1878. //T表示租借模式或非租借模式下的令牌数。  
  1879. //B表示租借模式或非租借模式下基于对应速率的单包峰值。  
  1880. //R表示租借模式或非租借模式下不同速率。  
  1881. //首先将根据流逝时间来对所剩的令牌数进行补充。  
  1882. #define HTB_ACCNT(T,B,R) toks = diff + cl->T; \  
  1883. //对令牌数进行上限峰值限制  
  1884. if (toks > cl->B) toks = cl->B; \  
  1885. //根据当前报文字节数,在速率表中查找符合当前字节范围  
  1886. //之内所需消耗的单位值,之后从令牌总数中扣除消耗的单  
  1887. //位值。  
  1888. toks -= L2T(cl, cl->R, bytes); \  
  1889. //如果令牌数小于最大负等待峰值时间(1分钟),则对令牌  
  1890. //数修正为最大负等待峰值时间。  
  1891. if (toks <= -cl->mbuffer) toks = 1-cl->mbuffer; \  
  1892. //将剩余的令牌数保留,供下一次使用。  
  1893. cl->T = toks  
  1894.   
  1895. while (cl)  
  1896. //计算当前HTB类最后一个工作到当前已经流逝的时间,  
  1897. //不能超过mbuffer上限。  
  1898. diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32)   
  1899. cl->mbuffer);  
  1900.   
  1901. //该函数第一次处理的cl类都是叶子类,之后在while  
  1902. //循环中cl类依次改变为当前类的父类,而level在循环  
  1903. //中是始终保持不变的。如果当前level是0,表明叶子一直  
  1904. //未超过最低速率,始终是可发送的。如果当前level非0,  
  1905. //表明叶子已经在借用父类的带宽。当cl->level >= level,则  
  1906. //对当前类及父类都进行tokens计费处理,当  
  1907. //cl->level < level则表明当前类是在对父类进行租借,不  
  1908. //再对当前类进行tokens计费处理,仅对提供租借的父类  
  1909. //进行tokens计费处理。但不管当前类是否在租借都会  
  1910. //对ctokens进行计费处理。这里要理解tokens与ctokens  
  1911. //的差别,tokens是当前类的速率限制,ctokens是可向父类  
  1912. //借用的速率限制。如果使用当前类发送报文时小于tokens,  
  1913. //则当前类速率还比较正常,可以继续处理,如果当前发送  
  1914. //报文时大于tokens但小于ctokens,则表明当前类已经超出  
  1915. //自己的速率限制,但还可以从父类借用带宽,如果当前发  
  1916. //送报文时大于ctokens,则表明都超出了可借用速率,此时  
  1917. //该类的模式会修改为不可发送模式,当前类暂时已经不能  
  1918. //在发送任何报文。  
  1919. if (cl->level >= level)  
  1920. if (cl->level == level)  
  1921. cl->xstats.lends++;  
  1922. HTB_ACCNT(tokens, buffer, rate);  
  1923. else  
  1924. cl->xstats.borrows++;  
  1925. cl->tokens += diff;  
  1926. HTB_ACCNT(ctokens, cbuffer, ceil);  
  1927.   
  1928. //记载当前类最后处理时间,用于下一个时间点来统计已经  
  1929. //流逝的时间。  
  1930. cl->t_c = q->now;  
  1931.   
  1932. //根据当前类中tokens、ctokens剩余令牌数,使用  
  1933. //htb_change_class_mode进行HTB类的模式切换处理。  
  1934. //同时根据是否还是可发送模式而确定是否将类插入到  
  1935. //HTB排队规则的等待树中。  
  1936. old_mode = cl->cmode;  
  1937. diff = 0;  
  1938. htb_change_class_mode(q, cl, &diff);  
  1939. if (old_mode != cl->cmode)  
  1940. if (old_mode != HTB_CAN_SEND)  
  1941. htb_safe_rb_erase(&cl->pq_node, q->wait_pq +   
  1942. cl->level);  
  1943. if (cl->cmode != HTB_CAN_SEND)  
  1944. htb_add_to_wait_tree(q, cl, diff);  
  1945.   
  1946. //非叶子类的统计。  
  1947. if (cl->level)  
  1948. cl->bstats.bytes += bytes;  
  1949. cl->bstats.packets++;  
  1950.   
  1951. //将cl修改为当前类的父类,在while循环中依次对父类  
  1952. //进行处理。  
  1953. cl = cl->parent;  
  1954.   
  1955. //如果成功取出报文,则HTB排队规则的队列个数递减,同时去除受限标记,  
  1956. //将当前skb返回。  
  1957. if (likely(skb != NULL))  
  1958. sch->q.qlen--;  
  1959. sch->flags &= ~TCQ_F_THROTTLED;  
  1960. goto fin;  
  1961.   
  1962. //当前HTB排队规则中有报文,但遍历完所有层、所有优先级的类后,没有成功取出  
  1963. //报文,则进行延时处理。  
  1964. htb_delay_by(sch, min_delay > 5 * HZ ? 5 * HZ : min_delay);  
  1965. //修改当前HTB排队规则的timer定时器时间,该定时器是在创建HTB排队规则  
  1966. //时,通过htb_init函数创建,该定时器的超时处理函数为htb_timer,该函数比较  
  1967. //简单,仅仅是去除TCQ_F_THROTTLED受限标记,之后调用netif_schedule进行  
  1968. //发送队列的重新调度。  
  1969. mod_timer(&q->timer, q->jiffies + delay);  
  1970.   
  1971. //加上受限标记,更新overlimits超出限速的统计。  
  1972. sch->flags |= TCQ_F_THROTTLED;  
  1973. sch->qstats.overlimits++;  
  1974.   
  1975. -----------------------------------------------------------------------------------------------------------------  
  1976. 查找可调度的叶子类(虽然代码不多,但这里是整个HTB排队规则中最难的一部分)  
  1977. htb_lookup_leaf(struct rb_root *tree, int prio,struct rb_node **pptr, u32 * pid)  
  1978. //定义了8个元素的数组,初始sp指向第0个元素。  
  1979. struct {  
  1980. struct rb_node *root;  
  1981. struct rb_node **pptr;  
  1982. u32 *pid;  
  1983. } stk[TC_HTB_MAXDEPTH], *sp = stk;  
  1984.   
  1985. //当前第0个元素的初始值由传入的参数提供,root指向当前层次、当前优先级下待处  
  1986. //理的HTB类所在的树节点。pptr指向HTB排队规则中记载的最后处理的树节点,  
  1987. //注意这里使用了双层指针,在当前函数处理时就可以通过*pptr来对传入参数进行修  
  1988. //改,更新HTB排队规则中记载的最后处理树节点,以方便下一次报文处理时使用。  
  1989. //pid指向HTB排队规则中记载的最后处理的类ID,同样,这里可以通过*pid来对传  
  1990. //入参数进行修改。  
  1991. sp->root = tree->rb_node;  
  1992. sp->pptr = pptr;  
  1993. sp->pid = pid;  
  1994.   
  1995. //尝试65535次处理  
  1996. for (i = 0; i < 65535; i++)  
  1997. //当树节点不存在,但类ID存在,则使用类ID尝试在当前待处理的树中进行查  
  1998. //找树节点。通过查看代码,发现有一种情景会走此流程,如果一个HTB叶子  
  1999. //类为租借模式,当其发送报文后,队列中已经没有数据,则触发htb_deactivate_prios  
  2000. //函数,该函数会判断当前类为租借模式,并且正好匹配父类中un.inner.ptr  
  2001. //记载的最后处理树节点,则将父类的un.inner.ptr设置为空,同时父类的  
  2002. //un.inner.last_ptr_id保留此叶子类的ID,并将该类的树节点从父类的供应树中  
  2003. //删除。之后立即有一个新的报文使用htb_enqueue函数对该叶子类又进行入队  
  2004. //操作,则触发htb_activate_prios函数将此叶子类又加入到父类的供应树中。  
  2005. //那么当使用htb_lookup_leaf查找可调度类时,如果q->ptr队列的最后树节点  
  2006. //处理正好是这个父类,在此函数中会将sp->root指向父类的供应树,sp->pptr  
  2007. //指向父类的un.inner.ptr,sp->pid指向父类的un.inner.last_ptr_id,此时正好满足  
  2008. //当前IF的条件判断。  
  2009. if (!*sp->pptr && *sp->pid)  
  2010. *sp->pptr = htb_id_find_next_upper(prio, sp->root, *sp->pid);  
  2011.   
  2012. //清除pid,保证上面的条件判断仅触发一次。  
  2013. *sp->pid = 0;  
  2014.   
  2015. //如果当前树节点不存在  
  2016. if (!*sp->pptr)  
  2017. //将树节点指向当前待处理的树根节点。一定要留意,使用了*sp也就意味着  
  2018. //将sp->pptr之前指向的对象内容做了修改,即如果之前对象是指针类型,则  
  2019. //把原对象的指针指向进行了修改。  
  2020. *sp->pptr = sp->root;  
  2021.   
  2022. //将pptr指向当前树中最小类ID的节点(生成该树时就是以类ID为索引进行  
  2023. //的红黑树排序)。  
  2024. while ((*sp->pptr)->rb_left)  
  2025. *sp->pptr = (*sp->pptr)->rb_left;  
  2026.   
  2027. //这里stk就是上面的临时数组,也相当于数组的第0元素指针,刚进入该  
  2028. //函数时,sp是指向数组的第0个元素的,当下面发现找到的调度类  
  2029. //非叶子类时,sp就会递增,同时sp将指向该类的供给类(也就是该  
  2030. //类的孩子类)。如果出现这种情况,则sp--来退到当前类的父类,同时  
  2031. //对父类的sp->pptr最后使用的树节点进行更新,htb_next_rb_node函数  
  2032. //是将sp->pptr值设置为比sp->pptr更大一点的节点(如果有右子树,则  
  2033. //取右子树中最小的树节点;否则如果有父亲则取父亲中最小的树节点;  
  2034. //否则如果没有父子节点则取值为NULL)。  
  2035. if (sp > stk)  
  2036. sp--;  
  2037. if (!*sp->pptr)  
  2038. return NULL;  
  2039. htb_next_rb_node(sp->pptr);  
  2040.   
  2041. else  
  2042. //pptr找到了树节点,则取出包含该树节点的类对象  
  2043. cl = rb_entry(*sp->pptr, struct htb_class, node[prio]);  
  2044.   
  2045. //检测如果当前是叶子类则表示查找成功,返回该叶子类。  
  2046. if (!cl->level)  
  2047. return cl;  
  2048.   
  2049. //走到此流程表明找到的类是非叶子类型,此时递增sp,使其指向stk的下一  
  2050. //个数组元素,同时将该类的相关信息存储到这个新的数组元素中。root指向  
  2051. //当前类供应的子类树节点,pptr指向当前类的最后处理树节点,pid指向当前  
  2052. //类的最后处理类ID。  
  2053. (++sp)->root = cl->un.inner.feed[prio].rb_node;  
  2054. sp->pptr = cl->un.inner.ptr + prio;  
  2055. sp->pid = cl->un.inner.last_ptr_id + prio;  
  2056.   
  2057. //经过这么多次查找,仍然没找到可调度的叶子类,则查找失败,返回NULL。  
  2058. return NULL;  
  2059. ---------------------------------------------------------------------------------------------------------------------  
  2060. (注意: 以下分析仅考虑htb_lookup_leaf函数配合类激活、去激活的流程分析,并未考虑数据报发送成功后,在其它条件下也会使用htb_next_rb_node进行下一个待处理的树节点修改的情况)。  
  2061. 一、到这里已经对htb_lookup_leaf怎样进行查找可调度的叶子类分析完成,但可能还是云里雾里,这里用一个实例来描述htb_lookup_leaf函数的晦涩算法机制。在如下实例中,我们假设仅创建了三个类(根父类、子类1、子类2),这里根类的层次为最高层7,叶子类的层次为最低层0,同时假设这三个类的优先级都为0级,所以可以看到下图中子类1与子类2在同层次、同优先级情况下构建了一棵树,并且假设子类1的ID比较小,所以子类1在红黑树的左侧。  
  2062.   
  2063. <img src="http://img.blog.csdn.net/20141214041248546?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />  
  2064.   
  2065. 1、最初时,子类1、与子类2都为可发送模式,并且随着报文进入对应类的队列,类分别被激活。  
  2066. 2、在出队时,根据第0层优先、0优先级优先的原则,此时会找到子类2的树对象上。  
  2067. 3、之后使用htb_lookup_leaf函数进行查找可调度的叶子类。  
  2068. 4、最初HTB排队规则的q->ptr最后处理的树节点为NULL,所以会走到if (!*sp->pptr)流程,在该流程中会更新q->ptr,使其指向子类2的树节点,之后又遍历该树中最小的类ID节点,所以q->ptr又被更新为子类1树节点。此时子类1是叶子类,查找成功,返回当前调度类为子类1。  
  2069. 5、后续由于q->ptr最后处理树节点指向了子类1,所有只要子类1一直有待发送的数据,则始终使用子类1进行调度。  
  2070.   
  2071. 二、当子类1不断发包,已经超出其带宽限制后。  
  2072. <img src="http://img.blog.csdn.net/20141214041507730?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" height="194" width="304" alt="" />  
  2073. 1、子类1的模式由可发送模式变为租借模式,在模式变更时触发了两个关键动作,首先子类1进行去激活处理,在去激活处理过程中q->ptr指向了同层次、同优先级、比子类1略大一点的子类2上。其次,父类被激活,同时父类的供给树根节点指向了子类1的树节点。  
  2074. 2、在后续出队时,优先处理第0层、第0优先级的类,当前q->ptr已经指向子类2,所以后续始终用子类2进行调度。  
  2075.   
  2076. 三、当子类2不断发包,已经超出其带宽限制后。  
  2077. <img src="http://img.blog.csdn.net/20141214041400431?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbmZlbmcxOTg0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />  
  2078. 1、子类2的模式由可发送模式变为租借模式,在模式变更时触发了两个关键动作,首先子类2进行去激活处理,在去激活处理过程中由于子类2已经是只含1个节点的树了,q->ptr指向了NULL。其次,子类2也被加入到了父类的供给树中,由于和子类1的优先级相同,所以子类2和子类1又成为一棵树上的节点。  
  2079. 2、此时第0层的类已经没有激活的,所以只能从当前只激活的第7层类中进行处理。首先检测当前q->ptr为NULL,则将q->ptr指向父类的树节点。(注意当前父类的树仅有一个节点,图中画的连线并非父节点的子节点,也就是说父类在HTB排队规则中指定层次、指定优先级的那棵树下仅自己一个节点,而父类自身有一个feed供给树,用来标识当前父类都给哪些孩子类提供带宽借用。)  
  2080. 3、之后判断当前父类并非叶子类,则将sp指向stk数组的第1个元素,第1个元素的  
  2081. root指向当前父类feed供给树的根节点(子类1),第1个元素的pptr指向父类的cl->un.inner.ptr,第1个元素的pid指向父类的cl->un.inner.last_ptr_id。  
  2082. 4、由于最终父类的cl->un.inner.ptr还为NULL值,所有又走到if (!*sp->pptr)条件里去,在这里将父类的cl->un.inner.ptr指向了父类的供给树根节点(子类1)。  
  2083. 5、sp此时指向stk数组的第1个元素,所有if (sp > stk)条件成立,将sp指针递减回第0个数组元素,第0个数组元素在第2步中已经将q->ptr指向父类的树节点,由于父类树节点仅有一个树节点,所有在执行htb_next_rb_node(sp->pptr)时返回NULL,导致q->ptr又变成NULL值。  
  2084. 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。  
  2085. 7、此时父类的cl->un.inner.ptr已经指向子类1,所以判断子类1为叶子类,则对子类1进行调度。  
  2086. 8、后续只要子类1中始终有报文,则重复第6、7步对子类1进行调度处理。  
0 0