基于Linux的QoS编程接口研究与分析7完.docx

上传人:b****6 文档编号:7258687 上传时间:2023-01-22 格式:DOCX 页数:11 大小:22.71KB
下载 相关 举报
基于Linux的QoS编程接口研究与分析7完.docx_第1页
第1页 / 共11页
基于Linux的QoS编程接口研究与分析7完.docx_第2页
第2页 / 共11页
基于Linux的QoS编程接口研究与分析7完.docx_第3页
第3页 / 共11页
基于Linux的QoS编程接口研究与分析7完.docx_第4页
第4页 / 共11页
基于Linux的QoS编程接口研究与分析7完.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

基于Linux的QoS编程接口研究与分析7完.docx

《基于Linux的QoS编程接口研究与分析7完.docx》由会员分享,可在线阅读,更多相关《基于Linux的QoS编程接口研究与分析7完.docx(11页珍藏版)》请在冰豆网上搜索。

基于Linux的QoS编程接口研究与分析7完.docx

基于Linux的QoS编程接口研究与分析7完

基于Linux的QoS编程接口研究与分析(7)[完]

第四章Linux的QoS编程接口4.1QoS数据通道

全局的数据通道的略图见图4.1,灰色部分是QoS:

图4.1网络数据加工

数据包的处理过程是:

InputInterface→IngressPolicing→InputDemultiplexing(判断分组是本地使用还是转发)→Forwarding→OutputQueuing→OutputInterface。

入口的流量限制(IngressPolicing)和出口的队列调度(OutputQueuing)是由Linux核心的流量控制的代码实现的。

入口的流量限制(IngressPolicing)丢弃不符合规定的分组,确保进入的各个业务流分组速率的上限,出口的队列调度(OutputQueuing)依据配置实现分组的排队、丢弃。

以下是分析Linux2.4的代码得出的数据通道,根据在图4.1中的位置,分为Ingresspolicing的数据通道和OutputQueuing的数据通路,一下分别阐述:

4.1.1Ingresspolicing的数据通道

Ingresspolicing的QoS的部分是通过HOOK实现的。

HOOK是Linux实现netfilter的重要手段,数据从接收到转发得通路上有5个HOOK点①,每个HOOK点有若干个挂钩处理函数:

(typedefunsignedintnf_hookfn(unsignedinthooknum,

structsk_buff**skb,

conststructnet_device*in,

conststructnet_device*out,

int(*okfn)(structsk_buff*));)。

Ingresspolicing用的是NF_IP_PRE_ROUTING这个HOOK点,其挂钩处理函数用的是net/sched/sch_ingress.cing_hook()。

Ingresspolicing的数据通道的略图,灰色部分是QoS,黑色部分是HOOK的处理过程②:

图4.2Ingresspolicing的总概图

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.3Ingresspolicing的数据通道的略图

netif_rx()->数据接收队列队列->BottomHalf③程序中通过软中断调用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将中断服务程序一分为二,各称作tophalf和bottomhalf,前者读取来自设备的数据,保存到预定的缓冲区(队列),然后通知bottomhalf在适当的时候完成。

4Ingress模块初始化时,把ing_hook()注册为挂钩(HOOK)处理函数

5Qdisc_ingress不是一个队列,它只有ingress_enqueue(),而ingress_dequeue()只是一个空函数。

把ingress_enqueue()理解为一个限流阀门更准确。

4.1.2OutputQueuing的数据通路

OutputQueuing的数据通路略图,灰色部分是QoS:

图4.4OutputQueuing的数据通路略图

这个部分描述转发数据的数据通路,位于路由之后:

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)dequeue(q)

………………4.2Sched的代码结构在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个提供一些通用的函数,后面几个是模块化的程序,以注册和注销一个结构体变量作为模块的初始化和清除。

6Linux中,在INETSocket层及其以下层次之间数据包的传递是通过structsk_buff{}结构完成的。

例如对于sch_tbf.c,这个文件实现的是令牌桶算法,最后生成一个structQdisc_ops的结构变量tbf_qdisc_ops,在模块初始化的时候,注册tbf_qdisc_ops,register_qdisc(&tbf_qdisc_ops),注册的过程其实就是加入一个链表的过程,sch_api.c提供这个注册的函数。

以下是sch_tbf.c文件的一部分代码,其余的sch*.c的第二部分的文件与之类似:

structQdisc_opstbf_qdisc_ops=

{NULL,NULL,"tbf",

sizeof(structtbf_sched_data),

tbf_enqueue,tbf_dequeue,

tbf_requeue,

tbf_drop,

tbf_init,

tbf_reset,

tbf_destroy,

tbf_change,

tbf_dump,};#ifdefMODULE

intinit_module(void){

returnregister_qdisc(&tbf_qdisc_ops);

}voidcleanup_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,提供一些通用的函数。

关键数据结构structQdisc_ops{

structQdisc_ops*next;

structQdisc_class_ops*cl_ops;

charid[IFNAMSIZ];

intpriv_size;int(*enqueue)(structsk_buff*,structQdisc*);

structsk_buff*(*dequeue)(structQdisc*);

int(*requeue)(structsk_buff*,structQdisc*);

int(*drop)(structQdisc*);int(*init)(structQdisc*,structrtattr*arg);

void(*reset)(structQdisc*);

void(*destroy)(structQdisc*);

int(*change)(structQdisc*,structrtattr*arg);int(*dump)(structQdisc*,structsk_buff*);

};4.3iprouter2的代码结构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)returndo_qdisc(argc-2,argv+2);

if(matches(argv[1],"class")==0)returndo_class(argc-2,argv+2);

if(matches(argv[1],"filter")==0)returndo_filter(argc-2,argv+2);

if(matches(argv[1],"help")==0)usage();

fprintf(stderr,"Object\"%s\"isunknown,try\"tchelp\".\n",argv[1]);

exit(-1);

}

iproute2提供给命令的详解可见[HMM+02],附一提供常用的命令。

4.4Sched与iproute2的通信:

AF_NETLINKSched与iproute2的通信,是典型的Linux内核模块和用户空间的进程之间的通信,这种通信一般由NetlinkSocket来提供这种双向的通信连接。

这种连接由标准的提供给用户进程的socket和提供给内核模块的API组成,用户空间的接口简单的说就是创建一个family为AF_NETLINK的socket,然后使用这个socket进行通信,自然,用户空间的进程除了sock_fd=socket(AF_NETLINK,SOCK_RAW,……);是看不到这与其他的通信方式(例如AF_INET)有任何的不同;内核空间维护一张link_rtnetlink_table表rtnetlinks[][]。

以下结合iproute2控制Linux的TC模块,通过一个例子分析控制通路,得到用户空间发送、用户空间接收、内核发送、内核接收的一些视图。

一个命令:

tcqdiscadddeveth1……

这个命令为某个网络接口eth1增加一个qdisc。

命令首先在用户空间被iproute2分析:

1)分析tc:

main(intargc,char**argv)被调用,此函数在tc/tc.c中;

2)分析tcqdisc:

do_qdisc(argc-2,argv+2);被调用,此函数在tc/tc_qdisc.c中;

3)分析tcqdiscadd:

tc_qdisc_modify(RTM_NEWQDISC,NLM_F_EXCL|NLM_F_CREATE,argc-1,argv+1);被调用,此函数在tc/tc_qdisc.c中,在这个函数中,将分析完这一行tc的命令,各种参数(例如RTM_NEWQDISC)被写到netlink包中,并且开始与核心通信。

在用户空间中,这个顺序为rtnl_openrtnl_talkrtnl_close,rtnl_open的作用是打开一个AF_NETLINK的socket,rtnl_close的作用是关闭打开的AF_NETLINK的socket,

intrtnl_talk(structrtnl_handle*rtnl,structnlmsghdr*n,

pid_tpeer,unsignedgroups,

structnlmsghdr*answer,

int(*junk)(structsockaddr_nl*,structnlmsghdr*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,"Cannotopenrtnetlink\n");

exit

(1);

}

if(d[0]){

intidx;ll_init_map(&rth);if((idx=ll_name_to_index(d))==0){

fprintf(stderr,"Cannotfinddevice\"%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,具体为:

intrtnl_talk(structrtnl_handle*rtnl,structnlmsghdr*n,pid_tpeer,

unsignedgroups,structnlmsghdr*answer,

int(*junk)(structsockaddr_nl*,structnlmsghdr*n,void*),

void*jarg){

…………

structsockaddr_nlnladdr;

structioveciov={(void*)n,n->nlmsg_len};

charbuf[8192];

structmsghdrmsg={

(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__initpktsched_init(void)函数中,初始化了link_rtnetlink_table表,link_rtnetlink_table是一张structrtnetlink_link的表

structrtnetlink_link{

int(*doit)(structsk_buff*,structnlmsghdr*,void*attr);

int(*dumpit)(structsk_buff*,structnetlink_callback*cb);

};

structrtnetlink_link由函数指针doit和dumpit组成,这张表可以由需要执行的动作的宏定义(例如:

RTM_NEWQDISC,RTM_DELQDISC)来索引,以使得能通过这张表调动相应的函数。

内核模块从用户空间收到的就是这些索引和参数,以此调用注册在此表中的函数。

link_p=rtnetlink_links[PF_UNSPEC];

/*Setuprtnetlinklinks.Itismadeheretoavoidexportinglargenumberofpublicsymbols.*/

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,在这里,通过函数指针,调用了staticintnetlink_sendmsg(structsocket*sock,structmsghdr*msg,intlen,structscm_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在这里,通过函数指针调用了staticintnetlink_recvmsg(structsocket*sock,struc

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1