Linux kernel之网络rps

rps即receive package scaling, 是内核软件层实现的一个性能扩展机制,根据网络包数据计算hash值,然后挂载到对应cpu的backlog队列中,代码如下:

static int netif_rx_internal(struct sk_buff *skb)

{

int ret;

net_timestamp_check(netdev_tstamp_prequeue, skb);

trace_netif_rx(skb);

#ifdef CONFIG_RPS

if (static_key_false(&rps_needed)) {

struct rps_dev_flow voidflow, *rflow = &voidflow;

int cpu;

preempt_disable();

rcu_read_lock();

cpu = get_rps_cpu(skb->dev, skb, &rflow); //获取cpu号

if (cpu < 0)

cpu = smp_processor_id();

ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);//入队到cpu的backlog队列

rcu_read_unlock();

preempt_enable();

} else

#endif

{

unsigned int qtail;

ret = enqueue_to_backlog(skb, get_cpu(), &qtail);

put_cpu();

}

return ret;

}

其关键点在于get_rps_cpu(),其函数实现如下:

根据skb入队记录获取skb的rxqueue

获取rxqueue->rps_map,若不存在映射表返回-1,这种情况下由上述netif_rx_internal()函数分析可知直接挂在到处理cpu的backlog队列中,若该rxqueue的map到唯一cpu,直接返回相应cpu

进入核心流程skb_get_hash(),即计算skb hash值

/*

* get_rps_cpu is called from netif_receive_skb and returns the target

* CPU from the RPS map of the receiving queue for a given skb.

* rcu_read_lock must be held on entry.

*/

static int get_rps_cpu(struct net_device *dev, struct sk_buff *skb,

struct rps_dev_flow **rflowp)

{

struct netdev_rx_queue *rxqueue;

struct rps_map *map;

struct rps_dev_flow_table *flow_table;

struct rps_sock_flow_table *sock_flow_table;

int cpu = -1;

u16 tcpu;

u32 hash;

if (skb_rx_queue_recorded(skb)) {

u16 index = skb_get_rx_queue(skb);

if (unlikely(index >= dev->real_num_rx_queues)) {

WARN_ONCE(dev->real_num_rx_queues > 1,

"%s received packet on queue %u, but number "

"of RX queues is %u\n",

dev->name, index, dev->real_num_rx_queues);

goto done;

}

rxqueue = dev->_rx + index;

} else

rxqueue = dev->_rx;

map = rcu_dereference(rxqueue->rps_map);

if (map) {

if (map->len == 1 &&

!rcu_access_pointer(rxqueue->rps_flow_table)) {

tcpu = map->cpus[0];

if (cpu_online(tcpu))

cpu = tcpu;

goto done;

}

} else if (!rcu_access_pointer(rxqueue->rps_flow_table)) {

goto done;

}

skb_reset_network_header(skb);

hash = skb_get_hash(skb);

if (!hash)

goto done;

flow_table = rcu_dereference(rxqueue->rps_flow_table);

sock_flow_table = rcu_dereference(rps_sock_flow_table);

if (flow_table && sock_flow_table) {

u16 next_cpu;

struct rps_dev_flow *rflow;

rflow = &flow_table->flows[hash & flow_table->mask];

tcpu = rflow->cpu;

next_cpu = sock_flow_table->ents[hash & sock_flow_table->mask];

/*

* If the desired CPU (where last recvmsg was done) is

* different from current CPU (one in the rx-queue flow

* table entry), switch if one of the following holds:

* - Current CPU is unset (equal to RPS_NO_CPU).

* - Current CPU is offline.

* - The current CPU's queue tail has advanced beyond the

* last packet that was enqueued using this table entry.

* This guarantees that all previous packets for the flow

* have been dequeued, thus preserving in order delivery.

*/

if (unlikely(tcpu != next_cpu) &&

(tcpu == RPS_NO_CPU || !cpu_online(tcpu) ||

((int)(per_cpu(softnet_data, tcpu).input_queue_head -

rflow->last_qtail)) >= 0)) {

tcpu = next_cpu;

rflow = set_rps_cpu(dev, skb, rflow, next_cpu);

}

if (tcpu != RPS_NO_CPU && cpu_online(tcpu)) {

*rflowp = rflow;

cpu = tcpu;

goto done;

}

}

if (map) {

tcpu = map->cpus[reciprocal_scale(hash, map->len)];

if (cpu_online(tcpu)) {

cpu = tcpu;

goto done;

}

}

done:

return cpu;

}

skb_get_hash流程分析:对于skb_get_hash()函数,首先判断是否已经设置了l4_hash或者sw_hash,如果已经设置,就直接返回已经设置的hash值(对于某些driver如hyperv,vmware以及某些设备上的网卡driver,在网卡driver层会为skb设置hash值,这种场景下无须再次计算)。否则调用__skb_get_hash()计算并设置hash值。对于这一函数的核心为__skb_flow_dissect()函数,具体见下面分析。

static inline bool skb_flow_dissect_flow_keys(const struct sk_buff *skb,

struct flow_keys *flow,

unsigned int flags)

{

memset(flow, 0, sizeof(*flow));

return __skb_flow_dissect(skb, &flow_keys_dissector, flow,

NULL, 0, 0, 0, flags);

}

static inline u32 ___skb_get_hash(const struct sk_buff *skb,

struct flow_keys *keys, u32 keyval)

{

skb_flow_dissect_flow_keys(skb, keys,

FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL);

return __flow_hash_from_keys(keys, keyval);

}

void __skb_get_hash(struct sk_buff *skb)

{

struct flow_keys keys;

u32 hash;

__flow_hash_secret_init();

hash = ___skb_get_hash(skb, &keys, hashrnd);

__skb_set_sw_hash(skb, hash, flow_keys_have_l4(&keys));

}

static inline __u32 skb_get_hash(struct sk_buff *skb)

{

if (!skb->l4_hash && !skb->sw_hash)

__skb_get_hash(skb);

return skb->hash;

}

此处__skb_flow_dissect()使用flow_keys_dissector作为流分发器,由flow_dissector_key定义和skb_flow_dissector_init()函数,对于flow_keys_dissector, enable了KEY_CONTROL, KEY_BASIC, KEY_IPV4_ADDR, KEY_TIPC_ADDR, KEY_PORTS, KEY_VLAN, KEY_FLOW_TABLE, KEY_GET_KEYID。据此,分析_skb_flow_dissect()函数:

基于skb是否存在vlan_tag获取protol,获取flow_key的key_control和key_basic指针。

如果是IP包,由于flow_dissector_key中enable了KEY_IPV4_ADDRS,key_control->addr_type = KEY_IPv4_ADDRS,key_addrs->v4addrs = iph->saddr, iph->daddr,即使用源地址和目的地址

判断是否是分片包,若是分片包,直接退出该函数

否则进一步判断是否在L3层stop,若stop l3,则退出,否则进一步从skb获取ports作为key_ports的输入。由于传入flag为0,因此会继续获取ports。

static const struct flow_dissector_key flow_keys_dissector_keys[] = {

{

.key_id = FLOW_DISSECTOR_KEY_CONTROL,

.offset = offsetof(struct flow_keys, control),

},

{

.key_id = FLOW_DISSECTOR_KEY_BASIC,

.offset = offsetof(struct flow_keys, basic),

},

{

.key_id = FLOW_DISSECTOR_KEY_IPV4_ADDRS,

.offset = offsetof(struct flow_keys, addrs.v4addrs),

},

{

.key_id = FLOW_DISSECTOR_KEY_IPV6_ADDRS,

.offset = offsetof(struct flow_keys, addrs.v6addrs),

},

{

.key_id = FLOW_DISSECTOR_KEY_TIPC_ADDRS,

.offset = offsetof(struct flow_keys, addrs.tipcaddrs),

},

{

.key_id = FLOW_DISSECTOR_KEY_PORTS,

.offset = offsetof(struct flow_keys, ports),

},

{

.key_id = FLOW_DISSECTOR_KEY_VLAN,

.offset = offsetof(struct flow_keys, vlan),

},

{

.key_id = FLOW_DISSECTOR_KEY_FLOW_LABEL,

.offset = offsetof(struct flow_keys, tags),

},

{

.key_id = FLOW_DISSECTOR_KEY_GRE_KEYID,

.offset = offsetof(struct flow_keys, keyid),

},

};

bool __skb_flow_dissect(const struct sk_buff *skb,

struct flow_dissector *flow_dissector,

void *target_container,

void *data, __be16 proto, int nhoff, int hlen,

unsigned int flags)

{

struct flow_dissector_key_control *key_control;

struct flow_dissector_key_basic *key_basic;

struct flow_dissector_key_addrs *key_addrs;

struct flow_dissector_key_ports *key_ports;

struct flow_dissector_key_icmp *key_icmp;

struct flow_dissector_key_tags *key_tags;

struct flow_dissector_key_vlan *key_vlan;

bool skip_vlan = false;

u8 ip_proto = 0;

bool ret;

if (!data) {

data = skb->data;

proto = skb_vlan_tag_present(skb) ?

skb->vlan_proto : skb->protocol;

nhoff = skb_network_offset(skb);

hlen = skb_headlen(skb);

}

/* It is ensured by skb_flow_dissector_init() that control key will

* be always present.

*/

key_control = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_CONTROL,

target_container);

/* It is ensured by skb_flow_dissector_init() that basic key will

* be always present.

*/

key_basic = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_BASIC,

target_container);

if (dissector_uses_key(flow_dissector,

FLOW_DISSECTOR_KEY_ETH_ADDRS)) {

struct ethhdr *eth = eth_hdr(skb);

struct flow_dissector_key_eth_addrs *key_eth_addrs;

key_eth_addrs = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_ETH_ADDRS,

target_container);

memcpy(key_eth_addrs, &eth->h_dest, sizeof(*key_eth_addrs));

}

proto_again:

switch (proto) {

case htons(ETH_P_IP): {

const struct iphdr *iph;

struct iphdr _iph;

ip:

iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);

if (!iph || iph->ihl < 5)

goto out_bad;

nhoff += iph->ihl * 4;

ip_proto = iph->protocol;

if (dissector_uses_key(flow_dissector,

FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {

key_addrs = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_IPV4_ADDRS,

target_container);

memcpy(&key_addrs->v4addrs, &iph->saddr,

sizeof(key_addrs->v4addrs));

key_control->addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;

}

if (ip_is_fragment(iph)) {

key_control->flags |= FLOW_DIS_IS_FRAGMENT;

if (iph->frag_off & htons(IP_OFFSET)) {

goto out_good;

} else {

key_control->flags |= FLOW_DIS_FIRST_FRAG;

if (!(flags & FLOW_DISSECTOR_F_PARSE_1ST_FRAG))

goto out_good;

}

}

__skb_flow_dissect_ipv4(skb, flow_dissector,

target_container, data, iph);

if (flags & FLOW_DISSECTOR_F_STOP_AT_L3)

goto out_good;

break;

}

...

if (dissector_uses_key(flow_dissector,

FLOW_DISSECTOR_KEY_PORTS)) {

key_ports = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_PORTS,

target_container);

key_ports->ports = __skb_flow_get_ports(skb, nhoff, ip_proto,

data, hlen);

}

if (dissector_uses_key(flow_dissector,

FLOW_DISSECTOR_KEY_ICMP)) {

key_icmp = skb_flow_dissector_target(flow_dissector,

FLOW_DISSECTOR_KEY_ICMP,

target_container);

key_icmp->icmp = skb_flow_get_be16(skb, nhoff, data, hlen);

}

out_good:

ret = true;

key_control->thoff = (u16)nhoff;

out:

key_basic->n_proto = proto;

key_basic->ip_proto = ip_proto;

return ret;

out_bad:

ret = false;

key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen);

goto out;

}

综上,对于分片包,仅根据IP包源地址和目的地址进行hash,对于非分片包,会进一步回去包的源端口和目的端口进行hash。

linux 内核rps,Linux kernel之网络rps相关推荐

  1. Linux内核参数(如kernel.shmmax)及Oracle相关参数调整(如SGA_MAX_SIZE)

    Linux内核参数(如kernel.shmmax)及Oracle相关参数调整(如SGA_MAX_SIZE) 我们一般在Linux 上安装 设置Oracle 数据库 或者在更换或升级硬件的时候都需要配置 ...

  2. linux内核和发行版本的关系,简述Linux内核和Linux发行版的区别

    做服务器运维工作,要经常和Linux的版本号打交道,但一直搞不明白Linux内核和Linux发行版到底是个啥东西.其实要理解Linux内核和Linux发行版之间的关系,只要能理解下面的关系就可以了: ...

  3. 国嵌linux内核编程,linux内核--那些年看国嵌视频学习

    1.linux系统构成 由用户空间和内核空间构成.其中用户空间由应用程序和C库:内核空间由系统调用接口.kernel.架构代码.硬件设备平台 为什么linux系统会被划分为用户空间和内核空间?处于安全 ...

  4. 深入理解Linux内核 学习Linux内核的一些建议及书记推荐

    深入理解Linux内核 学习Linux内核的一些建议_华清远见教育集团 经典书籍 待到山花烂漫时,还是那些经典在微笑. 有关内核的书籍可以用汗牛充栋来形容,不过只有一些经典的神作经住了考验.首先是5本 ...

  5. 一文看懂Linux内核!Linux内核架构和工作原理详解

    linux内核相关视频解析: 5个方面分析linux内核架构,让你对内核不再陌生 90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理 手把手带你实现一个Linux内核 ...

  6. 深度:一文看懂Linux内核!Linux内核架构和工作原理详解

    简介 作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址.目前支持模块的动态装卸(裁剪).Linux内核就是基于这个策略实现的.Linux进程1.采用层次结构,每个 ...

  7. 【Linux 内核】Linux 操作系统结构 ( Linux 内核在操作系统中的层级 | Linux 内核子系统及关系 | 进程调度 | 内存管理 | 虚拟文件系统 | 网络管理 | 进程间通信 )

    文章目录 一.Linux 内核在操作系统中的层级 二.Linux 内核子系统 三.Linux 内核子系统之间的关系 一.Linux 内核在操作系统中的层级 Linux 内核 所在层级 : 整个计算机系 ...

  8. Linux内核和Linux发行版(了解)

    Linux内核和Linux发行版(了解) Linux内核:Linux内核是一种开放源码的操作系统,由Linux Torvalds(Linux之父)负责维护,提供硬件抽象层.硬盘及文件系统控制及多任务功 ...

  9. 嵌入式烧写Linux内核,嵌入式linux 内核和根文件系统烧写方式简介

    总体来说,嵌入式Linux内核和根文件的引导与PC机差不多. 嵌入式linux内核和根文件系统可以存放在各种可能的存储设备中,一般情况下我们将内核和根文件系统直接烧入到Flash中(包括NOR和NAN ...

  10. 【Linux 内核】Linux 内核源码目录说明 ① ( arch 目录 | block 目录 | certs 目录 | crypto 目录 | Documentation 目录 )

    文章目录 一.arch 目录 二.block 目录 三.certs 目录 四.crypto 目录 五.Documentation 目录 在上一篇博客 [Linux 内核]Linux 内核源码结构 ( ...

最新文章

  1. 字符设备驱动程序 2
  2. pythontype(1+0xf*3.14)_numpy强制类型转换|图像线性增强|不同数据类型与图像的显示...
  3. MESSAGE_TYPE_X dump in RSM_DATASTATE_CHECK -6-
  4. NOS跨分区灾备设计与实现
  5. 混合云下割裂的Web安全管理挑战如何破?
  6. leetcode——给你两个非空链接表,代表两个非负整数。 数字以相反的顺序存储,并且它们的每个节点包含单个数字。 将两个数字相加,并将其作为链表返回。
  7. SQL查询学生信息表中的学生记录
  8. 中国大学MOOC(慕课)离线下载视频支持电脑播放
  9. php框架 tp laravel,TP框架和Laravel框架的区别是什么
  10. javascript解决猴子偷桃问题
  11. Koo叔说Shader-- 熟悉渲染管线
  12. 机器人设计必备的软件有哪些
  13. 关于lodop的学习小计
  14. ARCGIS 与SQL的衔接
  15. 【智能优化算法】改进的侏儒猫鼬优化算法(IDMO)附matlab代码
  16. 修改下拉状态栏点击屏幕录制后出现ANR。禁用Hotspot tethering菜单下的 “Wi-Fi hotspot。默认系统语言为英文。
  17. 开源组态HmiFuncDesigner之如何在ProjectManager新增通讯设备插件
  18. 3G门户Android面试题(2013年)
  19. 西门子PLC之间如何建立无线通讯?
  20. SQL SERVER 2005数据导入导出报“错误 0xc00470fe 数据流任务 产品级别对于 组件“源 - TestDB01$”(1) 而言不足”

热门文章

  1. 生成模型之flow-based model
  2. 【渝粤教育】电大中专营销策划原理与实务答案作业 题库
  3. java比特率转换,amr转换mp3所需的编码器、比特率、节录率、声音频道分别是什么?...
  4. warning C318: can‘t open file ‘STC15.h‘解决方法
  5. uva 10105(数论)
  6. 如何设计财务对账系统 —— 从0到1搭建对账中心实战
  7. WAV音乐文件无法修改标题
  8. Hadoop数字统计
  9. Linux led子系统分析之三 led设备驱动与ledtrigger驱动实现
  10. IOS清理缓存的几种方法