本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。msn:yfydz_no1@hotmail.com来源:内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下;而应用层上的控制是通过iproute2软件包中的tc来实现,tc和sched的关系就好象iptables和netfilter的关系一样,一个是用户层接口,一个是具体实现,关于tc的使用方法可详将LinuxAdvancedRoutingHOWTO,本文主要分析内核中的具体实现。流控包括几个部分:流控算法,通常在net/sched/sch_*.c中实现,缺省的是FIFO,是比较典型的黑盒模式,对外只看到入队和出对两个操作;流控结构的操作处理;和用户空间的控制接口,是通过rtnetlink实现的。以下内核代码版本为2.6.19.2。2.控制入口2.1控制入口linux流控功能反映为网卡设备的属性,表明是网络最底层的处理部分,已经和上层的网络协议栈无关了:/*include/linux/netdevice.h*/structnet_device{....../**Cachelinemostlyusedonqueuetransmitpath(qdisc)*//*devicequeuelock*/spinlock_tqueue_lock____cacheline_aligned_in_smp;//这是发送数据时的队列处理structQdisc*qdisc;//网卡停止时保存网卡活动时的队列处理方法structQdisc*qdisc_sleeping;//网卡处理的数据队列链表structlist_headqdisc_list;//最大队列长度unsignedlongtx_queue_len;/*Maxframesperqueueallowed*//*PartiallytransmittedGSOpacket.*/structsk_buff*gso_skb;/*ingresspathsynchronizer*///输入流控锁spinlock_tingress_lock;//这是对于接收数据时的队列处理structQdisc*qdisc_ingress;......2.1.2输出流控数据发出流控处理时,上层的所有处理已经完成,数据包已经交到网卡设备进行发送,在数据发送时进行相关的流控处理网络数据的出口函数为dev_queue_xmit();如果是接收流控,数据只是刚从网卡设备中收到,还未交到网络上层处理,不过网卡的输入流控不是必须的,缺省情况下并不进行流控,输入流控入口函数为ing_filter()函数,该函数被skb_receive_skb()调用:/*net/core/dev.c*/intdev_queue_xmit(structsk_buff*skb){structnet_device*dev=skb-dev;structQdisc*q;intrc=-ENOMEM;....../*Updatesofqdiscareserializedbyqueue_lock.*ThestructQdiscwhichispointedtobyqdiscisnowa*rcustructure-itmaybeaccessedwithoutacquiring*alock(butthestructuremaybestale.)Thefreeingofthe*qdiscwillbedeferreduntilit'sknownthatthereareno*morereferencestoit.**Iftheqdischasanenqueuefunction,westillneedto*holdthequeue_lockbeforecallingit,sincequeue_lock*alsoserializesaccesstothedevicequeue.*///获取网卡的qdisc指针,此出不需要锁,是各个CPU的私有数据q=rcu_dereference(dev-qdisc);#ifdefCONFIG_NET_CLS_ACTskb-tc_verd=SET_TC_AT(skb-tc_verd,AT_EGRESS);#endif//如果队列输入非空,将数据包入队//对于物理网卡设备,缺省使用的是FIFOqdisc,该成员函数非空,只有逻辑网卡//才可能为空if(q-enqueue){/*Grabdevicequeue*///加锁spin_lock(&dev-queue_lock);//可以直接访问dev-qdisc了q=dev-qdisc;if(q-enqueue){//入队处理rc=q-enqueue(skb,q);//运行流控,出队列操作qdisc_run(dev);spin_unlock(&dev-queue_lock);rc=rc==NET_XMIT_BYPASS?NET_XMIT_SUCCESS:rc;gotoout;}spin_unlock(&dev-queue_lock);}......}//出队操作staticinlinevoidqdisc_run(structnet_device*dev){if(!netif_queue_stopped(dev)&&!test_and_set_bit(__LINK_STATE_QDISC_RUNNING,&dev-state))__qdisc_run(dev);}/*net/sched/sch_generic.c*/void__qdisc_run(structnet_device*dev){//如果是noop_qdisc流控,实际是丢包if(unlikely(dev-qdisc==&noop_qdisc))gotoout;while(qdisc_restart(dev)0&&!netif_queue_stopped(dev))/*NOTHING*/;out:clear_bit(__LINK_STATE_QDISC_RUNNING,&dev-state);}/*Kickdevice.Note,thatthisprocedurecanbecalledbyawatchdogtimer,sothatwedonotcheckdev-tbusyflaghere.Returns:0-queueisempty.0-queueisnotempty,butthrottled.0-queueisnotempty.Deviceisthrottled,ifdev-tbusy!=0.NOTE:Calledunderdev-queue_lockwithlocallydisabledBH.*/staticinlineintqdisc_restart(structnet_device*dev){structQdisc*q=dev-qdisc;structsk_buff*skb;/*Dequeuepacket*///数据包出队if(((skb=dev-gso_skb))||((skb=q-dequeue(q)))){unsignednolock=(dev-features&NETIF_F_LLTX);dev-gso_skb=NULL;......}2.1.3输入流控输入流控好象不是必须的,目前内核需要配置CONFIG_NET_CLS_ACT选项才起作用:/*net/core/dev.c*/intnetif_receive_skb(structsk_buff*skb){......#ifdefCONFIG_NET_CLS_ACTif(pt_prev){ret=deliver_skb(skb,pt_prev,orig_dev);pt_prev=NULL;/*nooneelseshouldprocessthisafter*/}else{skb-tc_verd=SET_TC_OK2MUNGE(skb-tc_verd);}ret=ing_filter(skb);if(ret==TC_ACT_SHOT||(ret==TC_ACT_STOLEN)){kfree_skb(skb);gotoout;}skb-tc_verd=0;ncls:#endif......}staticinting_filter(structsk_buff*skb){structQdisc*q;structnet_device*dev=skb-dev;intresult=TC_ACT_OK;//如果网卡设备有输入流控处理if(dev-qdisc_ingress){__u32ttl=(__u32)G_TC_RTTL(skb-tc_verd);if(MAX_RED_LOOPttl++){printk(KERN_WARNINGRedirloopdetectedDroppingpacket(%s-%s)\n,skb-input_dev-name,skb-dev-name);returnTC_ACT_SHOT;}//设置数据包的TC参数skb-tc_verd=SET_TC_RTTL(skb-tc_verd,ttl);skb-tc_verd=SET_TC_AT(skb-tc_verd,AT_INGRESS);spin_lock(&dev-ingress_lock);if((q=dev-qdisc_ingress)!=NULL)//数据入队result=q-enqueue(skb,q);spin_unlock(&dev-ingress_lock);}returnresult;}2.2初始化本节先跟踪一下网卡设备的qdisc指针是如何被赋值的,缺省赋值为何值.在网卡设备的初始化函数register_netdevice()函数中调用dev_init_scheduler()函数对网卡设备的流控队列处理进行了初始化,也就是说每个网络网卡设备的qdisc指针都不会是空的:/*net/sched/sch_gentric.c*/voiddev_init_scheduler(structnet_device*dev){qdisc_lock_tree(dev);//处理发出数据的qdisc是必须的,而处理输入数据的qdisc_ingress则不是必须的//缺省情况下的qdiscdev-qdisc=&noop_qdisc;dev-qdisc_sleeping=&noop_qdisc;INIT_LIST_HEAD(&dev-qdisc_list);qdisc_unlock_tree(dev);dev_watchdog_init(dev);}当然noop_qdisc中的调度是不可用的,只进行丢包处理;网卡在激活(dev_open)时会调用dev_activate()函数重新对qdisc指针赋值,但未对qdisc_ingress赋值:/*net/sched/sch_generic.c*/voiddev_activate(structnet_device*dev){/*Noqueueingdisciplineisattachedtodevice;createdefaultonei.e.pfifo_fastfordevices,whichneedqueueingandnoqueue_qdiscforvirtualinterfaces*///如果当前的qdisc_sleeping是noop_qdisc,重新找一个流控操作指针if(dev-qdisc_sleeping==&noop_qdisc){structQdisc*qdisc;//前提条件是发送队列长度非0,这正常情况肯定满足的if(dev-tx_queue_len){//对dev设备建立fifo处理,只是缺省的网卡发送策略:FIFO,先入先出qdisc=qdi