cgroup 子系统之 net_cls 和 net_prio

栏目: 服务器 · Linux · 发布时间: 7年前

内容简介:cgroup 子系统之 net_cls 和 net_prio

在分析 net_clsnet_prio 之前先要解释几个东西,一个是网络的 QoS 以及 netfilter。

网络 QoS

IP 服务模型是尽力而为的,这样的模型不能体现某些流量的重要性,所以诞生了 QOS 技术,Linux 很早就提供了流量控制接口,命令行 工具tc

协议栈的 QoS 主要由三部分组成。

qdisc 队列规则(queueing discipline),class 控制策略,filter 根据 filter 划入具体的控制策略(class)。

一般由流程是这样的,当一个 qdisc 被入队的时候,会循环匹配其中的 filter 如果匹配到了的话,就会将 packet 入队到对应的 class 当中,大部分情况下被 class 的“所有者”代表的 qdisc 入队。一些没有匹配的 packet 会进入默认的 class。下面将会介绍几种常用的 queue discpline。

pfifo

默认 qdisc 就是 pfifo,实现在 net/sched/sch_prio.c

设有三个优先级的队列,优先级由高到低分别是

  1. “interactive”
  2. “best effort”
  3. “bulk”

先消费0队列再消费1队列,依次类推,一般的 packet 都是属于1。

cgroup 子系统之 net_cls 和 net_prio

用 IP 的 ToS 可以映射到这些队列,对应的关系如图。

cgroup 子系统之 net_cls 和 net_prio

cgroup 子系统之 net_cls 和 net_prio

他的实现依赖下面这个结构,bands 一般是3个,代表三个不同优先级的队列,然后 filter_list 是他的过滤器列表,最后 prio2band 就是上图的 ToS To Queue 的映射中的 Linux priority 到 Band 的那部分, queues 保存的是三个 fifoqdisc ,也就是三个最简单的队列。

struct prio_sched_data {
    int bands;
    struct tcf_proto __rcu *filter_list;
    u8  prio2band[TC_PRIO_MAX+1];
    struct Qdisc *queues[TCQ_PRIO_BANDS];
};

这里虽然和 IP 的 ToS 暂时没有关系,但是最后 cgroup 的部分会提到怎么联系起来的。

pfifo enqueue 的时候会调用 prio_classify 根据 skb->priority 来选择队列进行入队,dequeue 的时候则 round robin 每次依次从优先级高到低取出一个 packet。

HTB (Hierarchical Token Bucket)

HTB 是一个层级令牌桶的 qdisc,而且可以加入 class,HTB 的整体结构如下,HTB 的作者在 这里 解释了他的设计。

这里简单解释一下,每个 class 有一个 AR(保障速率)CR(最大速率),P(优先级),level(在树中的层次),quantum(量子,一个动态参数),和实际的速率 R,中间层的 class 通过计算子层的速率获得。

Leaf 没有子节点,并且只有 Leaf 有传输功能的 queue,其他只是帮助构造层级关系。

Mode class 的状态

  1. Red ( R > CR ) 也就是超速

  2. Yellow (R <= CR && R > AR) 也就是合理超速

  3. Green 就是没有超过保障速率

下面我们有几个等式

Rc = min(CRc, ARc + Bc)        [eq1]

Bc 表示从祖先那里借到的速率,下面这段公式表示的意思是,如果我是当前 prio 最小的,从优先级的兄弟节点中加权平均借到父节点的速率,其他的 prio 更大的节点都不能借到速率。

Qc Rp
Bc = -----------------------------  iff min[Pi over D(p)]>=Pc  [eq2]
     sum[Qi over D(p) where Pi=Pc]

Bc = 0   otherwise      [eq3]

如果没有父节点的话,Bc 就是0。这样算出来代表的意义是什么呢,就是说先服务优先级更高的节点,并且按照量子的大小在同优先级的节点中分配速率。

然后我们具体看一下 HTB 调度器是如何工作的。

cgroup 子系统之 net_cls 和 net_prio

cgroup 子系统之 net_cls 和 net_prio

每个 class 有个 slot 包含不同的 prio 级别,指向 yellow 的子节点,然后每个层级包含一个 slot 也有不同的 prio 级别指向这一层中 green 的节点,上图展示了的是有两个 prio 的 slot,右边的 self slot 有个白色的是用于 yellow red 的 wait list。

假设当前如图 1 的状态,所有节点都是绿的没有 packet 到来,现在有有两个包到达 C 和 D,然后激活它们,并且它们现在都是绿的,所以 self slot 指向他们,然后因为 D 的优先级更高,所以先把 D 出队。所以你可以发现,出队的顺序很简答,就是按照优先级从 self slot 里面把绿色的包按顺序取出来就可以。

cgroup 子系统之 net_cls 和 net_prio

cgroup 子系统之 net_cls 和 net_prio

然后我们看一下更复杂的情况,在图3中,我们从D中出队一个 packet( htb_dequeue ),然后 htb_charge_class 会增加D的速率,导致D变成 yellow,离开 self slot (通过 htb_deactivate_prioshtb_remove_class_from_row ),然后添加到 B 的 slot 里面( htb_activate_prios ),并且递归向上添加 htb_add_class_to_row ,D会在一段时间后进入 self slot 的白色等待区,然后 D 又会变回绿色。现在如果选择的话,就会从C出队,因为虽然C的优先级低但是C不需要借别人的速率。

在图4,假设C已经完全消耗了速率达到了最大限速,这个时候D就会开始工作然后把B消耗完,B被消耗了以后就会去消耗A,从这里就可以看到一个借取的过程。

cgroup 子系统之 net_cls 和 net_prio

cgroup 子系统之 net_cls 和 net_prio

现在说一个更复杂一点的例子,在图5中,A已经被消耗光了,E开始工作,然后C也能开始工作,变成图6的样子。注意即使D没有被使用但是他的优先级还会被 class slot 维持,也就是红线。但是 C 和 E都是同一个优先级,这样的话,就要使用 DRR 算法(也就是在 RR 算法上给每个变量加一个权重,也就是之前的那个量子 quantom)。然后也可以发现一个 class 可以对不同的优先级(红色和蓝色)保持 active。

下面是一个 HTB 的全貌图,抽象的可以理解成一个从父 class 中根据优先级带权重的分享令牌的一个算法。

cgroup 子系统之 net_cls 和 net_prio

下面三个值对应的就是三种颜色。

/* used internaly to keep status of single class */
enum htb_cmode {
    HTB_CANT_SEND,        /* class can't send and can't borrow */
    HTB_MAY_BORROW,        /* class can't send but may borrow */
    HTB_CAN_SEND        /* class can send */
};

HTB 的实现依赖于

struct htb_sched {
    struct Qdisc_class_hash clhash;
    int            defcls;        /* class where unclassified flows go to */
    int            rate2quantum;    /* quant = rate / rate2quantum */

    /* filters for qdisc itself */
    struct tcf_proto __rcu    *filter_list;

#define HTB_WARN_TOOMANYEVENTS    0x1
    unsigned int        warned;    /* only one warning */
    int            direct_qlen;
    struct work_struct    work;

    /* non shaped skbs; let them go directly thru */
    struct qdisc_skb_head    direct_queue;
    long            direct_pkts;

    struct qdisc_watchdog    watchdog;

    s64            now;    /* cached dequeue time */
    struct list_head    drops[TC_HTB_NUMPRIO];/* active leaves (for drops) */

    /* time of nearest event per level (row) */
    s64            near_ev_cache[TC_HTB_MAXDEPTH];

    int            row_mask[TC_HTB_MAXDEPTH];

    struct htb_level    hlevel[TC_HTB_MAXDEPTH];
};

其中 hlevel 对应的就是 self slot ,而 clhash 可以通过 classid 找到 hub_class ,其中的 ->un.inner 就是对应的 class slot->un.leaf 对应的就是叶子结点。

使用

qdisc 参数

parent major:minor 或者 root

一个qdisc是根节点就是root,否则其他的情况指定parent。其中major:minor是class的handle id,每个class都要指定一个id用于标识。

handle major: ,这个语法有点奇怪,是可选的,如果qdisc下面还要分类(多个class),则需要指定这个hanlde。对于root,通常是”1:”。

// handle 是一组用户指定的标示符,格式为major:minor。  
// 如果是一条queueing discipline,minor需要一直为0。  
# tc qdisc add dev eth0 root handle 1: htb  

// parent 指明该新增的 class 添加到那一个父 handle 上去  
// classid 指明该 class handle 的唯一ID,minor需要是非零值  
// ceil 定义 rate 的上界  
# tc class add dev eth0 parent 1:1 classid 1:6 htb rate 256kbit ceil 512kbit

// 新建一个带宽为100kbps的root class, 其classid为1:1
# tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
// 接着建立两个子class,指定其parent为1:1,ceil用来限制子类最大的带宽
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 40kbps ceil 100kbps
# tc class add dev eth0 parent 1:1 classid 1:11 htb rate 60kbps ceil 100kbps
// 随后建立filter指定哪些类型的packet进入那个class
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flow 1:11
// 最后为这些class添加queuing disciplines
# tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 5
# tc qdisc add dev eth0 parent 1:11 handle 30: sfq perturb 10

实现

用 rtnetlink 接口实现API。

include/net/sch_generic.h 下有 Qdisc_ops , Qdisc_class_ops , tcf_proto_ops 的定义。

同一个文件下还有 Qdisc_class_ops 的定义。

qdisc_run 首先检查设备的运行状态然后调用 __qdisc_run ,这个函数的主体就是调用 qdisc_restart ,直到超过限制或者需要让出时间CPU了,最后清空 qdiscRUNNING 状态。

在此结构中,enqueue 和 dequeue 两个函数是整个 QoS 调度的入口函数。其中的 Qdisc_class_ops 用于对此 Qdisc 的 filter list 进行操作,添加删除等,通过对 Qdisc 添加 fliter,filter 对 enqueue 到此 Qdisc 的 pkt 进行分类,从而归类到此 Qdisc 的子 class 中,而每个子 class 都有自己的 Qdisc 进行 pkt enqueue 的管理,因此实现一个树形的 filter 结构。

callgraph Qdisc 在从来自上层的 dev_queue_xmit 主动发送开始起作用,对出口数据包作限制。

dev_queue_xmit -> 
    __dev_queue_xmit ->
        __dev_xmit_skb ->
            qdisc->enqueue
            __qdisc_run ->
              qdisc_restart -> 
                qdisc->dequeue ->
                  sch_direct_xmit

enqueue 的时候会主动唤起 dequeue 也可能是硬件发送就绪会唤醒发送软中断来 dequeue ,描述的整个过程大概是图中的这部分。

cgroup 子系统之 net_cls 和 net_prio

补充

netfilter

netfilter 在数据包传输中有一些 hook 可以在其中注册回调函数。

cgroup 子系统之 net_cls 和 net_prio

iptables

主要是四表五链,是 netfilter 的用户态工具。

deep dive

cgroup 子系统 net_cls (Network classifier cgroup)

net_cls 可以给 packet 打上 classid 的标签,用于过滤分类,有了上面的详细解释,这个 classid 的作用也非常明显了,就是用于标记 skb 所属的 qdisc class 的。

有了这个标签,流量控制器(tc)可以对不同的 cgroup 的 packet 起作用,Netfilter(iptables)也可以基于这个标签有对应的动作。创建一个 net_cls cgroup 对应的是创建一个 net_cls.classid 文件,这个文件初始化为0。可以写16进制的 0xAAAABBBB 到这个文件里面,AAAA 是 major 号,BBBB 是 minor 号。读这个文件返回的是十进制的数字。

例子

mkdir /sys/fs/cgroup/net_cls
mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
mkdir /sys/fs/cgroup/net_cls/0
echo 0x100001 >  /sys/fs/cgroup/net_cls/0/net_cls.classid

设置一个 10:1 handle.

cat /sys/fs/cgroup/net_cls/0/net_cls.classid
1048577

配置 tc:

tc qdisc add dev eth0 root handle 10: htb
tc class add dev eth0 parent 10: classid 10:1 htb rate 40mbit

创建 traffic class 10:1

tc filter add dev eth0 parent 10: protocol ip prio 10 handle 1: cgroup

配置 iptables,也可以用于这个 classid。

iptables -A OUTPUT -m cgroup ! --cgroup 0x100001 -j DROP

对应的实现在 net/core/netclassid_cgroup.c 下面。起作用的方式是 css_cls_stateclassid 并且 sock_cgroup_set_classid(&sock->sk->sk_cgrp_data,(unsigned long)v) 来设置 sockclassid

cgroup net_prio 子系统

网络优先权(net_prio)子系统可以为各个 cgroup 中的应用程序动态配置每个网络接口的流量优先级。

net_prio.prioidx

只读文件。它包含一个特有整数值,kernel 使用该整数值作为这个 cgroup 的内部代表。

net_prio.ifpriomap

包含优先级图谱,这些优先级被分配给源于此群组进程的流量以及通过不同接口离开系统的流量。回顾 pfifo 里优先级映射,对应的就是这个值。该图用 的形式以成对列表表示:

~]# cat /cgroup/net_prio/iscsi/net_prio.ifpriomap
eth0 5
eth1 4
eth2 6

net_prio.ifpriomap 文件的目录可以使用上述格式,通过将字符串回显至文件的方式来修改。例如:

~]# echo "eth0 5" > /cgroup/net_prio/iscsi/net_prio.ifpriomap

上述指令将强制设定任何源于 iscsi net_prio cgroup 进程的流量和 eth0 网络接口传出的流量的优先级为 5 。父 cgroup 也有可写入的 net_prio.ifpriomap 文件,可以设定系统默认优先级。

对应的实现在 net/core/netprio_cgroup.c 下面。实现方式是通过扩展 dev->priomapprioid->prio 的映射记录这个优先级和cgroup的关系。

net_prio 使用每个 cgroup的id(cgroupo->id)作为 sequence number,并将这个存储在 sk_cgrp_prioidx 中。sk_cgrp_prioidx 这个是单纯的用于设置网络包的优先级,使用这个之后将会覆盖之前通过SO_PRIORITY socket选项或者其他方式设置的值。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程卓越之道

编程卓越之道

Hyde R / 韩东海 / 电子工业出版社 / 2006-4-1 / 49.80

各位程序员一定希望自己编写的代码是能让老板赞赏、满意的代码;是能让客户乐意掏钱购买的代码;是能让使用者顺利使用的代码;是能让同行欣赏赞誉的代码;是能让自己引以为豪的卓越代码。本书作者为希望能编写出卓越代码的人提供了自己积累的关于卓越编程的真知灼见。它弥补了计算机科学和工程课程中被忽略的一个部分——底层细节,而这正是构建卓越代码的基石。具体内容包括:计算机数据表示法,二进制数学运算与位运算,内存组织......一起来看看 《编程卓越之道》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器