《linux内核对qos的支持》由会员分享,可在线阅读,更多相关《linux内核对qos的支持(9页珍藏版)》请在金锄头文库上搜索。
1、本文描述了 linux 2.4.x 内核中对 QoS 支持的设计与实现,并且对缺省的数据包调度机制 PFIFO进行了详细的分析。在传统的 TCP/IP 网络的路由器中,所有的 IP 数据包的传输都是采用 FIFO(先进先出) ,尽最大努力传输的处理机制。在早期网络数据量和关键业务数据不多的时候,并没有体现出非常大的缺点,路由器简单的把数据报丢弃来处理拥塞。但是随着计算机网络的发展, 数据量的急剧增长,以及多媒体,VOIP 数据等对延时要求高的应用的增加。路由器简单丢弃数据包的处理方法已经不再适合当前的网络。单纯的增加网络带宽也不能从根本上解决问题。所以网络的开发者们提出了服务质量的概念。概括的
2、说:就是针对各种不同需求,提供不同服务质量的网络服务功能。提供 QoS 能力将是对未来 IP 网络的基本要求。1Linux 内核对 QoS 的支持Linux 内核网络协议栈从2.2.x 开始,就实现了对服务质量的支持模块。具体的代码位于net/sched/目录。在 Linux 里面,对这个功能模块的称呼是 Traffic Control ,简称 TC。首先我们了解一下 Linux 网络协议栈在没有 TC 模块时发送数据包的大致流程。如图 1。注:上图的分层是按照 Linux 实现来画,并没有严格遵守 OSI 分层从上图可以看出,没有 TC 的情况下,每个数据包的发送都会调用 dev_queue
3、_xmit,然后判断是否需要向 AF_PACKET 协议支持体传递数据包内容,最后直接调用网卡驱动注册的发送函数把数据包发送出去。发送数据包的机制就是本文开始讲到的 FIFO 机制。一旦出现拥塞,协议栈只是尽自己最大的努力去调用网卡发送函数。所以这种传统的处理方法存在着很大的弊端。为了支持 QoS,Linux 的设计者在发送数据包的代码中加入了 TC 模块。从而可以对数据包进行分类,管理,检测拥塞和处理拥塞。为了避免和以前的代码冲突,并且让用户可以选择是否使用 TC。内核开发者在上图中的两个红色圆圈之间添加了 TC 模块。 (实际上在 TC模块中,发送数据包也实现对 AF_PACKET 协议的
4、支持,本文为了描述方便,把两个地方的 AF_PACKET 协议处理分开来了) 。下面从具体的代码中分析一下对 TC 模块的支持。net/core/dev.c: dev_queue_xmit 函数中略了部分代码: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_ni
5、t)dev_queue_xmit_nit(skb,dev);/*对 AF_PACKET 协议的支持*/if (dev-hard_start_xmit(skb, dev) = 0) /*调用网卡驱动发送函数发送数据包*/return 0;从上面的代码中可以看出,当 q-enqueue 为假的时候,就不采用 TC 处理,而是直接发送这个数据包。如果为真,则对这个数据包进行 QoS 处理。2TC 的具体设计与实现第一节描述了 linux 内核是如何对 QoS 进行支持的,以及是如何在以前的代码基础上添加了 tc 模块。本节将对 TC 的设计和实现进行详细的描述。QoS 有很多的拥塞处理机制,如 FI
6、FO Queueing(先入先出队列) ,PQ(优先队列) ,CQ(定制队列) ,WFQ(加权公平队列)等等。QoS 还要求能够对每个接口分别采用不同的拥塞处理。为了能够实现上述功能,Linux 采用了基于对象的实现方法。上图是一个数据发送队列管理机制的模型图。其中的 QoS 策略可以是各种不同的拥塞处理机制。我们可以把这一种策略看成是一个类,策略类。在实现中,这个类有很多的实例对象,策略对象。使用者可以分别采用不同的对象来管理数据包。策略类有很多的方法。如入队列(enqueue) ,出队列(dequeue) ,重新入队列(requeue),初始化(init),撤销(destroy)等方法。在
7、 Linux 中,用 Qdisc_ops 结构体来代表上面描述的策略类。前面提到,每个设备可以采用不同的策略对象。所以在设备和对象之间需要有一个桥梁,使设备和设备采用的对象相关。在 Linux 中,起到桥梁作用的是 Qdisc 结构体。通过上面的描述,整个 TC 的架构也就出来了。如下图:加上 TC 之后,发送数据包的流程应该是这样的:(1) 上层协议开始发送数据包(2) 获得当前设备所采用的策略对象(3) 调用此对象的 enqueue 方法把数据包压入队列(4) 调用此对象的 dequeue 方法从队列中取出数据包(5) 调用网卡驱动的发送函数发送接下来从代码上来分析 TC 是如何对每个设备
8、安装策略对象的。在网卡注册的时候,都会调用 register_netdevice,给设备安装一个 Qdisc 和 Qdisc_ops。int register_netdevice(struct net_device *dev).dev_init_scheduler(dev);.void dev_init_scheduler(struct net_device *dev)./*安装设备的 qdisc 为 noop_qdisc*/dev-qdisc = .dev-qdisc_sleeping = dev_watchdog_init(dev);此时,网卡设备刚注册,还没有 UP,采用的是 noop_
9、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,;从 noop_enqueue,noop_dequeue,noop_requeue 函数的定义可以看出,他们并没有对数据包进行任何的分类或者排队,而是直接释放掉 sk
10、b。所以此时网卡设备还不能发送任何数据包。必须 ifconfig up 起来之后才能发送数据包。调用 ifconfig up 来启动网卡设备会走到 dev_open 函数。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, /*安装缺省的 qdisc*/if (dev-qdisc = dev-qdisc_sleep
11、ing) != &noqueue_qdisc) ./*.安装特定的 qdisc*/.设备启动之后,此时当前设备缺省的 Qdisc-ops 是 pfifo_fast_ops。如果需要采用不同的ops,那么就需要为设备安装其他的 Qdisc。本质上是替换掉 dev-Qdisc 指针。见sched/sch_api.c 的 dev_graft_qdisc 函数。static struct Qdisc *dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc)oqdisc = dev-qdisc_sleeping;/* 首先删除掉旧的 qd
12、isc */if (oqdisc & atomic_read(&oqdisc-refcnt) qdisc_sleeping = qdisc;dev-qdisc = /*启动新安装的 qdisc*/if (dev-flags & IFF_UP)dev_activate(dev);从 dev_graft_qdisc 可以看出,如果需要使用新的 Qdisc,那么首先需要删除旧的,然后安装新的,使 dev-qdisc_sleeping 为新的 qdisc,然后调用 dev_activate 函数来启动新的 qdisc。结合 dev_activate 函数中的语句:if (dev-qdisc = dev
13、-qdisc_sleeping) != &noqueue_qdisc) 可以看出,此时的 dev-qdisc 所指的就是新的 qdisc。 (注意,上面语句中左边是一个赋值语句。 )在网卡 down 掉的时候,通过调用 dev_close - dev_deactivate 重新使设备的 qdisc 为noop_qdisc,停止发送数据包。Linux 中的所有的 QoS 策略最终都是通过上面这个方法来安装的。在 sch_api.c 中,对dev_graft_qdisc 函数又封装了一层函数( register_qdisc) ,供模块来安装新的 Qdisc。如RED(早期随即检测队列)模块,就调用
14、 register_qdisc 来安装 RED 对象(net/sched/sch_red.c-init_module()) 。3. Linux 缺省策略对象 pfifo_fast_ops 分析在 Linux 中,如果设备启动之后,没有配置特定的 QoS 策略,内核对每个设备采用缺省的策略,pfifo_fast_ops。下面的 pfifo_fast_ops 进行详细的分析。上图中的信息可以对应于 pfifo_fast_ops 结构体的每个部分:static struct Qdisc_ops pfifo_fast_ops =NULL,NULL,pfifo_fast, /*ops 名称*/3 * s
15、izeof(struct sk_buff_head), /*数据包 skb 队列*/pfifo_fast_enqueue, /*入队列函数*/pfifo_fast_dequeue, /*出队列函数*/pfifo_fast_requeue, /*重新压入队列函数*/NULL,pfifo_fast_init, /*队列管理初始化函数*/pfifo_fast_reset, /*队列管理重置函数*/;在注册 pfifo_fast_ops 的时候首先会调用 pfifo_fast_init 来初始化队列管理,见qdisc_create_dflt 函数。static int pfifo_fast_init(struct Qdisc *qdisc, struct rtattr *opt)for (i=0; ienqueue 函数(在 qdisc_create_dflt 函数中已经将Qdisc_ops 的 enqueue,dequeue,requeue 函数分别赋值于 Qdisc 分别对应的函数指针) 。int dev_queue_xmit(struct sk_buff *skb).q = dev-qdisc;if (q-enqueu