Liunx下Qos功能实现简析
根据OSI参考模型来分,Qos可以应用在如下两层:即上层协议(主要是应用层)与链路层以及物理层网卡发出数据处。前者是通过TC工具对上层协议数据实施Qos,原理就是首先在应用层对要处理的包或者流打上mark,然后利用TC工具多不同的流量实施不同的功能处理,如流量整形,优先级设置,调度与过滤等等,值得说明的是TC工具实质是一套中间件,功能最后均由内核去负责实现;至于后者的Qos,就是在网卡驱动处设置Qos,具体实现与TC工具类似,最后也是由内核去负责实现。
一、上层协议Qos以及TC工具原理分析:
TC是一个在上层协议处添加Qos功能的工具,原理上看,它实质是专门供用户利用内核Qos调度模块去定制Qos的中间件,本节主要是阐述TC工具是如何去队列规则的,以及内部是如何实现的。
首先需要了解的是,TC作为一个应用工具,它又是如何与内核去实现通讯的?很简单,消息机制,所借助的工具则是Netlink,而所使用的协议正是NETLINK_ROUTE,更加详细的Netlink相关的知识,请参考《linux内核与用户之间的通信方式——虚拟文件系统、ioctl以及netlink》。不过在此可以说明下TC源代码中是如何初始化rtnetlink(可以理解为专门为路由设计的netlink)socket的。
struct rtnl_handle
{int fd;struct sockaddr_nl local;struct sockaddr_nl peer;__u32 seq;__u32 dump;
};
struct rtnl_handle *rth
rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
...
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = 0;
bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local);
下面主要以TC工具对qdisc操作(包括增加,修改,取代等等)的实现。对qdisc规则解析代码是在tc_qdisc_modify函数中完成的,然后通过消息机制交给内核相关模块去处理。下面是其中一段消息初始化代码片段:
struct {struct nlmsghdr n;struct tcmsg t;char buf[TCA_BUF_MAX];} req;struct tcmsg{unsigned char tcm_family;unsigned char tcm__pad1;unsigned short tcm__pad2;int tcm_ifindex;__u32 tcm_handle;__u32 tcm_parent;__u32 tcm_info;};req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));req.n.nlmsg_flags = NLM_F_REQUEST|flags;req.n.nlmsg_type = RTM_NEWQDISC;req.t.tcm_family = AF_UNSPEC;
需要解释的是,tcmsg结构体定义了跟流量控制相关的消息内容,nlmsghdr则定义了消息头,消息头中附带了消息的类型以及标志量(主要用来区分各种不同的消息),常见的消息类型有(只是针对qdisc而言,若是class或者filter,肯定会有差别):RTM_NEWQDISC和RTM_DELQDISC;常见的标志量有:NLM_F_REQUEST,NLM_F_CREATE,NLM_F_REPLACE,NLM_F_EXCL,分别意味着该消息时一个请求类的消息,进行创建或者取代操作,若存在则不予处理。Qdisc有关的各种操作所对应的消息类型以及标志量总结如下表:
有一点值得注意的是,因为针对各种不同的调度机制,有着不一样的参数选项,如sfq所对应的参数就有quantum, perturb, limit等,而htb则有r2q, default,在TC工具中针对这些不同的调度机制,定义了不一样的解析函数。如sfq和htb中的定义如下:
struct qdisc_util htb_qdisc_util = {.id = "htb",.parse_qopt = htb_parse_opt,.print_qopt = htb_print_opt,.print_xstats = htb_print_xstats,.parse_copt = htb_parse_class_opt,.print_copt = htb_print_opt,};struct qdisc_util sfq_qdisc_util = {.id = "sfq",.parse_qopt = sfq_parse_opt,.print_qopt = sfq_print_opt,};
而在 tc_qdisc_modify函数中则是首先get_qdisc_kind去获取对应的调度机制名,然后调用跟此种调度机制对应的解析参数函数去执行,对应代码片段如下:
q = get_qdisc_kind(k);...if (q->parse_qopt(q, argc, argv, &req.n))return 1;
所有的参数均解析完成之后,接下来就是将消息发给内核(接着内核将会处理所收到的消息请求),并及时接受内核的回复消息。下面着重阐述内核在收到消息请求之后是如何进行处理的呢?首先需要明白的是,当内核接收到请求消息后,按照消息的什么内容去完成消息的处理呢?消息的类型!前面总结了tc工具在不同的规则下有着对应的消息类型,例如,add, change, replace等操作所对应的消息类型则是RTM_NEWQDISC,因此,内核在收到此种消息类型之后会调用相应的模块去进行处理。OK,这些消息处理模块全部放在了sch_api.c文件中,相关代码如下:
static int __init pktsched_init(void){register_qdisc(&pfifo_qdisc_ops);register_qdisc(&bfifo_qdisc_ops);proc_net_fops_create(&init_net, "psched", 0, &psched_fops);rtnl_register(PF_UNSPEC, RTM_NEWQDISC, tc_modify_qdisc, NULL);rtnl_register(PF_UNSPEC, RTM_DELQDISC, tc_get_qdisc, NULL);rtnl_register(PF_UNSPEC, RTM_GETQDISC, tc_get_qdisc, tc_dump_qdisc);rtnl_register(PF_UNSPEC, RTM_NEWTCLASS, tc_ctl_tclass, NULL);rtnl_register(PF_UNSPEC, RTM_DELTCLASS, tc_ctl_tclass, NULL);rtnl_register(PF_UNSPEC, RTM_GETTCLASS, tc_ctl_tclass, tc_dump_tclass);return 0;}
从上面这段代码可以看出,模块中注册了消息类型以及与处理函数的对应关系。此处以RTM_NEWQDISC消息类型为例,此时需要调用tc_modify_qdisc函数去处理。处理的基本思想是这样的:因为不同的规则可能对应着相同的消息类型(如RTM_NEWQDISC),此时就需要再通过消息的标志量做进一步的操作,最后通过调用内核中有关qdisc的API函数去完成,相关代码片段如下:
static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg){......err = nlmsg_parse(n, sizeof(*tcm), tca, TCA_MAX, NULL);if (err < 0)return err;if (clid) {.......if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {if (tcm->tcm_handle) {......if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)goto create_n_graft;......atomic_inc(&q->refcnt);goto graft;} else {if (q == NULL)goto create_n_graft;if ((n->nlmsg_flags&NLM_F_CREATE) &&(n->nlmsg_flags&NLM_F_REPLACE) &&((n->nlmsg_flags&NLM_F_EXCL) ||(tca[TCA_KIND] &&nla_strcmp(tca[TCA_KIND], q->ops->id))))goto create_n_graft;}}....../* Change qdisc parameters */......err = qdisc_change(q, tca);if (err == 0)qdisc_notify(skb, n, clid, NULL, q);return err;create_n_graft:if (!(n->nlmsg_flags&NLM_F_CREATE))return -ENOENT;if (clid == TC_H_INGRESS)q = qdisc_create(dev, &dev->rx_queue,tcm->tcm_parent, tcm->tcm_parent,tca, &err);elseq = qdisc_create(dev, netdev_get_tx_queue(dev, 0),tcm->tcm_parent, tcm->tcm_handle,tca, &err);......graft:err = qdisc_graft(dev, p, skb, n, clid, q, NULL);......return 0;}
从上面的片段中可以看出,根据不同的标志量,调用不同的API函数去完成最后的功能,如qdisc_change用于去修改原qdisc规则,修改完成之后然后调用qdisc_notify去回复响应TC,qdisc_create用于去重新创建一个新的qdisc队列规则,qdisc_graft函数用于去将qdisc移植到某个对象上去。
以上以TC工具对Qdisc操作为例简单地阐述了TC工具是如何与内核进行交互的,以及内核又是如何响应请求并作出处理的,下节将探讨在ATM设备上如何设置Qos。
二、ATM设备的Qos:
本节结合Broadcom代码分析ATM设备上的Qos是如何被设置的。在讨论此问题之前,需要明白ATM设备是如何创建的,当用户配置通过ADSL拨号方式上网时,此时将会生成一个ATM设备接口,具体的创建过程代码片段如下:
static int bcmxtmcfg_ioctl( struct inode *inode, struct file *flip,unsigned int command, unsigned long arg )
{int ret = 0;unsigned int cmdnr = _IOC_NR(command);FN_IOCTL IoctlFuncs[] = {DoInitialize, DoUninitialize, DoGetTrafficDescrTable, DoSetTrafficDescrTable, DoGetInterfaceCfg,DoSetInterfaceCfg, DoGetConnCfg, DoSetConnCfg, DoGetConnAddrs,DoGetInterfaceStatistics, DoSetInterfaceLinkInfo, DoSendOamCell,DoCreateNetworkDevice, DoDeleteNetworkDevice, DoReInitialize, DoGetBondingInfo, NULL};if( cmdnr >= 0 && cmdnr < MAX_XTMCFGDRV_IOCTL_COMMANDS &&IoctlFuncs[cmdnr] != NULL ){(*IoctlFuncs[cmdnr]) (arg);}
……
}
Bcmxtmcfg_ioctl在收到来自于用户请求需要创建一个XTM(ATM或者PTM)时,接着调用DoCreateNetworkDevice函数,最后向bcmxtmrt驱动发送创建设备的请求信息XTMRT_CMD_CREATE_DEVICE,相关代码片段如下:
int bcmxtmrt_request( XTMRT_HANDLE hDev, UINT32 ulCommand, void *pParm )
{PBCMXTMRT_DEV_CONTEXT pDevCtx = (PBCMXTMRT_DEV_CONTEXT) hDev;int nRet = 0;switch( ulCommand ){.......case XTMRT_CMD_CREATE_DEVICE:nRet = DoCreateDeviceReq( (PXTMRT_CREATE_NETWORK_DEVICE) pParm );break;.......
}
接着进入DoCreateDeviceReq接口函数去创建设备,相关代码片段如下:
static int DoCreateDeviceReq( PXTMRT_CREATE_NETWORK_DEVICE pCnd )
{......if( pGi->ulDrvState != XTMRT_UNINITIALIZED &&(dev = alloc_netdev( sizeof(BCMXTMRT_DEV_CONTEXT),pCnd->szNetworkDeviceName, ether_setup )) != NULL ){dev_alloc_name(dev, dev->name);SET_MODULE_OWNER(dev);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)pDevCtx = (PBCMXTMRT_DEV_CONTEXT) netdev_priv(dev);#elsepDevCtx = (PBCMXTMRT_DEV_CONTEXT) dev->priv;#endifmemset(pDevCtx, 0x00, sizeof(BCMXTMRT_DEV_CONTEXT));memcpy(&pDevCtx->Addr, &pCnd->ConnAddr, sizeof(XTM_ADDR));if(( pCnd->ConnAddr.ulTrafficType & TRAFFIC_TYPE_ATM_MASK ) == TRAFFIC_TYPE_ATM )pDevCtx->ulHdrType = pCnd->ulHeaderType;elsepDevCtx->ulHdrType = HT_PTM;if (pDevCtx->ulHdrType == HT_PTM) {if (pGi->bondConfig.sConfig.ptmBond == BC_PTM_BONDING_ENABLE)pDevCtx->ulTrafficType = TRAFFIC_TYPE_PTM_BONDED ;elsepDevCtx->ulTrafficType = TRAFFIC_TYPE_PTM ;}else {if (pGi->bondConfig.sConfig.atmBond == BC_ATM_BONDING_ENABLE)pDevCtx->ulTrafficType = TRAFFIC_TYPE_ATM_BONDED ;elsepDevCtx->ulTrafficType = TRAFFIC_TYPE_ATM ;}....../* format the mac id */i = strcspn(dev->name, "0123456789");if (i > 0)unit = simple_strtoul(&(dev->name[i]), (char **)NULL, 10);if (pDevCtx->ulHdrType == HT_PTM)macId = MAC_ADDRESS_PTM;elsemacId = MAC_ADDRESS_ATM;/* set unit number to bit 20-27 */macId |= ((unit & 0xff) << 20);kerSysGetMacAddress(dev->dev_addr, macId);......dev->netdev_ops = &bcmXtmRt_netdevops; //控制接口(包括设备相关的ioctl函数)
#else/* Setup the callback functions. */dev->open = bcmxtmrt_open;dev->stop = bcmxtmrt_close;dev->hard_start_xmit = (HardStartXmitFuncP) bcmxtmrt_xmit;dev->tx_timeout = bcmxtmrt_timeout;dev->set_multicast_list = NULL;dev->do_ioctl = &bcmxtmrt_ioctl;dev->poll = bcmxtmrt_poll;dev->weight = 64;dev->get_stats = bcmxtmrt_query;
#endif
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)dev->clr_stats = bcmxtmrt_clrStats;
#endifdev->watchdog_timeo = SAR_TIMEOUT;/* identify as a WAN interface to block WAN-WAN traffic */dev->priv_flags |= IFF_WANDEV;switch( pDevCtx->ulHdrType ){......nRet = register_netdev(dev);........
}
从上面这段代码可以看出,主要是完成新建设备的一些初始化工作,包括控制接口、操作回调函数等,其中最主要的就是在 register_netdev(register_netdevice)中,它是在内核中完成的,其中完成的一项工作就是队列规则的初始化,相关代码片段如下:
void dev_init_scheduler(struct net_device *dev){netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);}
从代码中可以看到初始化时给设备加载的是noop_qdisc规则,而通过此规则对应的回调函数可以看出,实质上他并没有给队列加载任何规则,只是做了释放空间的工作。以noop_enquene为例,它负责对入队列加载规则,但是在noop_enqueue函数仅仅进行了数据的释放。
struct Qdisc noop_qdisc = {.enqueue = noop_enqueue,.dequeue = noop_dequeue,.flags = TCQ_F_BUILTIN,.ops = &noop_qdisc_ops,.list = LIST_HEAD_INIT(noop_qdisc.list),.q.lock = __SPIN_LOCK_UNLOCKED(noop_qdisc.q.lock),.dev_queue = &noop_netdev_queue,
};
static int noop_enqueue(struct sk_buff *skb, struct Qdisc * qdisc)
{kfree_skb(skb);return NET_XMIT_CN;
}
OK,前面很长篇幅阐述了broadcom代码中是如何去生成一个XTM设备以及是如何去完成它的初始化的,同时也知道了对新创建的设备并没有加载任何的Qos规则,那么要想对刚创建的设备增加Qos功能,该如何去实现呢?首先在rutQos_qMgmtQueueConfig函数中完成了对QMgmtQueueObject对象的相关Qos参数的设置,之后调用devCtl_xtmSetConnCfg函数试图将所配置的参数写进ATM设备中,之后进入bcmxtmcfg_ioctl中的DoSetConnCfg函数,然后是BcmXtm_SetConnCfg函数,一次类推,最后是通过DoSetTxQueue函数完成最后的配置,整个逻辑流程如下:
ATM TC:
Rut_qos.c(rutQos_qMgmtQueueConfig-->devCtl_xtmSetConnCfg)bcmxtmcfg_ioctl-->DoSetConnCfg-->BcmXtm_SetConnCfg-->SetConnCfg-->SetCfg->CheckTransmitQueues->bcmxtmrt_request-àDoSetTxQueue
参考文献:
1 Linux 2.4.x 网络协议栈QoS模块(TC)的设计与实现(http://www.ibm.com/developerworks/cn/linux/kernel/l-qos/)
Liunx下Qos功能实现简析相关推荐
- ruoyi框架默认的导出Excel功能代码简析
ruoyi框架默认导出Excel功能 项目使用的是RuoYi Bootstrap多模块版本4.7.2,启动项目后会有默认的导出功能.包括使用ruoyi自带代码生成器,都会有导出功能的附带.接下就讲解一 ...
- 六款具备自动泊车功能车型简析
新皇冠 型号:V8 4.3 Royal Saloon VIP 价格:76.47万-89.96万元 特点:皇冠装载的这套自动泊车系统简称IPA,该系统最大亮点是支持自动垂直倒车入库,首先该系统需要手动启 ...
- Unity下Animation资源压缩简析
最近几天在做包体大小的优化,当然这个优化首当其冲的就是对图片压缩格式的优化,网上有很多Unty图片压缩格式的介绍,我们使用的也是常规操作,因此这里就不再赘述了,我想给大家分享的是对Animation的 ...
- 加密狗+AES算法在QT Windows下的加密简析例程
目录 一. 前言 二. 相关知识 三.效果展示 四.加密解密流程 五.主要函数解析 六.源码/相关包 一. 前言 本例主要简析加密狗加密解密的过程,结合实体加密狗和AES加密算法,提供QT Win ...
- Windows Vista系统自带刻录功能简析
Windows Vista系统自带刻录功能简析 我们知道Windows XP系统自带CD刻录功能,但遗憾的是功能较简陋,且无法支持DVD刻录.在微软最新推出的Windows Vista操作系统中,自带 ...
- 简析平衡树(三)——浅谈Splay
前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ...
- 基于libmad库的MP3解码简析
基于libmad库的MP3解码简析 MAD (libmad)是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3). ...
- 简析 .NET Core 构成体系
简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...
- ceph存储原理_Ceph存储引擎BlueStore简析
前文我们创建了一个单节点的Ceph集群,并且创建了2个基于BlueStore的OSD.同时,为了便于学习,这两个OSD分别基于不同的布局,也就是一个OSD是基于3中不同的存储介质(这里是模拟的,并非真 ...
最新文章
- 深度残差收缩网络:借助注意力机制实现特征的软阈值化
- 权限表管理之获取用户权限表列表数据
- 智能车竞赛技术报告 | 智能车视觉 -重庆大学 - 风林火山
- 【 Verilog HDL 】Verilog 迭代连接运算符
- Kail Linux渗透测试教程之免杀Payload生成工具Veil
- 乔布斯留给后人最宝贵的十条经验!
- Django整理(二) - 视图和模板的初步使用
- Table definition on master and slave does not match
- 查看linux的计划任务日志,查看计划任务日志(共5篇).docx
- unity shader 编辑器扩展类 ShaderGUI
- (一)pscc学习笔记
- docker的安装--基于docker1.6
- 浅谈如何带领好一个团队
- linux与pe到移动硬盘,几步把WinPE安装到移动硬盘上
- 黄教头第六周作业 一个基础的反射型xss
- “App开发者需要更新此App以在此iOS版本上正常工作 ” 解决方法
- 如何画出漂亮的神经网络图?
- 聊天宝裁员85% 罗永浩的下一个风口是电子烟
- 09-mumu模拟器调键盘,回车键
- Web API 学习笔记 - 剪切板 Clipboard API