根据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_NEWQDISCRTM_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功能实现简析相关推荐

  1. ruoyi框架默认的导出Excel功能代码简析

    ruoyi框架默认导出Excel功能 项目使用的是RuoYi Bootstrap多模块版本4.7.2,启动项目后会有默认的导出功能.包括使用ruoyi自带代码生成器,都会有导出功能的附带.接下就讲解一 ...

  2. 六款具备自动泊车功能车型简析

    新皇冠 型号:V8 4.3 Royal Saloon VIP 价格:76.47万-89.96万元 特点:皇冠装载的这套自动泊车系统简称IPA,该系统最大亮点是支持自动垂直倒车入库,首先该系统需要手动启 ...

  3. Unity下Animation资源压缩简析

    最近几天在做包体大小的优化,当然这个优化首当其冲的就是对图片压缩格式的优化,网上有很多Unty图片压缩格式的介绍,我们使用的也是常规操作,因此这里就不再赘述了,我想给大家分享的是对Animation的 ...

  4. 加密狗+AES算法在QT Windows下的加密简析例程

    目录 一. 前言 二. 相关知识 三.效果展示 四.加密解密流程 五.主要函数解析 六.源码/相关包 ​ 一. 前言 本例主要简析加密狗加密解密的过程,结合实体加密狗和AES加密算法,提供QT Win ...

  5. Windows Vista系统自带刻录功能简析

    Windows Vista系统自带刻录功能简析 我们知道Windows XP系统自带CD刻录功能,但遗憾的是功能较简陋,且无法支持DVD刻录.在微软最新推出的Windows Vista操作系统中,自带 ...

  6. 简析平衡树(三)——浅谈Splay

    前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ...

  7. 基于libmad库的MP3解码简析

    基于libmad库的MP3解码简析  MAD (libmad)是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3). ...

  8. 简析 .NET Core 构成体系

    简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...

  9. ceph存储原理_Ceph存储引擎BlueStore简析

    前文我们创建了一个单节点的Ceph集群,并且创建了2个基于BlueStore的OSD.同时,为了便于学习,这两个OSD分别基于不同的布局,也就是一个OSD是基于3中不同的存储介质(这里是模拟的,并非真 ...

最新文章

  1. 深度残差收缩网络:借助注意力机制实现特征的软阈值化
  2. 权限表管理之获取用户权限表列表数据
  3. 智能车竞赛技术报告 | 智能车视觉 -重庆大学 - 风林火山
  4. 【 Verilog HDL 】Verilog 迭代连接运算符
  5. Kail Linux渗透测试教程之免杀Payload生成工具Veil
  6. 乔布斯留给后人最宝贵的十条经验!
  7. Django整理(二) - 视图和模板的初步使用
  8. Table definition on master and slave does not match
  9. 查看linux的计划任务日志,查看计划任务日志(共5篇).docx
  10. unity shader 编辑器扩展类 ShaderGUI
  11. (一)pscc学习笔记
  12. docker的安装--基于docker1.6
  13. 浅谈如何带领好一个团队
  14. linux与pe到移动硬盘,几步把WinPE安装到移动硬盘上
  15. 黄教头第六周作业 一个基础的反射型xss
  16. “App开发者需要更新此App以在此iOS版本上正常工作 ” 解决方法
  17. 如何画出漂亮的神经网络图?
  18. 聊天宝裁员85% 罗永浩的下一个风口是电子烟
  19. 09-mumu模拟器调键盘,回车键
  20. Web API 学习笔记 - 剪切板 Clipboard API

热门文章

  1. 初中高中生用计算机,中学生如何科学的使用计算器?
  2. 中水处理设备:中水处理工艺流程的选择
  3. CQF量化金融职业指南
  4. 五年企稳上升的阿里,还能再涨吗?
  5. 一个简单的Python樱花飘落动画代码
  6. HAL库开发—基于stm32的智能小家电
  7. 2018-12-24:企业微信分享功能
  8. 关于ELF格式文件里面的调试信息解读
  9. 【Day2.2】茶卡盐湖
  10. AWVS工具太顶了,漏洞扫描工具AWVS介绍及安装教程