`

Linux内核中流量控制(10)

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

5.10 TEQL("True" (or "trivial") link equalizer.)

TEQL流控方法是比较特殊的一个算法,在net/sched/sch_teql.c中定义, 使用时不需要tc提供参数, 该算法是构造一个虚拟网卡, 将物理网卡加入到这个虚拟网卡中实现多网卡的流量均衡, 这和bonding有点象, 不过这些物理网卡的都保持各自的地址, 不用相同。

5.10.1 teql操作结构定义

// teql私有数据结构
struct teql_sched_data
{
// 下一个流控节点
 struct Qdisc *next;
// 指向teql属主
 struct teql_master *m;
// 路由cache
 struct neighbour *ncache;
// 数据队列
 struct sk_buff_head q;
};

// teql网卡私有数据
struct teql_master
{
// 第一个元素必须是流控操作结构, 这样两个结构指针类型可以互换
 struct Qdisc_ops qops;
 struct net_device *dev;
// 从流控节点
 struct Qdisc *slaves;
 struct list_head master_list;
 struct net_device_stats stats;
};

一些定义:

// 下一个从流控节点
#define NEXT_SLAVE(q) (((struct teql_sched_data*)qdisc_priv(q))->next)
// 虚拟网卡链表
static LIST_HEAD(master_dev_list);
// 最大平衡器数量, 缺省为1个, 对应虚拟网卡teql0, 可在插入模块时设置该参数
static int max_equalizers = 1;
module_param(max_equalizers, int, 0);
MODULE_PARM_DESC(max_equalizers, "Max number of link equalizers");
 

5.10.1 初始化

TEQL没有象其他流控算法那样明确定义流控结构, 而是先定义虚拟网卡, 然后定义该网卡的流控算法为TEQL。

static int __init teql_init(void)
{
 int i;
 int err = -ENODEV;
// 最多建立max_equalizers个teql*虚拟网卡
 for (i = 0; i < max_equalizers; i++) {
  struct net_device *dev;
  struct teql_master *master;
// 分配teql网卡名, teql_master_setup为网卡初始化函数
  dev = alloc_netdev(sizeof(struct teql_master),
      "teql%d", teql_master_setup);
  if (!dev) {
   err = -ENOMEM;
   break;
  }
// 登记该teql网卡
  if ((err = register_netdev(dev))) {
   free_netdev(dev);
   break;
  }
// 初始化teql的流控算法信息
  master = netdev_priv(dev);
// 流控算法名称为虚拟网卡名称, 如teql0, 这和其他流控算法不同
  strlcpy(master->qops.id, dev->name, IFNAMSIZ);
// 登记该流控操作结构
  err = register_qdisc(&master->qops);
  if (err) {
// 错误时释放网卡信息
   unregister_netdev(dev);
   free_netdev(dev);
   break;
  }
// 将新建虚拟网卡添加到虚拟网卡链表
  list_add_tail(&master->master_list, &master_dev_list);
 }
 return i ? 0 : err;
}

// teql网卡初始化
static __init void teql_master_setup(struct net_device *dev)
{
// teql结构作为网卡私有数据
 struct teql_master *master = netdev_priv(dev);
// 流控操作
 struct Qdisc_ops *ops = &master->qops;
// 网卡设备回指
 master->dev = dev;
 ops->priv_size  = sizeof(struct teql_sched_data);
// 流控操作结构初始化 
 ops->enqueue = teql_enqueue;
 ops->dequeue = teql_dequeue;
 ops->requeue = teql_requeue;
 ops->init = teql_qdisc_init;
 ops->reset = teql_reset;
 ops->destroy = teql_destroy;
 ops->owner = THIS_MODULE;
// 虚拟网卡设备初始化
 dev->open  = teql_master_open;
 dev->hard_start_xmit = teql_master_xmit;
 dev->stop  = teql_master_close;
 dev->get_stats  = teql_master_stats;
 dev->change_mtu  = teql_master_mtu;
 dev->type  = ARPHRD_VOID;
 dev->mtu  = 1500;
 dev->tx_queue_len = 100;
// 缺省虚拟网卡是不响应ARP信息的
 dev->flags  = IFF_NOARP;
// 硬件头长度至少32, 也可能是48或96
 dev->hard_header_len = LL_MAX_HEADER;
 SET_MODULE_OWNER(dev);
}

// teql释放
static void __exit teql_exit(void)
{
 struct teql_master *master, *nxt;
// 遍历虚拟网卡链表
 list_for_each_entry_safe(master, nxt, &master_dev_list, master_list) {
// 从链表中断开
  list_del(&master->master_list);
// 释放流控操作
  unregister_qdisc(&master->qops);
// 从系统网卡链表断开
  unregister_netdev(master->dev);
// 释放网卡
  free_netdev(master->dev);
 }
}

5.10.3 初始化

// 初始化是将物理网卡和虚拟网卡联系起来, 但不需要其他参数
// 在使用TC定义某物理网卡的流控为teql算法时调用
// 这里的sch应该是使用teql的ops的流控
static int teql_qdisc_init(struct Qdisc *sch, struct rtattr *opt)
{
// 物理网卡
 struct net_device *dev = sch->dev;
// m->dev是虚拟网卡
 struct teql_master *m = (struct teql_master*)sch->ops;
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 如果物理网卡硬件地址长度超过虚拟网卡硬件地址长度, 失败
// 虚拟网卡硬件地址应该能容纳物理网卡硬件地址
 if (dev->hard_header_len > m->dev->hard_header_len)
  return -EINVAL;
// 物理网卡和虚拟网卡相同, 回环了
 if (m->dev == dev)
  return -ELOOP;
// teql属主
 q->m = m;
// 初始化数据包队列
 skb_queue_head_init(&q->q);
// slave链表非空情况
 if (m->slaves) {
// 虚拟网卡启动情况
  if (m->dev->flags & IFF_UP) {
// 物理网卡不是PPP的虚拟网卡也应该不是PPP的
   if ((m->dev->flags&IFF_POINTOPOINT && !(dev->flags&IFF_POINTOPOINT))
// 物理网卡不支持广播, 虚拟网卡也不应该支持广播
       || (m->dev->flags&IFF_BROADCAST && !(dev->flags&IFF_BROADCAST))
// 物理网卡不支持多播, 虚拟网卡也不应该支持多播
       || (m->dev->flags&IFF_MULTICAST && !(dev->flags&IFF_MULTICAST))
// 物理网卡的MTU应该不小于虚拟网卡的MTU
       || dev->mtu < m->dev->mtu)
    return -EINVAL;
  } else {
// 虚拟网卡没启动, 根据物理网卡属性调整虚拟网卡属性
   if (!(dev->flags&IFF_POINTOPOINT))
    m->dev->flags &= ~IFF_POINTOPOINT;
   if (!(dev->flags&IFF_BROADCAST))
    m->dev->flags &= ~IFF_BROADCAST;
   if (!(dev->flags&IFF_MULTICAST))
    m->dev->flags &= ~IFF_MULTICAST;
   if (dev->mtu < m->dev->mtu)
    m->dev->mtu = dev->mtu;
  }
// 将当前流控节点插入链表
  q->next = NEXT_SLAVE(m->slaves);
  NEXT_SLAVE(m->slaves) = sch;
 } else {
// slave链表空, 该sch作为链表头
  q->next = sch;
  m->slaves = sch;
// 初始化虚拟网卡MTU和网卡标志
  m->dev->mtu = dev->mtu;
  m->dev->flags = (m->dev->flags&~FMASK)|(dev->flags&FMASK);
 }
 return 0;
}

5.10.5 入队

static int
teql_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
// 物理网卡
 struct net_device *dev = sch->dev;
// TEQL私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 将数据包挂接到TEQL队列数据包链表末尾
 __skb_queue_tail(&q->q, skb);
// 队列长度不超过限制的情况下更新统计数据, 返回成功
 if (q->q.qlen <= dev->tx_queue_len) {
  sch->bstats.bytes += skb->len;
  sch->bstats.packets++;
  return 0;
 }
// 队列长度过大, 从链表中断开数据包, 丢包
// 应该先检查队列长度, 这样就不用进行队列挂接操作了
 __skb_unlink(skb, &q->q);
 kfree_skb(skb);
 sch->qstats.drops++;
 return NET_XMIT_DROP;
}

5.10.6 重入队

static int
teql_requeue(struct sk_buff *skb, struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(sch);
// 直接将数据挂接到链表头
 __skb_queue_head(&q->q, skb);
 sch->qstats.requeues++;
 return 0;
}
 
5.10.7 出队

static struct sk_buff *
teql_dequeue(struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
 struct sk_buff *skb;
// 数据包出队
 skb = __skb_dequeue(&dat->q);
 if (skb == NULL) {
// 如果没取到数据包的情况, 队列空
// 虚拟网卡teql*, dev和dev->qdisc->dev应该是相同的嘛
  struct net_device *m = dat->m->dev->qdisc->dev;
  if (m) {
   dat->m->slaves = sch;
// 唤醒虚拟网卡队列
   netif_wake_queue(m);
  }
 }
// 流控结构队列长度是teql队列长度和虚拟网卡队列的长度之和
// 虚拟网卡本身也带流控, 缺省是pfifo_fast, 可以修改为其他流控算法
 sch->q.qlen = dat->q.qlen + dat->m->dev->qdisc->q.qlen;
 return skb;
}
 

5.10.9 复位

static void
teql_reset(struct Qdisc* sch)
{
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
// 清除teql内部队列数据
 skb_queue_purge(&dat->q);
 sch->q.qlen = 0;
// 释放邻居路由缓存
 teql_neigh_release(xchg(&dat->ncache, NULL));
}
 
5.10.10 释放

static void
teql_destroy(struct Qdisc* sch)
{
 struct Qdisc *q, *prev;
// teql私有数据
 struct teql_sched_data *dat = qdisc_priv(sch);
 struct teql_master *master = dat->m;
// 遍历从流控节点链表查找sch
 if ((prev = master->slaves) != NULL) {
  do {
   q = NEXT_SLAVE(prev);
// 找到指定的流控节点
   if (q == sch) {
// 从链表中断开
    NEXT_SLAVE(prev) = NEXT_SLAVE(q);
    if (q == master->slaves) {
// 如果是链表头, 进行相应链表头处理
     master->slaves = NEXT_SLAVE(q);
     if (q == master->slaves) {
// 如果链表中只有这个节点了, 复位设备的流控
      master->slaves = NULL;
      spin_lock_bh(&master->dev->queue_lock);
      qdisc_reset(master->dev->qdisc);
      spin_unlock_bh(&master->dev->queue_lock);
     }
    }
// 释放队列中数据包
    skb_queue_purge(&dat->q);
// 释放邻居路由
    teql_neigh_release(xchg(&dat->ncache, NULL));
    break;
   }
    
  } while ((prev = q) != master->slaves);
 }
}
 

5.10.13 teql网卡操作

// 打开网卡
static int teql_master_open(struct net_device *dev)
{
 struct Qdisc * q;
 struct teql_master *m = netdev_priv(dev);
// mtu初始值
 int mtu = 0xFFFE;
// 初始网卡标志
 unsigned flags = IFF_NOARP|IFF_MULTICAST;
// 如果没有从流控节点, 返回失败
// 也就是必须先用tc将某物理网卡的流控设置为teql0后才能让teql0网卡up
// tc qdisc add dev eth0 root teql0
// ifconfig teql0 up
 if (m->slaves == NULL)
  return -EUNATCH;
 flags = FMASK;
 q = m->slaves;
// 遍历所有从流控节点, 也就是附着于teql0各物理网卡的流控节点
// 用于调整虚拟网卡的MTU和标志参数
 do {
// slave是物理网卡
  struct net_device *slave = q->dev;
  if (slave == NULL)
   return -EUNATCH;
// mtu不能超过物理网卡的MTU
  if (slave->mtu < mtu)
   mtu = slave->mtu;
// 物理网卡的硬件地址长度不能超过虚拟网卡的硬件地址长度
  if (slave->hard_header_len > LL_MAX_HEADER)
   return -EINVAL;
  /* If all the slaves are BROADCAST, master is BROADCAST
     If all the slaves are PtP, master is PtP
     Otherwise, master is NBMA.
   */
// 根据物理网卡标志调整虚拟网卡标志
  if (!(slave->flags&IFF_POINTOPOINT))
   flags &= ~IFF_POINTOPOINT;
  if (!(slave->flags&IFF_BROADCAST))
   flags &= ~IFF_BROADCAST;
  if (!(slave->flags&IFF_MULTICAST))
   flags &= ~IFF_MULTICAST;
 } while ((q = NEXT_SLAVE(q)) != m->slaves);
// 设置虚拟网卡的MTU和标志
 m->dev->mtu = mtu;
 m->dev->flags = (m->dev->flags&~FMASK) | flags;
// 网卡队列启动
 netif_start_queue(m->dev);
 return 0;
}
 
// 网卡关闭, 应该是ifconfig teql0 down
static int teql_master_close(struct net_device *dev)
{
// 停止网卡队列
 netif_stop_queue(dev);
 return 0;
}
// 返回虚拟网卡的统计参数值
static struct net_device_stats *teql_master_stats(struct net_device *dev)
{
// teql参数
 struct teql_master *m = netdev_priv(dev);
// 返回统计结构
 return &m->stats;
}

// 调整虚拟网卡的MTU
static int teql_master_mtu(struct net_device *dev, int new_mtu)
{
 struct teql_master *m = netdev_priv(dev);
 struct Qdisc *q;
 if (new_mtu < 68)
  return -EINVAL;
 q = m->slaves;
 if (q) {
  do {
   if (new_mtu > q->dev->mtu)
    return -EINVAL;
  } while ((q=NEXT_SLAVE(q)) != m->slaves);
 }
 dev->mtu = new_mtu;
 return 0;
}

// teql*网卡的hard_start_xmit函数, 实际的数据包发送处理
// dev为teql*网卡
// 发送是取一个物理网卡来实际发送数据
static int teql_master_xmit(struct sk_buff *skb, struct net_device *dev)
{
 struct teql_master *master = netdev_priv(dev);
 struct Qdisc *start, *q;
 int busy;
 int nores;
 int len = skb->len;
 struct sk_buff *skb_res = NULL;
// 从流控节点链表的起始节点, 也就是各物理网卡的流控节点
 start = master->slaves;
restart:
 nores = 0;
 busy = 0;
// 没有物理网卡, 丢包
 if ((q = start) == NULL)
  goto drop;
 do {
// 实际的物理网卡
  struct net_device *slave = q->dev;
  
// 如果该物理网卡的流控不是teql, 跳过, qdisc_sleeping保存有效流控
// 因为在网线拔掉后网卡的当前流控会更新为noop_disc
  if (slave->qdisc_sleeping != q)
   continue;
// 物理网卡的队列停或网卡没运行, 设置忙标志, 跳过
  if (netif_queue_stopped(slave) || ! netif_running(slave)) {
   busy = 1;
   continue;
  }
// 在该物理网卡进行邻居解析操作, ARP查询
  switch (teql_resolve(skb, skb_res, slave)) {
  case 0:
// 发送成功
   if (netif_tx_trylock(slave)) {
// 调用物理网卡的hard_start_xmit函数真正地发送数据包
    if (!netif_queue_stopped(slave) &&
        slave->hard_start_xmit(skb, slave) == 0) {
// 发送成功
     netif_tx_unlock(slave);
// 更新下一个物理网卡流控节点, 实现网卡间的流量均衡, 是轮询算法
     master->slaves = NEXT_SLAVE(q);
     netif_wake_queue(dev);
// 发送统计更新
     master->stats.tx_packets++;
     master->stats.tx_bytes += len;
     return 0;
    }
    netif_tx_unlock(slave);
   }
// 加锁失败, 设置忙标志
   if (netif_queue_stopped(dev))
    busy = 1;
   break;
  case 1:
// 该网卡发送失败, slave更新到下一个物理网卡, 下一个包将准备从下一个网卡发出
   master->slaves = NEXT_SLAVE(q);
   return 0;
  default:
// 其他情况设置nores(no result)标志为1
   nores = 1;
   break;
  }
// 解析操作失败, 恢复skb数据包为网络层次数据包, 因为上面可能已经把数据包
// push成以太包了
  __skb_pull(skb, skb->nh.raw - skb->data);
 } while ((q = NEXT_SLAVE(q)) != start);
 if (nores && skb_res == NULL) {
// 如果没结果, 而且只进行了一次, 更新skb_res为当前skb, 重新发送
  skb_res = skb;
  goto restart;
 }
 if (busy) {
// 如果网卡忙, 停止队列, 返回1
  netif_stop_queue(dev);
  return 1;
 }
// 发送失败, 丢包
 master->stats.tx_errors++;
drop:
 master->stats.tx_dropped++;
 dev_kfree_skb(skb);
 return 0;
}

// 实际调用的还是__teql_resolve
static __inline__ int
teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
{
 if (dev->hard_header == NULL ||
     skb->dst == NULL ||
     skb->dst->neighbour == NULL)
  return 0;
 return __teql_resolve(skb, skb_res, dev);
}

static int
__teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
{
// teql私有数据
 struct teql_sched_data *q = qdisc_priv(dev->qdisc);
// 网卡路由的邻居
 struct neighbour *mn = skb->dst->neighbour;
// teql结构的邻居缓存
 struct neighbour *n = q->ncache;
// 网卡邻居表为空, 返回失败
 if (mn->tbl == NULL)
  return -EINVAL;
// 如果teql的邻居表等于网卡邻居表
 if (n && n->tbl == mn->tbl &&
     memcmp(n->primary_key, mn->primary_key, mn->tbl->key_len) == 0) {
// 增加trql邻居计数
  atomic_inc(&n->refcnt);
 } else {
// 重新查询teql几个的邻居缓存
  n = __neigh_lookup_errno(mn->tbl, mn->primary_key, dev);
  if (IS_ERR(n))
   return PTR_ERR(n);
 }
 if (neigh_event_send(n, skb_res) == 0) {
// 发送成功
  int err;
  read_lock(&n->lock);
  err = dev->hard_header(skb, dev, ntohs(skb->protocol), n->ha, NULL, skb->len);
  read_unlock(&n->lock);
  if (err < 0) {
   neigh_release(n);
   return -EINVAL;
  }
// 交换teql结构的cache为新的缓存n, 释放老缓存
  teql_neigh_release(xchg(&q->ncache, n));
  return 0;
 }
// 发送失败, 释放缓存n
 neigh_release(n);
// 如果skb_res为空, 准备重新再来
 return (skb_res == NULL) ? -EAGAIN : 1;
}

...... 待续 ......
分享到:
评论

相关推荐

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    基于Linux LQL流量控制系统的研究与实现

    该方法摒弃了传统方法中所运用的TC命令解析,netlink传输,内核空间执行的3层结构,而直接在Linux内核的框架下,采用LQL库直接对内核进行操控,并改进了相关U32过滤器以对IP段的流量控制,从而实现对系统的智能流量控制。...

    Linux高级路由和流量控制

    15.8. 终极的流量控制:低延迟,高速上/下载 98 15.8.1. 为什么缺省设置不让人满意 99 15.8.2. 实际的脚本(CBQ) 100 15.8.3. 实际的脚本(HTB) 102 15.9. 为单个主机或子网限速 103 15.10. 一个完全NAT和QOS的范例...

    编写Linuxc操作系统设备驱动程序概述

    在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。Linux的网络系统主要是基于BSD unix的socket机制 。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据...

    xt_fset:扩展到Linux内核netfilter子系统(iptables)(插件),使您可以通过发送ICMP包远程操作linux内核ipset(向ipset中添加或删除一些ip地址)。

    xt_fset是linux内核netfilter子系统的内核模块和iptables扩展(插件),允许您通过发送控制ICMP数据包来远程操作linux内核ipset(在ipset中添加或删除一些ip地址)。 该插件的创建是对Linux内核netfilter子系统的...

    Linux Kernel v4.19.1 Stable.zip

    Linux Kernel主要新特性包括:合并了来自Android项目的内核代码,支持新的架构TI C6X,改进了Btrfs...网络优先控制组允许管理员动态设置网络流量的优先次序;支持EFI引导固件;改进内存管理,等等。 Linux Kernel截图

    Linux的高级路由和流量控制HOWTO-中文版

    虽然这些工具能够工作,但它们在 Linux2.2 和更高版本的内核上显 得有一些落伍。比如,现在 GRE 隧道已经成为了路由的一个主要概念,但却不 能通过上述工具来配置。 使用了 iproute2,隧道的配置与其他部分完全集成了。

    lksctp-rs:Rust 的 Linux 内核 SCTP 低级绑定

    流量控制和拥塞控制 此外,它还补充说: 多路复用流:通过单个连接,您可以多路复用多个信息流。 端点的多宿主:一个端点可以有多个 IP 地址,允许网络故障转移/可靠性(注意:它需要是一台机器,或者复制端点的...

    《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

    15.2.3 流量控制315 15.3 缓冲区管理和并发控制315 15.4 设备实例:以太网nic316 15.5 isa网络驱动程序321 15.6 atm321 15.7 网络吞吐量322 15.7.1 驱动程序性能322 15.7.2 协议性能323 15.8 查看...

    精通LINUX设备驱动程序开发

    311 15.1.6 统计 312 15.1.7 配置 313 15.1.8 总线相关内容 314 15.2 与协议层会话 314 15.2.1 接收路径 314 15.2.2 发送路径 315 15.2.3 流量控制 315 15.3 缓冲区管理和并发控制 315 15.4 设备实例:...

    基于Linux 的防火墙技术研究

    络地址转换、流量控制及高级的包处理等。Netfilter/Iptables 系统采用模块化的架构方式,其主要模块 有:通用框架Netfilter、数据包选择系统、连接跟踪系统及NAT系统等等。 2.1 Netfilter/Iptables 系统工作原理 ...

    Linux模拟网络丢包与延迟的方法

    netem 与 tc: netem 是 Linux 2.6 及以上...tc 是 Linux 系统中的一个工具,全名为traffic control(流量控制)。tc 可以用来控制 netem 的工作模式,也就是说,如果想使用 netem ,需要至少两个条件,一个是内核中的

    DarkShell_Linux-Win集群版V2014年

    Linux支持路由内核、2.6、3.1等普通内核,路由内核支持路由三大内核、Ubuntu、admin等,独立开发的Linux穿盾CC模式,SYN稳定发包100%,自启动,无需Root权限上线即可发包。 VIP版本攻击代码实时更新,通过服务器...

    ip route2 源码 第二代网络工具

    如果你仍在使用net-tools,而且尤其需要跟上新版Linux内核中的最新最重要的网络特性的话,那么是时候转到iproute2的阵营了。原因就在于使用iproute2可以做很多net-tools无法做到的事情。对于那些想要转到使用iproute...

    Linux C 一站式学习

    7.3. 流量控制 37. socket编程 1. 预备知识 1.1. 网络字节序 1.2. socket地址的数据类型及相关函数 2. 基于TCP协议的网络程序 2.1. 最简单的TCP网络程序 2.2. 错误处理与读写控制 2.3. 把client改为交互式输入 2.4. ...

Global site tag (gtag.js) - Google Analytics