• 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)))
    View Code
    结构体 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)))
    View Code
    结构体 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);
    View Code
    当有数据包进行了响应后,会调用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     }
    View Code
    上面的代码中第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
    View Code
    表达式 (int)(per_cpu(softnet_data, tcpu).input_queue_head 求出了在tcpu 这个CPU上的出队数目,而rflow->last_qtail
    代表设备队列上此sock对应的最后入队的位置,如果出队数目大于入队数目,那么说明这一链路上的包都处理完毕,不会
    出现乱序处理的包。第3143的if 语句就是为了防止乱序包的出现,假如是多进程或者多线程同时处理一个socket,那么此
    socket对应的CPU id就会不停变化。
     
     
    参考文献:
     
     
     
     
     
  • 相关阅读:
    005.SQLServer AlwaysOn可用性组高可用简介
    004.Windows Server 故障转移群集 (WSFC)简介
    003.SQLServer数据库镜像高可用部署
    附008.Kubernetes TLS证书介绍及创建
    附007.Kubernetes ABAC授权
    附006.Kubernetes RBAC授权
    附005.Kubernetes身份认证
    附004.Kubernetes Dashboard简介及使用
    附003.Kubeadm部署Kubernetes
    附002.Minikube介绍及使用
  • 原文地址:https://www.cnblogs.com/lxgeek/p/4182364.html
Copyright © 2020-2023  润新知