古装电视剧头饰:基于Linux的QoS编程接口研究与分析

来源:百度文库 编辑:偶看新闻 时间:2024/05/04 05:35:44
基于Linux的QoS编程接口研究与分析(1)
第一章绪论
1.1什么是Linux
Linux是一套免费使用和自由传播的类Unix操作系统,它主要用于基于Intel x86系列CPU的计算机上。这个系统是由全世界各地的成千上万的程序员设计和实现的。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。 Linux的出现,最早开始于一位名叫Linus Torvalds的计算机业余爱好者,当时他是芬兰赫尔辛基大学的学生。他的目的是想设计一个代替Minix(是由一位名叫Andrew Tannebaum的计算机教授编写的一个操作系统示教程序)的操作系统,这个操作系统可用于386、486或奔腾处理器的个人计算机上,并且具有Unix操作系统的全部功能,因而开始了Linux雏形的设计。 Linux以它的高效性和灵活性著称。它能够在PC计算机上实现全部的Unix特性,具有多任务、多用户的能力。Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统。
Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器、高级语言编译器等应用软件。它还包括带有多个窗口管理器的X-Windows图形用户界面,如同我们使用Windows NT一样,允许我们使用窗口、图标和菜单对系统进行操作。 Linux之所以受到广大计算机爱好者的喜爱,主要原因有两个,一是它属于自由软件,用户不用支付任何费用就可以获得它和它的源代码,并且可以根据自己的需要对它进行必要的修改,无偿对它使用,无约束地继续传播。另一个原因是,它具有Unix的全部功能,任何使用Unix操作系统或想要学习Unix操作系统的人都可以从Linux中获益。
Linux支持一系列的UNIX开发,它是一个完整的UNIX开发平台,几乎所有的主流程序设计语言都已移植到Linux上并可免费得到,如C、C++、Fortran77、ADA、PASCAL、Modual2和3、Tcl/TkScheme、SmallTalk/X等。
由于Linux是一套具有Unix全部功能的免费操作系统,它在众多的软件中占有很大的优势,为广大的计算机爱好者提供了学习、探索以及修改计算机操作系统内核的机会。操作系统是一台计算机必不可少的系统软件,是整个计算机系统的灵魂。一个操作系统是一个复杂的计算机程序集,它提供操作过程的协议或行为准则。没有操作系统,计算机就无法工作,就不能解释和执行用户输入的命令或运行简单的程序。大多数操作系统都是由一些主要的软件公司支持的商品化程序,用户只能有偿使用。如果用户购买了一个操作系统,他就必须满足供应商所要求的一切条件。因为操作系统是系统程序,用户不能擅自修改或试验操作系统的内核。这对于广大计算机爱好者来说无疑是一种束缚。要想发挥计算机的作用,仅有操作系统还不够,用户还必须要有各种应用程序的支持。应用程序是用于处理某些工作(如字处理)的软件包,通常它也只能有偿使用。每个应用程序的软件包都为特定的操作系统和机器编写。使用者无权修改这些应用程序。使用Linux,可以将操作系统变成一种操作环境。由于Linux是一套自由软件,用户可以无偿地得到它及其源代码,可以无偿地获得大量的应用程序,而且可以任意地修改和补充它们。这对用户学习、了解Unix操作系统的内核非常有益。学习和使用Linux,能为用户节省一笔可观的资金。Linux是目前唯一可免费获得的、为PC机平台上的多个用户提供多任务、多进程功能的操作系统,这是人们要使用它的主要原因。就PC机平台而言,Linux提供了比其他任何操作系统都要强大的功能,Linux还可以使用户远离各种商品化软件提供者促销广告的诱惑,再也不用承受每过一段时间就升级之苦,因此,可以节省大量用于购买或升级应用程序的资金。 Linux不仅为用户提供了强大的操作系统功能,而且还提供了丰富的应用软件。用户不但可以从Internet上下载Linux及其源代码,而且还可以从Internet上下载许多Linux的应用程序。可以说,Linux本身包含的应用程序以及移植到Linux上的应用程序包罗万象,任何一位用户都能从有关Linux的 网站上找到适合自己特殊需要的应用程序及其源代码,这样,用户就可以根据自己的需要下载源代码,以便修改和扩充操作系统或应用程序的功能。这对Windows NT、Windows98、MS-DOS或OS/2等商品化操作系统来说是无法做到的。 Linux为广大用户提供了一个在家里学习和使用Unix操作系统的机会。尽管Linux是由计算机爱好者们开发的,但是它在很多方面上是相当稳定的,从而为用户学习和使用目前世界上最流行的Unix操作系统提供了廉价的机会。现在有许多CD-ROM供应商和软件公司,支持Linux操作系统。Linux成为Unix系统在个人计算机上的一个代用品,并能用于替代那些较为昂贵的系统。因此,如果一个用户在公司上班的时候在Unix系统上编程,或者在工作中是一位Unix的系统管理员,他就可以在家里安装一套Unix的兼容系统,即Linux系统,在家中使用Linux就能够完成一些工作任务。
1.2 Linux的特点
LINUX系统有文本编辑界面和图形用户界面(GUI),。其特征包括:多用户、多任务、多平台、可编程SHELL、提供源代码、仿真终端、支持多种文件系统及强大的网络功能等。
多用户:多个用户(六个)能同时从相同或不同的终端(终端号:tty1~tty6)上用同一个应用程序的副本进行工作。在控制台,切换终端的命令是:ALT+F1~F6;在仿真终端窗口(ps/0~n)是:SHIFT+ALT+F1~F6。
多任务:可同时执行多个程序,程序之间互不妨碍。与WINDOWS的? 务不同,LINUX将系统没有用到的剩余物理内存全部用来做硬盘的高速缓存。笔者曾经打开三个xterm,分别用于查找文件、调试程序、发邮件。而且还可以指定某一个程序在后台运行,指定某一些程序在特定的时间内运行(at命令)。
多平台:LINUX能在X86平台上运行,也能移植到其他平台。
可编程SHELL:SHELL是解释并执行命令的系统外壳程序。通过编写SHELL程序,使得系统更加个性化;而且在一些程序中具有C语言的功能。
提供源代码:LINUX是自由软件,源代码完全公开,可以自行编译内核,修改和扩充操作系统,进行二次开发。
LINUX支持的文件系统很多,例如:EXT2、NFS、VFS、ISO9660、MSDOS等等。
网络功能:较全面的实现了TCP/IP、SLIP、PPP、PLIP协议,功能强大。
1.3什么是QoS
QoS:服务质量(QoS:Quality of Service)服务质量是指网络提供更高优先服务的一种能力,包括专用带宽、抖动控制和延迟(用于实时和交互式流量情形)、丢包率的改进以及不同 WAN、LAN 和 MAN 技术下的指定网络流量等,同时确保为每种流量提供的优先权不会阻碍其它流量的进程
基于Linux的QoS编程接口研究与分析(2)
1.4 QoS在网络应用中的重要性
随着因特网的快速爆炸式发展,新兴的业务也层出不穷。因为网络正在从初期的单一的数据网络,向集成音频、视频、数据的宽带多媒体网络方向发展。而随着骨干网和接入网上带宽的不断增长,语音、传真、会议电视、视频点播等业务迅速的浮出水面,成为电信网络上新的增值业务。这些业务有一个共同的特征,对带宽要求高,对时延非常敏感。初期的因特网基于TCP/IP协议栈规划和设计,其中IP网络最典型和最精髓的一个设计思想是“Best effort”,和传统的电信网络相比,它是一种面向无连接(connectionless)尽力而为的基于包交换的一种网络,在这种机制之上很难保证用户的带宽、吞吐量和传输的时延,尽管高层的协议提供了更好的机制,但是依然无法使新兴的业务得到服务质量。在这种状况下,服务质量保证(QoS)成为因特网发展的一个重点。
传统的因特网协议栈中经典的方式是先进先出(FIFO),即不区分业务类型和对时延的敏感程度,统一按照一个队列进行处理。这种机制对于音频和视频等多媒体业务很不合适,尤其是对时延要求非常敏感的一些业务。当网络发生拥塞,队列过长时,部分数据将被丢弃或者重传,对于实时的业务而言,这样造成服务质量无法保证的局面。
针对以上的问题,出现了很多QoS技术。比较典型的是:集成服务(InterServ,intergrate Service)与RSVP(Resource Reservation Protocol)模型,区分服务(DiffServ,Differentiated Service),流量工程和约束路由,多协议标记交换(MPLS)技术。与QoS相关的参数主要包括用户传送带宽、传送时延、时延抖动、传送误码率等。比如视频传送属于调用型业务,为保证其实时性,需要很高的传输速率,占用较大的带宽。传送带宽的不足和网络业务量的拥塞往往引起传送时延和时延抖动,实现QoS的关键就是解决网络的拥塞问题。对于企业网络来说,越是简单的解决方案,越有生命力。人们公认ATM技术有完善的QoS支持、灵活的应用形式,但现在使用者很少,主要原因也就是它太复杂、太昂贵。当通过在网络中采用更高的带宽、更快的设备同样能实现服务质量要求时,没有人愿意再去进行繁复设置。
由于以太网是基于不定长数据包的技术,很难像ATM那样提供显示的QoS功能,但QoS组件为它提供了可供选择的方案。所谓QoS组件就是指诸如具有资源预留的分组或流量的优先级控制和通过技术映射业务级别等实现服务质量要求的方法。常见的QoS组件有IEEE 802.1Q/p标准、RSVP(IEFT资源预约协议)、IP TOS(服务类型)等。对于网络管理者来说,重要的是选择满足其服务质量要求的QoS组件的最佳组合。
1)灵活支持CoS/ToS/Port任选方式之一的每个端口2个优先级队列。
2)基于IEEE802.1p的优先级控制使网络能主动避免导致吞吐量下降的拥塞现象;具有主动从拥塞状态恢复正常的能力。
3)基于IP ToS的优先级支持使交换机能优先处理具有较高优先级的业务,而不加剧拥塞现象。
4)基于Port的优先级设置,使管理员能更简便的为VIP用户分配高优先级。
5)灵活的QoS保障,使得用户能在不对基础网络硬件和协议做大规模或根本性改造的前提下,充分运用已安装的网络要素和软件,提高网络容量,增加新服务,并因此保护原有投资。
拥有QOS保障,在实际网络应用中具有特殊的意义。因为在网络应用业务不断发展的情况下,需要适当扩大广域网的接入带宽,以满足需求。但是,一味扩大带宽并不能彻底解决问题,只有和QoS结合起来,才能取得事半功倍的效果,并为企业节约投资。通过采用QoS策略、千兆以太网技术智能型交换机,可以解决应用可用性的基本问题,这样,通过区分应用优先级、灵活分配带宽,因此可以有效地利用网络资源,并建立智能网络以提高网络可用性,从而实现QoS策略,保证业务平稳运行。
当前有三种典型的 QoS:
尽力而为型服务(Best-effort service)――基本连通性,没有发送流量、发送速度和顺序方面的保证。
区分型服务(Differentiated service)――流量较好(处理快速、平均带宽较多、平均丢包率较低)。它属于统计性能,不同于由流量分类提供的典型保证型服务。
证型服务(Guaranteed service)――它是解决网络资源特殊流量的最佳方案。该服务通过分组网络中的QoS工具RSVP实现。传统的电话网络(PSTN)使用的线路交换能够确保链路连接和带宽分配,从而保证服务质量。
QoS技术涉及以下三个方面:
QoS识别和标志技术:主要是调整网络单元之间从终端到终端的服务质量,这是通过数据包流量分类和预留带宽完成的。识别流量的一般方法包括访问控制表(ACLs)、策略路由技术、承诺访问速率(CAR)以及基于网络应用的识别(NBAR)。
单一网络单元中的 QoS :包括拥塞控制、队列管理、链接效率等技术和分层/流量监管工具。
QoS 策略、管理和计费功能:主要控制和管理终端到终端的网络流量,包括配置网络设备如RMON探测器。当获取网络流量及目标应用程序时,需要采用 QoS 技术来提高服务质量。通过测试目标应用程序的响应可以知道该过程是否达到 QoS 标准。
1.5 小结
Linux操作系统在短短的几年之内得到了非常迅猛的发展,这与Linux具有的良好特性是分不开的。
Linux诞生于因特网,它具有Unix血统特性保证了它支持所有标准因特网协议(事实上,Linux是第一个支持IPv6的操作系统)。由于Linux低成本、高可靠、丰富的Internet应用软件,Linux是因特网服务提供商ISP中最流行的服务器操作系统。任何Linux发行版都提供了电子邮件、文件传输、网络新闻等等服务软件。
Linux是一个优异的、标准的网页Web应用平台,您可利用它来作为路由器、防火墙、网页服务器、电子邮件服务器、数据库服务器和目录服务器来建立一个完善的、安全的因特网站点。Linux 包含复杂的带宽管理系统 TC (流量控制,Traffic Control)。该系统支持分类、优先、共享和输入、输出流量限制等。这套系统可以与专用的带宽管理系统相媲美。
目前,Linux 所提供的 QoS(服务质量)是所有操作系统中最复杂、最完善的。另外, BSD 的 ALTQ 应该说也相当不错;但是,在复杂性、灵活性和可扩展性等方面要落后 Linux 一大截。我不太清楚微软的产品是否提供了这方面的功能。Sun 的 Solaris 提供 了 CBQ 和 RSVP 的功能。
基于Linux的QoS编程接口研究与分析(3)
第二章 QoS的协议与结构
标准Internet协议(IP)的网络提供尽力而为的数据传输。这种IP网络允许客户端主机的结构复杂一些,而网络端的结构可以保持相对的简单,因为Internet要支持自身的快速发展,所以这样的结构划分是有好处的。当越来越多的主机联在一起的时候,网络服务的需求最终会超过网
络的能力,而服务却不会停止,由此产生网络性能的逐渐恶化,进而造成传输迟延的变化(抖动),甚至引起分组丢失,但不会影响常用的Internet应用(如,电子邮件,文件传输和Web应用),而其它应用就不能适应有迟延的服务。传输迟延对有实时要求的应用(例如传送多媒体信息)及大多数双向通信(如电话)带来了问题。
增加带宽是满足这些实时应用必要的第一步。但是当业务量猛增的时候,仍难以避免传输迟延。甚至在一个负载相对较轻的IP网络上,传输迟延也能累积到影响实时应用的程度。为了提供能够满足要求的服务,必须补充制订有关服务数量、或服务质量水平的规定。规定中需要在网络方面增加一些协议,来区分具有严格定时要求的业务和能够容忍迟延、抖动和分组丢失的业务。这就是服务质量(QoS)协议要做的事情。QoS不是创造带宽,而是管理带宽,因此它能应用得更为广泛,能满足更多的应用需求。QoS的目标是要提供一些可预测性的质量级别,以及控制超过目前IP网络最大服务能力的的服务。
QoS协议要满足各种应用的需要。这篇文章将对这些协议做详尽的描述,然后说明它们在遵循端到端原则的不同的网络结构里是如何相互配合工作的。IPQoS技术所面临的挑战是要能把传输的服务从不同的数据流中区分开,或者在不中断网络工作的情况下将数据聚合起来。尽力而为服务的改进表明让Internet发挥其最大作用的设计思路发生了根本变化。
为了避免QoS协议用于网络时出现问题,端到端原则仍然是QoS设计者首要遵循的原则。所以,保持网络边缘复杂、核心简单这一基本原则是QoS结构设计的中心问题。这不是指单个QoS协议,而是指这些协议如何相互配合来实现端到端的QoS。
2.1 QoS协议
描述QoS的方法不只一种。通常情况下,QoS是一个网络部件(如一个应用、一台主机或一个路由器)提供的一些保证稳定传输网络数据的质量级别。某些应用对QoS的要求比其它应用更高,因此有下面两种基本的QoS:
资源预留(适于综合服务):根据某个应用对QoS的要求来分配网络资源,并且服从带宽管理策略
按优先级排列(适于差分服务):对网络的业务进行分类,并根据带宽管理策略的规范来分配网络资源。当所标识的业务类别有更强烈服务要求的需求时,网络部件会给予优先处理。
有两种其它方法描述QoS类型:
“流”型QoS:把一个'流'定义为一个信源和信宿之间、单个、单向、由传输协议唯一标识的数据流,其中包括数据流的源地址、源端口号、目的地址以及目的端口号
“聚合”型QoS:一个聚合是简单的两个或更多的“流”。最为突出的是,这些“流”会有相同的标记或者优先级号码,也许还有一些相同的认证信息。
应用、网络拓扑结构和策略决定了哪一种QoS最适合个别“流”,或者“流聚合”。
为满足对QoS不同的需要,有以下几种QoS协议和算法:
资源预留协议(RSVP):提供网络资源预留的信令。尽管RSVP经常用于单个流,但也用于聚合流的资源预留
差分服务(DiffServ):提供一个简单的分类和网络聚合流的优先级
多协议标记交换(MPLS):根据分组头的标记,通过网络路径控制来提供聚合流的带宽管理
子网带宽管理(SBM):负责OSI第二层(数据链路层)的分类和优先级排列,同IEEE802网络进行共享和交换。
2.1.1 RSVP-资源预留
RSVP是一个信令协议,它提供建立连接的资源预留,控制综合业务,往往在IP网络上提供仿真电路。RSVP是所有QoS技术中最复杂的一种,与尽力而为的IP服务标准差别最大,它能提供最高的QoS等级,使得服务得到保障、资源分配量化,服务质量的细微变化能反馈给支持QoS的应用和用户。
1)协议的工作情况
发送端依据高、低带宽的范围、传输迟延,以及抖动来表征发送业务。RSVP从含有“业务类别(TSpec)”信息的发送端发送一个路径信息给目的地址(单点广播或多点广播的接收端)。每一个支持RSVP的路由器沿着下行路由建立一个“路径状态表”,其中包括路径信息里先前的源地址(例如,朝着发送端的上行的下一跳)
为了获得资源预留,接收端发送一个上行的RESV(预留请求)消息。除了TSpec,RESV消息里有“请求类别(RSpec)”,表明所要求的综合服务类型,还有一个'过滤器类别',表征正在为分组预留资源(如传输协议和端口号)。RSpec和过滤器类别合起来代表一个'流的描述符',路由器就是靠它来识别每一个预留资源的
当每个支持RSVP的路由器沿着上行路径接收RESV的消息时,它采用输入控制过程证实请求,并且配置所需的资源。如果这个请求得不到满足(可能由于资源短缺或未通过认证),路由器向接收端返回一个错误消息。如果这个消息被接受,路由器就发送上行RESV到下一个路由器
当最后一个路由器接收RESV,同时接受请求的时候,它再发送一个证实消息给接收端
当发送端或接收端结束了一个RSVP会话时,有一个明显的断开连接的过程。
基于Linux的QoS编程接口研究与分析(4)
2) RSVP支持的综合业务的两种基本类型
有保证业务:这种业务是,尽可能地仿真成一条专用虚电路。除了要根据TSpec参数的要求确保带宽的有效性外,它还可以用把一条路径里的不同网络部件的参数合并起来的方法来提供一个端到端的固定的队列延迟
受控负载:这相当于“无负载条件下尽力而为服务”。因此,它比“尽力而为”服务更好,但是不能提供“有保证业务”所承诺的,具有严格固定队列延迟的服务。
对于“有保证业务”和受控负载,处理不同的(与类别无关)数据业务就象处理没有QoS的尽力而为数据业务那样。综合业务采用令牌筐模式来表征输入/输出排序算法。设计令牌筐是为了平滑输出的业务流,但不象泄露筐模式(也可以平滑输出的业务流),令牌筐模式允许数据突发、在短时间内维持更高的发送速率。
3) RSVP协议机制要点
每个路由器的预留资源是'软'的,即这些资源需要由接收端定期地刷新
RSVP不是传输协议,而是网络(控制)协议。作为这样的协议,它不传送数据,但是和TCP或者UDP的数据'流'是并行工作的
应用要求API详细说明数据流的需求,初始化预留资源请求,并且在发出初始化请求后,接收预留成功或失败的通知并贯穿于整个会话过程。为了更好地利用API,API也要包含那些描述在整个预留时间内的预留建立期间或之后,当条件发生变化时出现问题的RSVP错误信息
根据接收端的情况来预留资源,是为了有效的接纳相当复杂的(组播)接收端组
在上行方向的业务复制点处组播预留资源混合在一起(仍然有不易理解的复杂算法在里面)
尽管RSVP业务可以通过不支持RSVP的路由器,但是这会在QoS'链'上产生一条'弱链路',于是,QoS'链'的服务质量降回到'尽力而为'的水平(即在这些链路上没有预留资源)
两种RSVP协议:一是纯RSVP,包含IP的46号协议(用于IP分组头的协议区),RSVP的分组头和有效负载封装在IP分组头里。封装在UDP里的RSVP把它的分组头放在UDP数据报里。下文将描述只支持纯RSVP的802协议,即'子网带宽管理'。
上面提到RSVP提供最高的IPQoS等级。应用可以请求高量化程度的QoS,以及具有最佳传输质量保证的QoS。这听上去似乎万无一失,可让我们感觉疑惑的是,为什么我们还要考虑其它问题。这是由于RSVP协议存在着复杂性和开销的价格问题,因而许多应用和网络的一些组成部分并不采用它。简单地说,RSVP缺少微调的方法,而DiffServ却可以提供这种方法。
2.1.2 DiffServ-优先级排列
差分服务提供一种简单粗略的方法对各种服务加以分类。不过用其它方法也可以,目前有两个每跳(PHBs)的标准,其中对两个最有代表性的服务等级(业务类别)作了规定:
快速转发(EF):有一个单独的码点(DiffServ值)。EF可以把延迟和抖动减到最小,因而能提供总合服务质量的最高等级。任何超过服务范围(由本地服务策略决定)的业务被删除
保证转发(AF):有四个等级,每个等级有三个下降过程(总共有12个码点)。超过AF范围的业务不会象'业务范围内'的业务那样以尽可能高的概率传送出去。这意味着业务量有可能下降,但不是绝对的。
根据预定策略的标准,PHBs适用于网络入口的业务。业务在这点加以标记,然后根据这个标记进行路由指向,没有作标记的业务就放到了网络的出口。
DiffServ假定共享同一个网络边界的网络之间存在着服务等级协定(SLA)。SLA确定了策略标准和业务范围。按照SLA协议,业务会在网络出口接受监督,并得到平滑。任何在网络入口的超出范围的业务没有质量保证。(否则,按照SLA,要承担额外的成本。)
服务采用的协议机制在DS字节里是比特形式的,对IPv4是业务类别(TOS)的八位位组,对IPv6则是业务量类别的八位位组。
DiffServ对业务量优化的单一性同它本身的复杂性及强大的功能形成对比。当DiffServ利用RSVP的参数或特殊应用类别来标识和划分固定比特率(CBR)业务时,会形成具有严格定义的综合业务流,并直接指向具有固定带宽的通道。这样一来,资源库就能得到有效地共享,而且仍然可以提供可靠服务。
2.1.3 MPLS-标记交换
多协议标记交换在某些方面类似与DiffServ,因为它也在网络入口的边界处标记业务,而在出口没有标记。但与DiffServ(里的标记用于判别在路由器中的优先级)不同,MPLS的标记(20比特长的标签)是用于判别路由器的下一跳的。MPLS不是受控制的应用(它没有MPLS的API),也不含最终主机协议的成份。MPLS不象这里所描述的其它QoS协议,它只存在于路由器上。MPLS也独立于协议(即多协议),所以它可以和其它网络协议一同使用,而不仅仅是IP(象IPX,ATM,PPP,或帧中继),还能直接用在数据链路层上。
MPLS更多的是一个业务量工程协议,而不只是一个QoS协议。MPLS路由是为建立固定带宽通道,类似于ATM或帧中继的虚电路。作用是服务得到了改善,增加了更为灵活的服务种类,还有基于策略的网络管理控制。以上这些功能其它QoS协议也可提供。
MPLS简化了路由过程(减小开销,提高性能),同时增加了协议层迂回的灵活性。支持MPLS的路由器叫做标记交换路由器(LSR),它们如下工作:
在MPLS网络中第一跳的路由器上,该路由器根据目的地址(或根据本地策略所规定的报头中的其它信息)做出转发的决定,接着判别合适的标签值(标识着平衡等级转发类别-FEC)并把它贴在分组上,再转发给下一跳
在下一跳,路由器把这个标签值作为一个索引放入一张表里,这张表指明了下一跳以及一张新表。LSR再贴上新标签,把分组转发到下一跳。
标有MPLS分组的路径叫做标记交换路径(LSP)。在有了MPLS之后的一个想法是通过采用一个标签来判定下一跳,路由器的工作量会少一些并且能处理更多的简单交换。这个标签表示一条路由,再利用策略来分配标签,网络管理者能更精确地控制业务量工程。
标签的处理过程实际上要比上面所描述的复杂得多,因为标签能够被堆在一起(为的是MPLS可以在路由之中包含路由。另外,MPLS更为复杂的地方在于,为了确保各种标签含义的一致性,还要负责MPLS路由器之间标签的分布和管理。标签分布协议(LDP)是专为这一目的设计的,但不只有这一种可能性。
即使象标签分布这样的网络结构细节被强调再三,但对于大多数网络管理者来说,这些将是透明的。大多数网络管理者们更为关心的是策略管理,即判别何种标签用于何地,以及如何分布标签。
基于Linux的QoS编程接口研究与分析(5)
2.2 SBM:子网带宽管理
QoS只能保证和最弱的链路一样的通信质量。QoS懥磼是发送端和接收端间的端到端,这就表明沿着路由的每一个路由器一定要支持现在使用的QoS技术。然而,QoS懥磼由顶至底也是要从下面两个方面认真考虑的:
发送端和接收端主机必须支持QoS,使得应用和系统能获得明显或不明显的好处。OSI的每一层向下的应用必须也要支持QoS,以保证在网络里具有高优先级别的发送和接收请求能获得高优先级别的处理
局域网(LAN)必须支持QoS,以便具有高优先级别的帧在网络媒介中传送(如:从主机到主机,主机到路由器,以及路由器到路由器之间)时可以获得高优先级别的处理。LAN位于OSI的第二层,即数据链路层,而前面所描述的QoS技术已经到了第三层(DiffServ)及以上层。
某些第二层的技术已经可以支持QoS了,例如异步转移模式ATM)。而其它更多的LAN技术(如以太网技术)最初并非为支持QoS设计的。以太网作为共享的广播媒介,或者,在它的交换方式中,提供了一种类似与标准的尽力而为的IP服务,这种服务中的各种迟延影响着有实时要求的应用。用于802LAN(如以太网)资源共享和交换的子网带宽管理(SBM)协议是一种信令协议,它允许网络节点之间的通信、协作,以及交换并使之能够映射到更高层的QoS协议。
2.3 QoS结构
这些QoS协议是不可能单独使用的,实际上,设计它们是为了在发送端和接收端之间同其它QoS技术一起,来提供顶到底、端到端的QoS。至今,大多数把这些QoS协议粘在一起的规范还没有标准化,但是搭建各种尽可能提供统一的端到端QoS结构框架的工作已经开始进行了。
RSVP和DiffServ的端到端模式:RSVP为网络业务预留资源,而DiffServ简单地标记业务并给业务分配优先等级。从路由器的要求来讲,RSVP比DiffServ更复杂,要求更高,由此可能会对骨干路由器的性能产生不良影响。这就是为什么最普通的方法恰恰会限制RSVP在骨干网上的应用。
DiffServ和RSVP结合能够支持端到端的QoS。终端主机可以采用高量化程度(如:带宽,抖动门限等)的RSVP请求。于是,骨干网入口的边界路由器就能把那些RSVP预留的资源映射到相应的服务级别上去。这个在网络边界处使用RSVP、核心处使用DiffServ的概念已经在IETF的DiffServ小组的工作进展中很快得到了支持,虽然最初的测试没有显示出明显的结果:
支持RSVP的MPLS:建议在RSVP里使用EXPLICIT_ROUTE对象,来判别由标记交换的RSVP流所携带的路径信息。这些RSVP流是利用虚拟通道,经过支持MPLS的路由器形成的。即使在RSVP内没有为EXPLICIT_ROUTE对象预留资源,根据这个RSVP流的说明,为MPLS分配标签也是可能的。无论哪种情况,作用都是在MPLS路由器上简单地支持RSVP
支持DiffServ的MPLS:由于DiffServ和MPLS在支持QoS方面有相似之处,把DiffServ的业务映射到MPLS'通道'上相对简单一些,但是仍然要专门为DiffServ考虑。为了支持DiffServ的'每跳'模式,MPLS网络运营商需要在每个MPLS路由器里,为每个DiffServ转发级分配一批综合转发资源并负责标签的分配。
2.4 QoS对多媒体的支持
如果Internet继续迅猛发展下去,那么IP组播就成为一个要求,而不是一种选择。它对于QoS在Internet上支持点对多点的音频和视频组播是一种自然地补充,所以支持组播已经是设计QoS协议的基本要求了。
尽管在QoS协议设计的最初已经有所考虑,但是,所有支持组播的QoS仍没有统一标准,或还没有完全被理解。最初对RSVP和综合服务的设计已经把对IP组播的支持考虑进去了(如,基于接收端的资源预留)。而要支持组播就会面临一个困难,那就是,构成组播组的接收端要可以在它们的下行带宽容量范围内较随意地变化。
差分业务的相对简单使得它能更好地适应组播支持,但仍存在问题。特别是由于组播组是动态变化的,另外尽管一个组播分布树可以有一个单独网络入口,但经常有多个网络出口(并随着组播组的变化而变化),所以给业务量的估算带来困难。
MPLS支持组播是一个大家努力快速发展的主题,但至今还没有标准。SBM显然支持组播,并且采用IP组播作为协议的组成部分。
基于Linux的QoS编程接口研究与分析(6)
第三章 Linux网络协议栈QoS模块(TC)的设计与实现
本文描述了linux 2.4.x内核中对QoS支持的设计与实现,并且对缺省的数据包调度机制PFIFO进行了详细的分析。
在传统的TCP/IP网络的路由器中,所有的IP数据包的传输都是采用FIFO(先进先出),尽最大努力传输的处理机制。在早期网络数据量和关键业务数据不多的时候,并没有体现出非常大的缺点,路由器简单的把数据报丢弃来处理拥塞。但是随着计算机网络的发展, 数据量的急剧增长,以及多媒体,VOIP数据等对延时要求高的应用的增加。路由器简单丢弃数据包的处理方法已经不再适合当前的网络。单纯的增加网络带宽也不能从根本上解决问题。所以网络的开发者们提出了服务质量的概念。概括的说:就是针对各种不同需求,提供不同服务质量的网络服务功能。提供QoS能力将是对未来IP网络的基本要求。
.Linux内核对QoS的支持
Linux内核网络协议栈从2.2.x开始,就实现了对服务质量的支持模块。具体的代码位于net/sched/目录。在Linux里面,对这个功能模块的称呼是Traffic Control ,简称TC。
首先我们了解一下Linux网络协议栈在没有TC模块时发送数据包的大致流程。如图1。
注:上图的分层是按照Linux实现来画,并没有严格遵守OSI分层
从上图可以看出,没有TC的情况下,每个数据包的发送都会调用dev_queue_xmit,然后判断是否需要向AF_PACKET协议支持体传递数据包内容,最后直接调用网卡驱动注册的发送函数把数据包发送出去。发送数据包的机制就是本文开始讲到的FIFO机制。一旦出现拥塞,协议栈只是尽自己最大的努力去调用网卡发送函数。所以这种传统的处理方法存在着很大的弊端。
为了支持QoS,Linux的设计者在发送数据包的代码中加入了TC模块。从而可以对数据包进行分类,管理,检测拥塞和处理拥塞。为了避免和以前的代码冲突,并且让用户可以选择是否使用TC。内核开发者在上图中的两个红色圆圈之间添加了TC模块。(实际上在TC模块中,发送数据包也实现对AF_PACKET协议的支持,本文为了描述方便,把两个地方的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_nit)
dev_queue_xmit_nit(skb,dev);
/*对AF_PACKET协议的支持*/
if (dev->hard_start_xmit(skb, dev) == 0) {
/*调用网卡驱动发送函数发送数据包*/
return 0;
}
}
………………
}
从上面的代码中可以看出,当q->enqueue为假的时候,就不采用TC处理,而是直接发送这个数据包。如果为真,则对这个数据包进行QoS处理。
回页首
.TC的具体设计与实现
第一节描述了linux内核是如何对QoS进行支持的,以及是如何在以前的代码基础上添加了tc模块。本节将对TC的设计和实现进行详细的描述。
QoS有很多的拥塞处理机制,如FIFO Queueing(先入先出队列),PQ(优先队列),CQ(定制队列),WFQ(加权公平队列)等等。QoS还要求能够对每个接口分别采用不同的拥塞处理。为了能够实现上述功能,Linux采用了基于对象的实现方法。
上图是一个数据发送队列管理机制的模型图。其中的QoS策略可以是各种不同的拥塞处理机制。我们可以把这一种策略看成是一个类,策略类。在实现中,这个类有很多的实例对象,策略对象。使用者可以分别采用不同的对象来管理数据包。策略类有很多的方法。如入队列(enqueue),出队列(dequeue),重新入队列(requeue),初始化(init),撤销(destroy)等方法。在Linux中,用Qdisc_ops结构体来代表上面描述的策略类。
前面提到,每个设备可以采用不同的策略对象。所以在设备和对象之间需要有一个桥梁,使设备和设备采用的对象相关。在Linux中,起到桥梁作用的是Qdisc结构体。
通过上面的描述,整个TC的架构也就出来了。如下图:
加上TC之后,发送数据包的流程应该是这样的:
(1) 上层协议开始发送数据包
(2) 获得当前设备所采用的策略对象
(3) 调用此对象的enqueue方法把数据包压入队列
(4) 调用此对象的dequeue方法从队列中取出数据包
(5) 调用网卡驱动的发送函数发送
接下来从代码上来分析TC是如何对每个设备安装策略对象的。
在网卡注册的时候,都会调用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 = &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,
};
从noop_enqueue,noop_dequeue,noop_requeue函数的定义可以看出,他们并没有对数据包进行任何的分类或者排队,而是直接释放掉skb。所以此时网卡设备还不能发送任何数据包。必须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, &pfifo_fast_ops);
/*安装缺省的qdisc*/
}
……………
if ((dev->qdisc = dev->qdisc_sleeping) != &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;
/* 首先删除掉旧的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);
…………………
}
从dev_graft_qdisc可以看出,如果需要使用新的Qdisc,那么首先需要删除旧的,然后安装新的,使dev->qdisc_sleeping 为新的qdisc,然后调用dev_activate函数来启动新的qdisc。结合dev_activate函数中的语句:
if ((dev->qdisc = dev->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(早期随即检测队列)模块,就调用register_qdisc来安装RED对象(net/sched/sch_red.c->init_module())。
回页首
缺省策略对象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 * sizeof(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; i<3; i++)
skb_queue_head_init(list+i);          /*初始化3个优先级队列*/
……….
}
init函数的作用就是初始化3个队列。
在注销一个Qdisc的时候都会调用Qdisc的ops的reset函数。见dev_graft_qdisc函数。
static void
pfifo_fast_reset(struct Qdisc* qdisc)
{
…………..
for (prio=0; prio < 3; prio++)
skb_queue_purge(list+prio);           /*释放3个优先级队列中的所有数据包*/
…………..
}
在数据包发送的时候会调用Qdisc->enqueue函数(在qdisc_create_dflt函数中已经将Qdisc_ops的enqueue,dequeue,requeue函数分别赋值于Qdisc分别对应的函数指针)。
int dev_queue_xmit(struct sk_buff *skb)
{
……………….
q = dev->qdisc;
if (q->enqueue) {
/* 对应于pfifo_fast_enqueue 函数*/
int ret = q->enqueue(skb, q);
/*启动这个设备的发送,这里涉及到两个函数pfifo_fast_dequeue ,pfifo_fast_requeue 稍后介绍*/
qdisc_run(dev);
return;
}
……………
}
入队列函数pfifo_fast_enqueue:
static int
pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc)
{
…………..
list = ((struct sk_buff_head*)qdisc->data) +
prio2band[skb->priority&TC_PRIO_MAX];
/*首先确定这个数据包的优先级,决定放入的队列*/
if (list->qlen <= skb->dev->tx_queue_len) {
__skb_queue_tail(list, skb);   /*将数据包放入队列的尾部*/
qdisc->q.qlen++;
return 0;
}
……………..
}
在数据包放入队列之后,调用qdisc_run来发送数据包。
static inline void qdisc_run(struct net_device *dev)
{
while (!netif_queue_stopped(dev) &&
qdisc_restart(dev)<0)
/* NOTHING */;
}
在qdisc_restart函数中,首先从队列中取出一个数据包(调用函数pfifo_fast_dequeue)。然后调用网卡驱动的发送函数(dev->hard_start_xmit)发送数据包,如果发送失败,则需要将这个数据包重新压入队列(pfifo_fast_requeue),然后启动协议栈的发送软中断进行再次的发送。
static struct sk_buff *
pfifo_fast_dequeue(struct Qdisc* qdisc)
{
…………..
for (prio = 0; prio < 3; prio++, list++) {
skb = __skb_dequeue(list);
if (skb) {
qdisc->q.qlen--;
return skb;
}
}
……………….
}
从dequeue函数中可以看出,pfifo的策略是:从高优先级队列中取出数据包,只有高优先级的队列为空,才会对下一优先级的队列进行处理。
requeue函数重新将数据包压入相应优先级队列的头部。
static int
pfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc)
{
struct sk_buff_head *list;
list = ((struct sk_buff_head*)qdisc->data) +
prio2band[skb->priority&TC_PRIO_MAX];
/*确定相应优先级的队列*/
__skb_queue_head(list, skb);/*将数据包压入队列的头部*/
qdisc->q.qlen++;
return 0;
}
回页首
QoS是当前一个非常热门的话题,几乎所有高端的网络设备都支持QoS功能,并且这个功能也是当前网络设备之间竞争的一个关键技术。Linux为了在在高端服务器能够占有一席之地,从2.2.x内核开始就支持了QoS。本文在linux 2.4.0的代码基础上对Linux如何支持QoS进行了分析。并且分析了Linux内核的缺省队列处理方法PFIFO的实现。
基于Linux的QoS编程接口研究与分析(7)
4.1 QoS 数据通道
全局的数据通道的略图见图4.1,灰色部分是 QoS:
图 4.1 网络数据加工
数据包的处理过程是:Input Interface → Ingress Policing → Input Demultiplexing (判断分组是本地使用还是转发) → Forwarding → Output Queuing → Output Interface。入口的流量限制(Ingress Policing)和出口 的队列调度(Output Queuing)是由 Linux 核心的流量控制的代码实现的。入口 的流量限制(Ingress Policing)丢弃不符合规定的分组,确保进入的各个业务 流分组速率的上限,出口的队列调度(Output Queuing)依据配置实现分组的排 队、丢弃。
以下是分析Linux 2.4 的代码得出的数据通道,根据在图4.1中的位置,分为 Ingress policing的数据通道和 Output Queuing 的数据通路,一下分别阐述:
4.1.1 Ingress policing 的数据通道
Ingress policing 的 QoS 的部分是通过 HOOK 实现的。HOOK 是 Linux 实现 netfilter 的重要手段,数据从接收到转发得通路上有5个HOOK点①,每个 HOOK点有若干个挂钩处理函数:
(typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *)); )。Ingress policing 用 的 是 NF_IP_PRE_ROUTING 这个 HOOK 点,其挂钩处理函数用的是 net/sched/sch_ingress.c ing_hook()。Ingress policing 的数据通道的略图,灰色部分是 QoS,黑色部分是 HOOK 的处理过程②:
图 4.2 Ingress policing 的总概图
1其中,HOOK 点NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),源地址转换在此点进行;NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT 包过滤在此点进行; NF_IP_FORWARD:要转发的包通过此检测点,FORWORD 包过滤在此点进行;NF_IP_LOCAL_OUT:本机进程发出 的包通过此检测点,OUTPUT 包过滤在此点进行;NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的 包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行
2以下数据通路是以 ipv4 为例,ipv6 类似
图 4.3 Ingress policing 的数据通道的略图
netif_rx()-> 数据接收队列队列-> Bottom Half③ 程序中通过软中断调用 net_rx_action()将硬件层得到的数据传输到IP层->
ip_rcv() #net/ipv4/ip_input.c 丢弃校验和不正确的ip包->
nf_hook_slow()#net/core/netfilter.c->
nf_iterate()#net/core/netfilter.c->
ing_hook() ④#net/sched/sch_ingress.c->
QoS:如果设置了 qdisc_ingress,则调用 ingress_dequeue()⑤,此处可以对流量进行限制#net/sched/sch_ingress.c->
ip_rcv_finish()#net/ipv4/ip_input.c(sch_ingress.c 的 enqueue()有限流制作用,然而 dequeue() 却是空函数。)->
以下路由:ip_route_input() #net/ipv4/route.c->
如 果 转 发 ip_route_input_slow() #net/ipv4/route.c , 如 果 本 地 处 理 ip_route_input_mc() #net/ipv4/route.c
3 为了避免处理长中断服务程序的时候,关中时间过长,Linux 将中断服务程序一分为二,各称作 top half和 bottom half,前者读取来自设备的数据,保存到预定的缓冲区(队列),然后通知 bottom half 在适当的时候完成。
4 Ingress 模块初始化时,把 ing_hook()注册为挂钩(HOOK)处理函数
5 Qdisc_ingress 不是一个队列,它只有 ingress_enqueue(),而 ingress_dequeue()只是一个空函数。把ingress_enqueue()理解为一个限流阀门更准确。
4.1.2 Output Queuing 的数据通路
Output Queuing 的数据通路略图, 灰色部分是 QoS:
图4.4 Output Queuing 的数据通路略图
这个部分描述转发数据的数据通路,位于路由之后:
ip_forward() #net/ipv4/ip_forward.c->
ip_forward_finish() #net/ipv4/ip_forward.c->
ip_send()#include/net/ip.h->ip_finish_output()#net/ipv4/ip_output.c->
ip_finish_output2() #net/ipv4/ip_output.c->
neigh_resolve_output()orneigh_connected_output()#net/core/neighbour.c->
dev_queue_xmit() #net/core/dev.c->
QoS:如果有排队方式,那么skb⑥先进入排队 q->enqueue(skb,q),然后运行qdisc_run()
#include/net/pkt_sched.h:
while (!netif_queue_stopped(dev) && qdisc_restart(dev)<0);
->
qdisc_restart(dev)#net/sched/sch_generic.c->#linux/net/pkt_sched.h
而后, q->dequeue(q)
………………
4.2 Sched 的代码结构
在 net/sched/目录下放着 Linux 目前已经实现的用于路由转发调度的各个算法,例如 cbq、tbf 等等。 这个目录下的文件大体上可以分为三个部分:
1) sch*.c:sch_api.c、sch_generic.c 和 sch_atm.c、sch_cbq.c、 sch_csz.c、sch_dsmark.c、sch_fifo.c、 sch_gred.c、sch_htb.c、sch_ingress.c、sch_prio.c、sch_red.c、sch_sfq.c、sch_tbf.c、sch_teql.c, 前 2 个提供一些通用的函数,后面几个是模块化的程序,以注册和注销一个结构体变量 作为模块的初始化和清除。
6 Linux 中,在 INET Socket 层及其以下层次之间数据包的传递是通过 struct sk_buff{}结构完成的。
例如对于 sch_tbf.c,这个文件实现的是令牌桶算法,最后生成一个 struct Qdisc_ops 的结 构 变 量 tbf_qdisc_ops , 在 模 块 初 始 化 的 时 候 , 注 册 tbf_qdisc_ops , register_qdisc(&tbf_qdisc_ops),注册的过程其实就是加入一个链表的过程,sch_api.c 提 供这个注册的函数。以下是 sch_tbf.c 文件的一部分代码,其余的 sch*.c 的第二部分的文 件与之类似:
struct Qdisc_ops tbf_qdisc_ops =
{ NULL, NULL, "tbf",
sizeof(struct tbf_sched_data),
tbf_enqueue, tbf_dequeue,
tbf_requeue,
tbf_drop,
tbf_init,
tbf_reset,
tbf_destroy,
tbf_change,
tbf_dump, };
#ifdef MODULE
int init_module(void) {
return register_qdisc(&tbf_qdisc_ops);
}
void cleanup_module(void) {
unregister_qdisc(&tbf_qdisc_ops);
} #endif
sch*.c 的第二部分的文件定义了、、、,有 enqueue……….
2) cls*.*:cls_api.c 和 cls_fw.c、cls_route.c、cls_rsvp.c、cls_rsvp6.c、cls_tcindex.c、cls_u32.c, cls_rsvp.h。前者提供分类通用的函数,后面几个是模块化的东西,以注册和注销一个结 构体变量作为模块的初始化和清除。
3) estimator.c 和 police.c,提供一些通用的函数。
关键数据结构 struct Qdisc_ops {
struct Qdisc_ops *next;
struct Qdisc_class_ops *cl_ops;
char id[IFNAMSIZ];
int priv_size;
int (*enqueue)(struct sk_buff *, struct Qdisc *);
struct sk_buff* (*dequeue)(struct Qdisc *);
int (*requeue)(struct sk_buff *, struct Qdisc *);
int (*drop)(struct Qdisc *);
int (*init)(struct Qdisc *, struct rtattr *arg);
void (*reset)(struct Qdisc *);
void (*destroy)(struct Qdisc *);
int (*change)(struct Qdisc *, struct rtattr *arg);
int (*dump)(struct Qdisc *, struct sk_buff *);
};
4.3 iprouter2的代码结构
iproute2 是一个用户空间的程序,它的功能是解释以 tc8开头的命令,如果解释成功,把 它们通过 AF_NETLINK 的 socket 传给 Linux 的内核空间。 iproute2 的代码主要有 include、ip、lib、misc、tc 目录组成,misc 的代码量很少,并且 作用不大,此处略去。Include 是一个包含头文件的目录,这个目录下的头文件会被其他目 录下的*.c 文件所用,lib 目录定义了一些通用的函数,例如与向 linux 系统传递 tc 参数的方 法:例如 rtnl_talk9, rtnl_send10等等,此点在第 4 节中有详细的介绍。Ip 目录代码主要用于解 释路由的命令,使得流量的控制策略可以与路由挂钩。不过这不是本文想要详细讨论的。tc 目录的代码是 Tc 的最为主要的部分,解释了流量控制和整形的大部分命令。 tc 目录的代码分为四个部分,f_*.c、q_*.c、m_*.c、tc_*.c+tc*.h:
1) f_*.c,解释各种分类器(filter),与 sched/cls_*.c 相对应。
2)q_*.c,解释各种队列规范(qdisc)…..与 sched/sch_*.c 相对应。
3)m_*.c,这部分就两个文件:m_estimator.c 和 m_police.c,分别对应于 sched/estimator.c 和 sched/police.c 。
4)tc_*.c+tc.h,主控文件 tc.c,把解释任务分给 tc_qdisc.c、tc_filter.c、tc_class.c 中的函数。
以下是 tc.c/main()中的代码:
if (argc > 1) {
if (matches(argv[1], "qdisc") == 0) return do_qdisc(argc-2, argv+2);
if (matches(argv[1], "class") == 0) return do_class(argc-2, argv+2);
if (matches(argv[1], "filter") == 0) return do_filter(argc-2, argv+2);
if (matches(argv[1], "help") == 0) usage();
fprintf(stderr, "Object \"%s\" is unknown, try \"tc help\".\n", argv[1]);
exit(-1);
}
iproute2 提供给命令的详解可见[HMM+02],附一提供常用的命令。
4.4 Sched 与 iproute2 的通信:AF_NETLINK
Sched 与 iproute2 的通信,是典型的 Linux 内核模块和用户空间的进程之间的通信,这 种通信一般由 Netlink Socket 来提供这种双向的通信连接。这种连接由标准的提供给用户进 程的 socket 和提供给内核模块的 API 组成,用户空间的接口简单的说就是创建一个 family 为 AF_NETLINK 的 socket,然后使用这个 socket 进行通信,自然,用户空间的进程除了 sock_fd = socket(AF_NETLINK ,SOCK_RAW, ……); 是看不到这与其他的通信方式(例如 AF_INET)有任何的不同; 内核空间维护一张 link_rtnetlink_table 表 rtnetlinks[][]。
以下结合 iproute2 控制 Linux 的 TC 模块,通过一个例子分析控制通路,得到用户空间 发送、用户空间接收、内核发送、内核接收的一些视图。
一个命令:tc qdisc add dev eth1……
这个命令为某个网络接口 eth1 增加一个 qdisc。
命令首先在用户空间被 iproute2 分析:
1)分析 tc:main(int argc, char **argv)被调用,此函数在 tc/tc.c 中;
2)分析 tc qdisc:do_qdisc(argc-2, argv+2);被调用,此函数在 tc/tc_qdisc.c 中;
3)分析 tc qdisc add: tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1); 被调用,此函数在 tc/tc_qdisc.c 中,在这个函数中,将分析完这一行 tc 的命令,各种参数(例如 RTM_NEWQDISC) 被写到 netlink 包中,并且开始与核心通信。
在用户空间中,这个顺序为 rtnl_open rtnl_talk rtnl_close,rtnl_open 的作用是打开一个 AF_NETLINK 的 socket,rtnl_close 的作用是关闭打开的 AF_NETLINK 的 socket,
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
pid_t peer, unsigned groups,
struct nlmsghdr *answer,
int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n,
void *), void *jarg),
这个函数是 iproute2 与 linux 内核通信的一个库函数,是属于用户空间的函数。
用户空间通信前的准备:填充 netlink 包;然后把 netlink 包发送到内核空间去。详见以下代 码。
if (k[0])
addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
if (est.ewma_log)
addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
/* 通过这个函数,所有的参数都被填充进 netlink 的包中 */
………
if (rtnl_open(&rth, 0) < 0) {
fprintf(stderr, "Cannot open rtnetlink\n");
exit(1);
}
if (d[0]) {
int idx;
ll_init_map(&rth);
if ((idx = ll_name_to_index(d)) == 0) {
fprintf(stderr, "Cannot find device \"%s\"\n", d);
exit(1);
}
req.t.tcm_ifindex = idx;
}
if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
/*在此之前,已经通过 rtnl_open(&rth, 0))打开一个 socket*/
exit(2);
rtnl_close(&rth);
(rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)的发送过程包括 sendmsg 和 recvmsg,具体为:
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
unsigned groups, struct nlmsghdr *answer,
int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *),
void *jarg)
{
…………
struct sockaddr_nl nladdr;
struct iovec iov = { (void*)n, n->nlmsg_len };
char buf[8192];
struct msghdr msg = {
(void*)&nladdr, sizeof(nladdr),
&iov, 1,
NULL,0,
0,
}
……
status = sendmsg(rtnl->fd, &msg, 0);
……
iov.iov_base = buf;
while (1) {
……
status = recvmsg(rtnl->fd, &msg, 0);
……
}
}
内核模块的初始化:在 net/sched/sch_api.c 文件中的 void __init pktsched_init (void)函数 中,初始化了 link_rtnetlink_table 表,link_rtnetlink_table 是一张 struct rtnetlink_link 的表
struct rtnetlink_link {
int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};
struct rtnetlink_link 由函数指针 doit 和 dumpit 组成,这张表可以由需要执行的动作的宏定义 (例如:RTM_NEWQDISC,RTM_DELQDISC)来索引,以使得能通过这张表调动相应的 函数。内核模块从用户空间收到的就是这些索引和参数,以此调用注册在此表中的函数。
link_p = rtnetlink_links[PF_UNSPEC];
/* Setup rtnetlink links. It is made here to avoid exporting large number of public symbols. */
if (link_p) {
link_p[RTM_NEWQDISC-RTM_BASE].doit = tc_modify_qdisc;
link_p[RTM_DELQDISC-RTM_BASE].doit = tc_get_qdisc;
link_p[RTM_GETQDISC-RTM_BASE].doit = tc_get_qdisc;
link_p[RTM_GETQDISC-RTM_BASE].dumpit = tc_dump_qdisc;
link_p[RTM_NEWTCLASS-RTM_BASE].doit = tc_ctl_tclass;
link_p[RTM_DELTCLASS-RTM_BASE].doit = tc_ctl_tclass;
link_p[RTM_GETTCLASS-RTM_BASE].doit = tc_ctl_tclass;
link_p[RTM_GETTCLASS-RTM_BASE].dumpit = tc_dump_tclass;
}
下面具体分析一下这个通信的过程:
用户空间:
用户空间发送
rtnl_talk() #iproute2/lib/libnetlink.c->
sendmsg(rtnl->fd,&msg,0) #net/socket.c->
sock_sendmsg(sock, &msg_sys, total_len) #net/socket.c->
sock->ops->sendmsg(sock, msg, size, &scm) #net/socket.c,在这里,通过函数指针, 调用了 static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, int len,struct scm_cookie *scm) 这在 af_netlink.c 中定义⑦。
netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);或者 netlink_broadcast(sk, skb, dst_pid, dst_groups, GFP_KERNEL);
用户空间接收 rtnl_talk() #iproute2/lib/libnetlink.c->
while{…
status = recvmsg(…); #net/socket.c ….
}->
sock_recvmsg(sock, &msg_sys, total_len, flags); #net/socket.c->
sock->ops-> recvmsg(sock, msg, size, flags, &scm); #net/socket.c 在这里,通过函数指 针调用了 static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm),这在 af_netlink.c 中定义。
⑦net\netlink\af_netlink.c 中定义
struct proto_ops netlink_ops = {
family: PF_NETLINK,
release: netlink_release,
bind: netlink_bind,
connect: netlink_connect,
socketpair: sock_no_socketpair,
accept: sock_no_accept,
getname: netlink_getname,
poll: datagram_poll,
ioctl:sock_no_ioctl,
listen: sock_no_listen,
shutdown: sock_no_shutdown,
setsockopt: sock_no_setsockopt,
getsockopt:sock_no_getsockopt,
sendmsg: netlink_sendmsg,
recvmsg: netlink_recvmsg,
mmap: sock_no_mmap,
sendpage: sock_no_sendpage,
};
在内核中:
内核发送 例如 qdisc_notify(skb, n, clid, NULL, q);
#net/sched/sch_api.c->
rtnetlink_send(skb, pid, RTMGRP_TC, n->nlmsg_flags&NLM_F_ECHO); #net/core/rtnetlink.c->
netlink_broadcast(rtnl, skb, pid, group, GFP_KERNEL); 和
netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT); 这在 af_netlink.c 中定义。
内核接收(关于这个部分,我想还需要修改)
void __init rtnetlink_init() #net/core/rtnetlink.c->
netlink_kernel_create(NETLINK_ROUTE, rtnetlink_rcv)
#net/netlink/af_netlink.c:
{ ……
if (input)
sk->protinfo.af_netlink->data_ready = input;
……
} 在 af_netlink.c 中定义,通过函数指针调用->
static void rtnetlink_rcv(struct sock *sk, int len) #net/core/rtnetlink.c
{ ……
while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
if (rtnetlink_rcv_skb(skb)) {
if (skb->len)
skb_queue_head(&sk->receive_queue, skb);
else
kfree_skb(skb);
break;
}
kfree_skb(skb);
}
……
}
->
renetlink_rcv_skb() #net/core/rtnetlink.c->
rtnetlink_rcv_msg() #net/core/rtnetlink.c :
{

link = &(rtnetlink_links[PF_UNSPEC][type]);

Err = link->doit(skb,nlh,(void*)rta); /*此处核心调用了 link_rtnetlink_table 表中的相 应于用户命令的方法被调用*/

}
4.5 小结
第五章 QoS的未来
在下一代网络标准体系中,QoS(服务质量保证)是极为关键的内容。目前ITU-TSG13在这一方面主要围绕着三个草案建议开展工作:Y.qosar、Y.123.qos、Y.e2eqos。在最近一次会议上通过了Y.qosar。
Y.qosqr草案建议提供了在分组网络中支持QoS的框架。这一框架结构由一组QoS构建模块组成。这些模块跨越了三个逻辑层面,即控制平面、数据平面和管理平面。它们组合起来可以达到控制网络性能的目的。最终借助这些模块的组合应用可以帮助电信营运商提供综合的业务性能,从而确定出针对某种业务的客户满意度。
Y.123.qos草案建议给出了基于以太的IP接入网络的QoS架构。它规定了一个分层的参考模型,并给出了协议需求及实施方法。在Y.123.qos中针对此架构所涉及的拓扑及资源状态收集需求及信令描述也进行了规定。
Y.e2e草案建议给出了从IP网络演进成为NGN网络的一种端到端QoS架构。尤其是在核心网络中,MPLS作为一种关键技术应得以支持,从而方便地提供VPN、流量工程和QoS路由。国际电联下一步将在以下方面开展工作:进一步增强计费灵活性和商业应用模式的研究;进一步深入研究业务控制层与承载层间的接口与控制信息的交互;加强域间QoS控制的研究;进一步包含无连接业务的情况,增大协议适用范围;增加对移动性的支持。