RFS 理解

1.背景

网卡接收一个数据包的情况下,会经过三个阶段:

- 网卡产生硬件中断通知CPU有包到达

- 通过软中断处理此数据包

- 在用户态程序处理此数据包

在SMP体系下,这三个阶段有可能在3个不同的CPU上处理,如下图所示:

而RFS的目标就是增加CPU缓存的命中率从而提高网络延迟。当使用RFS后,其效果如下:

2.实现原理

当用户程序调用 revmsg() 或者 sendmsg()的时候,RFS会将此用户程序运行的CPU id存入hash表;

而当有关用户程序的数据包到达的时候,RFS尝试从hash表中取出相应的CPU id, 并将数据包放置

到此CPU的队列,从而对性能进行优化。

3.重要数据结构

/*
* The rps_sock_flow_table contains mappings of flows to the last CPU
* on which they were processed by the application (set in recvmsg).
*/
struct rps_sock_flow_table {
    unsigned int mask;
    u16 ents[0];
};
#define RPS_SOCK_FLOW_TABLE_SIZE(_num) (sizeof(struct rps_sock_flow_table) + \
    ((_num) * sizeof(u16)))

结构体 rps_sock_flow_table 实现了一个hash表,RFS会将其声明一个全局变量用于存放所有sock对应的CPU。

/*
* The rps_dev_flow structure contains the mapping of a flow to a CPU, the
* tail pointer for that CPU‘s input queue at the time of last enqueue, and
* a hardware filter index.
*/
struct rps_dev_flow {
    u16 cpu;     //此链路上次使用的cpu
    u16 filter;
    unsigned int last_qtail;   //此设备队列入队的sk_buff的个数
};
#define RPS_NO_FILTER 0xffff

/*
* The rps_dev_flow_table structure contains a table of flow mappings.
*/
struct rps_dev_flow_table {
    unsigned int mask;
    struct rcu_head rcu;
    struct rps_dev_flow flows[0]; //实现hash表
};
#define RPS_DEV_FLOW_TABLE_SIZE(_num) (sizeof(struct rps_dev_flow_table) + \
    ((_num) * sizeof(struct rps_dev_flow)))

结构体 rps_dev_flow_table 是针对一个设备队列

4.具体实现

用户程序使用revmsg() 或者 sendmsg()的时候 设置CPU id。

int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
         size_t size, int flags)
{
    struct sock *sk = sock->sk;
    int addr_len = 0;
    int err;

    sock_rps_record_flow(sk);   //设置CPU id

    err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
                   flags & ~MSG_DONTWAIT, &addr_len);
    if (err >= 0)
        msg->msg_namelen = addr_len;
    return err;
}
EXPORT_SYMBOL(inet_recvmsg);

当有数据包进行了响应后,会调用get_rps_cpu()选择合适的CPU id。其关键代码如下:

3117     hash = skb_get_hash(skb);
3118     if (!hash)
3119         goto done;
3120
3121     flow_table = rcu_dereference(rxqueue->rps_flow_table);     //设备队列的hash表
3122     sock_flow_table = rcu_dereference(rps_sock_flow_table);    //全局的hash表
3123     if (flow_table && sock_flow_table) {
3124         u16 next_cpu;
3125         struct rps_dev_flow *rflow;
3126
3127         rflow = &flow_table->flows[hash & flow_table->mask];
3128         tcpu = rflow->cpu;
3129
3130         next_cpu = sock_flow_table->ents[hash & sock_flow_table->mask];   //得到用户程序运行的CPU id
3131
3132         /*
3133          * If the desired CPU (where last recvmsg was done) is
3134          * different from current CPU (one in the rx-queue flow
3135          * table entry), switch if one of the following holds:
3136          *   - Current CPU is unset (equal to RPS_NO_CPU).
3137          *   - Current CPU is offline.
3138          *   - The current CPU‘s queue tail has advanced beyond the
3139          *     last packet that was enqueued using this table entry.
3140          *     This guarantees that all previous packets for the flow
3141          *     have been dequeued, thus preserving in order delivery.
3142          */
3143         if (unlikely(tcpu != next_cpu) &&
3144             (tcpu == RPS_NO_CPU || !cpu_online(tcpu) ||
3145              ((int)(per_cpu(softnet_data, tcpu).input_queue_head -
3146               rflow->last_qtail)) >= 0)) {
3147             tcpu = next_cpu;
3148             rflow = set_rps_cpu(dev, skb, rflow, next_cpu);
3149         }
3150
3151         if (tcpu != RPS_NO_CPU && cpu_online(tcpu)) {
3152             *rflowp = rflow;
3153             cpu = tcpu;
3154             goto done;
3155         }
3156     }

上面的代码中第3145行比较难理解,数据结构 softnet_data用于管理进出的流量,他有两个关键的变量:

2374 #ifdef CONFIG_RPS
2375     /* Elements below can be accessed between CPUs for RPS */
2376     struct call_single_data csd ____cacheline_aligned_in_smp;
2377     struct softnet_data *rps_ipi_next;
2378     unsigned int        cpu;
2379     unsigned int        input_queue_head;   //队列头,也可以理解为出队的位置
2380     unsigned int        input_queue_tail;     //队列尾,也可以理解为入队的位置
2381 #endif

表达式 (int)(per_cpu(softnet_data, tcpu).input_queue_head 求出了在tcpu 这个CPU上的出队数目,而rflow->last_qtail

代表设备队列上此sock对应的最后入队的位置,如果出队数目大于入队数目,那么说明这一链路上的包都处理完毕,不会

出现乱序处理的包。第3143的if 语句就是为了防止乱序包的出现,假如是多进程或者多线程同时处理一个socket,那么此

socket对应的CPU id就会不停变化。

参考文献:

http://www.pagefault.info/?p=115

http://syuu.dokukino.com/2013/05/linux-kernel-features-for-high-speed.html

https://www.kernel.org/doc/Documentation/networking/scaling.txt

时间: 2024-10-30 16:04:45

RFS 理解的相关文章

RFS一些基本概念

1. Project.Directory.TestSuit.TestCase.Resource的区别?   Project:项目名称   Directory:对项目进行分层   TestSuit:测试套件   TestCase:测试用例   Resource:可理解为高级语言中的类 2. setting中teardown和返回值的应用   teardown代表测试后需要执行的步骤(你自己可以写一些清理变量或者操作的步骤),由界面来看,清理步骤只能写一步(内置关键字或者用户定义关键字)   返回值

RFS的web自动化验收测试

RFS——RobotFramework+Selenium2library 第1讲 菜单栏&工具栏 前一讲已经看到我们最新版本的工具界面了. 我把RIDE的界面大致分了四个区域:菜单栏.工具栏.案例及资源区.工作区,如下图 菜单栏:RIDE所有的功能都在这里面: 工具栏:比较常用的功能,可以快捷操作: 案例及资源区:这里将会是一个目录一样的树形结构(当前目前是刚打开的样子,里面只有一个空的external resources) 工作区:这里是我们主要编辑案例,运行案例的操作区. 其实我一直在想,这

为什么使能RPS/RFS, 或者RSS/网卡多队列后,QPS反而下降?

http://laoar.github.io/blog/2017/05/07/rps/ TL;DR RPS 即receive side steering,利用网卡的多队列特性,将每个核分别跟网卡的一个首发队列绑定,以达到网卡硬中断和软中断均衡的负载在各个CPU上. 他要求网卡必须要支持多队列特性. RPS receive packet steering 他把收到的packet依据一定的hash规则给hash到不同的CPU上去,以达到各个CPU负载均衡的目的. 他只是把软中断做负载均衡,不去改变硬

Python——深入理解urllib、urllib2及requests(requests不建议使用?)

深入理解urllib.urllib2及requests            python Python 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年,Python 源代码同样遵循 GPL(GNU General Public License)协议[1] .Python语法简洁而清晰,具有丰富和强大的类库. urllib and urllib2 区别 urllib和urllib2模块都做与请求URL相关的操作,但

关于SVM数学细节逻辑的个人理解(三) :SMO算法理解

第三部分:SMO算法的个人理解 接下来的这部分我觉得是最难理解的?而且计算也是最难得,就是SMO算法. SMO算法就是帮助我们求解: s.t.   这个优化问题的. 虽然这个优化问题只剩下了α这一个变量,但是别忘了α是一个向量,有m个αi等着我们去优化,所以还是很麻烦,所以大神提出了SMO算法来解决这个优化问题. 关于SMO最好的资料还是论文<Sequential Minimal Optimization A Fast Algorithm for Training Support Vector

2.2 logistic回归损失函数(非常重要,深入理解)

上一节当中,为了能够训练logistic回归模型的参数w和b,需要定义一个成本函数 使用logistic回归训练的成本函数 为了让模型通过学习来调整参数,要给出一个含有m和训练样本的训练集 很自然的,希望通过训练集找到参数w和b,来得到自己得输出 对训练集当中的值进行预测,将他写成y^(I)我们希望他会接近于训练集当中的y^(i)的数值 现在来看一下损失函数或者叫做误差函数 他们可以用来衡量算法的运行情况 可以定义损失函数为y^和y的差,或者他们差的平方的一半,结果表明你可能这样做,但是实际当中

理解信息管理系统

1.信息与数据的区别是什么? 数据是记录客观事物,可鉴别的符号,而信息是具有关联性和目的性的结构化,组织化的数据.数据经过处理仍是数据,而信息经过加工可以形成知识.处理数据是为了便于更好的解释,只有经过解释,数据才有意义,才可以成为信息.可以说信息是经过加工以后,对客观世界产生影响的数据. 2.信息与知识的区别是什么? 信息是具有关联性和目的性的结构化,组织化的数据,知识是对信息的进一步加工和应用,是对事物内在规律和原理的认识.信息经过加工可以形成知识. 3.举一个同一主题不同级别的数据.信息.

深度理解div+css布局嵌套盒子

1. 网页布局概述 网页布局的概念是把即将出现在网页中的所有元素进行定位,而CSS网页排版技术有别于传统的网页排版方法,它将页面首先在整体上使用<div>标记进行分块,然后对每个快进行CSS定位以及设置显示效果,最后在每个块中添加相应的内容.利用CSS排版方法更容易地控制页面每个元素的效果,更新也更容易,甚至页面的拓扑结构也可以通过修改相应的CSS属性来重新定位.  2. 盒子模型 盒子模型是CSS控制页面元素的一个重要概念,只有掌握了盒子模型,才能让CSS很好地控制页面上每一个元素,达到我们

深入理解Java:类加载机制及反射

一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能. 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.工作机制 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示