📄 linux 2_4_x 网络协议栈qos模块(tc)的设计与实现.htm
字号:
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-qos/index.shtml#author1">关于作者</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End TOC--><!-- Start Related Content Area--><!-- <table border="0" cellpadding="0" cellspacing="0" width="160"><tr><td bgcolor="#000000" height="1" width="160"><img alt="" height="1" src="/developerWorks/cn/i/c.gif" width="160" /></td></tr><tr><td align="center" background="/developerWorks/cn/i/bg-gold.gif" height="5"><b>相关内容:</b></td></tr><tr><td bgcolor="#666666" height="1" width="160"><img alt="" height="1" src="/developerWorks/cn/i/c.gif" width="160" /></td></tr><tr><td align="right"><table border="0" cellpadding="3" cellspacing="0" width="98%"><tr><td><a href="#1">介绍</a></td></tr><tr><td><a href="#1">介绍</a></td></tr></table></td></tr></table> --><!-- End TOC--><!-- Start Related Content Area-->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt=""
src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/c.gif"
width=160></TD></TR>
<TR>
<TD align=middle
background="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/bg-gold.gif"
height=5><A class=nav
href="http://www-900.ibm.com/developerWorks/cn/linux/index.shtml"><B>在
Linux 专区还有:</B></A></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt=""
src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/c.gif"
width=160></TD></TR>
<TR>
<TD align=right>
<TABLE cellSpacing=0 cellPadding=3 width="98%" border=0>
<TBODY>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/linux-onlinecourse-bytitle?OpenView&Count=500">教程</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cntools.nsf/dw/linux-codelib-byname?OpenDocument&Count=500">工具与产品</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cntools.nsf/dw/linux-projects-byname?OpenDocument&Count=500">代码与组件</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/cnpapers.nsf/linux-papers-bynewest?OpenView&Count=500">文章</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End Related dW Content Area-->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2
alt="" src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/c.gif"
width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2
alt="" src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/c.gif"
width=160></TD></TR></TBODY></TABLE><!-- END STANDARD SIDEBAR AREA--></TD></TR></TBODY></TABLE><!-- START SUBTITLE AND CONTENT-->
<P><A
href="http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-qos/index.shtml#author1">祝顺民</A>
(<A href="mailto:getmoon@163.com">getmoon@163.com</A>)<BR>2003 年 6 月</P>
<P>本文描述了linux 2.4.x内核中对QoS支持的设计与实现,并且对缺省的数据包调度机制PFIFO进行了详细的分析。</P>
<P>在传统的TCP/IP网络的路由器中,所有的IP数据包的传输都是采用FIFO(先进先出),尽最大努力传输的处理机制。在早期网络数据量和关键业务数据不多的时候,并没有体现出非常大的缺点,路由器简单的把数据报丢弃来处理拥塞。但是随着计算机网络的发展,
数据量的急剧增长,以及多媒体,VOIP数据等对延时要求高的应用的增加。路由器简单丢弃数据包的处理方法已经不再适合当前的网络。单纯的增加网络带宽也不能从根本上解决问题。所以网络的开发者们提出了服务质量的概念。概括的说:就是针对各种不同需求,提供不同服务质量的网络服务功能。提供QoS能力将是对未来IP网络的基本要求。</P>
<P><A name=1><SPAN class=atitle2>1.Linux内核对QoS的支持</SPAN></A></P>
<P>Linux内核网络协议栈从2.2.x开始,就实现了对服务质量的支持模块。具体的代码位于net/sched/目录。在Linux里面,对这个功能模块的称呼是Traffic
Control ,简称TC。</P>首先我们了解一下Linux网络协议栈在没有TC模块时发送数据包的大致流程。如图1。
<P>
<CENTER><IMG height=346 alt=""
src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/image001.gif" width=463
border=0> <BR></CENTER>
<P></P>
<P>注:上图的分层是按照Linux实现来画,并没有严格遵守OSI分层</P>
<P>从上图可以看出,没有TC的情况下,每个数据包的发送都会调用dev_queue_xmit,然后判断是否需要向AF_PACKET协议支持体传递数据包内容,最后直接调用网卡驱动注册的发送函数把数据包发送出去。发送数据包的机制就是本文开始讲到的FIFO机制。一旦出现拥塞,协议栈只是尽自己最大的努力去调用网卡发送函数。所以这种传统的处理方法存在着很大的弊端。</P>
<P>为了支持QoS,Linux的设计者在发送数据包的代码中加入了TC模块。从而可以对数据包进行分类,管理,检测拥塞和处理拥塞。为了避免和以前的代码冲突,并且让用户可以选择是否使用TC。内核开发者在上图中的两个红色圆圈之间添加了TC模块。(实际上在TC模块中,发送数据包也实现对AF_PACKET协议的支持,本文为了描述方便,把两个地方的AF_PACKET协议处理分开来了)。</P>
<P>下面从具体的代码中分析一下对TC模块的支持。</P>
<P>net/core/dev.c: dev_queue_xmit函数中略了部分代码:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
int dev_queue_xmit(struct sk_buff *skb)
{
……………….
q = dev->qdisc;
if (q->enqueue) {
/*如果这个设备启动了TC,那么把数据包压入队列*/
int ret = q->enqueue(skb, q);
/*启动这个设备发送*/
qdisc_run(dev);
return;
}
if (dev->flags&IFF_UP) {
………….
if (netdev_nit)
dev_queue_xmit_nit(skb,dev);
/*对AF_PACKET协议的支持*/
if (dev->hard_start_xmit(skb, dev) == 0) {
/*调用网卡驱动发送函数发送数据包*/
return 0;
}
}
………………
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>从上面的代码中可以看出,当q->enqueue为假的时候,就不采用TC处理,而是直接发送这个数据包。如果为真,则对这个数据包进行QoS处理。</P>
<P><A name=2><SPAN class=atitle2>2.TC的具体设计与实现</SPAN></A></P>
<P>第一节描述了linux内核是如何对QoS进行支持的,以及是如何在以前的代码基础上添加了tc模块。本节将对TC的设计和实现进行详细的描述。</P>
<P>QoS有很多的拥塞处理机制,如FIFO
Queueing(先入先出队列),PQ(优先队列),CQ(定制队列),WFQ(加权公平队列)等等。QoS还要求能够对每个接口分别采用不同的拥塞处理。为了能够实现上述功能,Linux采用了基于对象的实现方法。</P>
<P>
<CENTER><IMG height=263 alt=""
src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/image002.gif" width=337
border=0> <BR></CENTER>
<P></P>
<P>上图是一个数据发送队列管理机制的模型图。其中的QoS策略可以是各种不同的拥塞处理机制。我们可以把这一种策略看成是一个类,策略类。在实现中,这个类有很多的实例对象,策略对象。使用者可以分别采用不同的对象来管理数据包。策略类有很多的方法。如入队列(enqueue),出队列(dequeue),重新入队列(requeue),初始化(init),撤销(destroy)等方法。在Linux中,用Qdisc_ops结构体来代表上面描述的策略类。</P>
<P>前面提到,每个设备可以采用不同的策略对象。所以在设备和对象之间需要有一个桥梁,使设备和设备采用的对象相关。在Linux中,起到桥梁作用的是Qdisc结构体。</P>
<P>通过上面的描述,整个TC的架构也就出来了。如下图:</P>
<P>
<CENTER><IMG height=307 alt=""
src="Linux 2_4_x 网络协议栈QoS模块(TC)的设计与实现.files/image003.gif" width=418
border=0> <BR></CENTER>
<P></P>
<P>加上TC之后,发送数据包的流程应该是这样的:</P>
<P>(1) 上层协议开始发送数据包</P>
<P>(2) 获得当前设备所采用的策略对象</P>
<P>(3) 调用此对象的enqueue方法把数据包压入队列</P>
<P>(4) 调用此对象的dequeue方法从队列中取出数据包</P>
<P>(5) 调用网卡驱动的发送函数发送</P>
<P>接下来从代码上来分析TC是如何对每个设备安装策略对象的。</P>
<P>在网卡注册的时候,都会调用register_netdevice,给设备安装一个Qdisc和Qdisc_ops。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
int register_netdevice(struct net_device *dev)
{
………………….
dev_init_scheduler(dev);
………………….
}
void dev_init_scheduler(struct net_device *dev)
{
………….
/*安装设备的qdisc为noop_qdisc*/
dev->qdisc = &noop_qdisc;
………….
dev->qdisc_sleeping = &noop_qdisc;
dev_watchdog_init(dev);
}
此时,网卡设备刚注册,还没有UP,采用的是noop_qdisc,
struct Qdisc noop_qdisc =
{
noop_enqueue,
noop_dequeue,
TCQ_F_BUILTIN,
&noop_qdisc_ops,
};
noop_qdisc采用的数据包处理方法是noop_qdisc_ops,
struct Qdisc_ops noop_qdisc_ops =
{
NULL,
NULL,
"noop",
0,
noop_enqueue,
noop_dequeue,
noop_requeue,
};
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>从noop_enqueue,noop_dequeue,noop_requeue函数的定义可以看出,他们并没有对数据包进行任何的分类或者排队,而是直接释放掉skb。所以此时网卡设备还不能发送任何数据包。必须ifconfig
up起来之后才能发送数据包。</P>
<P>调用ifconfig up来启动网卡设备会走到dev_open函数。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
int dev_open(struct net_device *dev)
{
…………….
dev_activate(dev);
……………..
}
void dev_activate(struct net_device *dev)
{
…………. if (dev->qdisc_sleeping == &noop_qdisc) {
qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops);
/*安装缺省的qdisc*/
}
……………
if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc) {
……………./*.安装特定的qdisc*/
}
……………..
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>设备启动之后,此时当前设备缺省的Qdisc->ops是pfifo_fast_ops。如果需要采用不同的ops,那么就需要为设备安装其他的Qdisc。本质上是替换掉dev->Qdisc指针。见sched/sch_api.c
的dev_graft_qdisc函数。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
static struct Qdisc *
dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc)
{
……………
oqdisc = dev->qdisc_sleeping;
/* 首先删除掉旧的qdisc */
if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)
qdisc_reset(oqdisc);
/*安装新的qdisc */
if (qdisc == NULL)
qdisc = &noop_qdisc;
dev->qdisc_sleeping = qdisc;
dev->qdisc = &noop_qdisc;
/*启动新安装的qdisc*/
if (dev->flags & IFF_UP)
dev_activate(dev);
…………………
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>从dev_graft_qdisc可以看出,如果需要使用新的Qdisc,那么首先需要删除旧的,然后安装新的,使dev->qdisc_sleeping
为新的qdisc,然后调用dev_activate函数来启动新的qdisc。结合dev_activate函数中的语句:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc)
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>可以看出,此时的dev->qdisc所指的就是新的qdisc。(注意,上面语句中左边是一个赋值语句。)</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -