linux 内核rps,Linux kernel之网络rps
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, ð->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相关推荐
- Linux内核参数(如kernel.shmmax)及Oracle相关参数调整(如SGA_MAX_SIZE)
Linux内核参数(如kernel.shmmax)及Oracle相关参数调整(如SGA_MAX_SIZE) 我们一般在Linux 上安装 设置Oracle 数据库 或者在更换或升级硬件的时候都需要配置 ...
- linux内核和发行版本的关系,简述Linux内核和Linux发行版的区别
做服务器运维工作,要经常和Linux的版本号打交道,但一直搞不明白Linux内核和Linux发行版到底是个啥东西.其实要理解Linux内核和Linux发行版之间的关系,只要能理解下面的关系就可以了: ...
- 国嵌linux内核编程,linux内核--那些年看国嵌视频学习
1.linux系统构成 由用户空间和内核空间构成.其中用户空间由应用程序和C库:内核空间由系统调用接口.kernel.架构代码.硬件设备平台 为什么linux系统会被划分为用户空间和内核空间?处于安全 ...
- 深入理解Linux内核 学习Linux内核的一些建议及书记推荐
深入理解Linux内核 学习Linux内核的一些建议_华清远见教育集团 经典书籍 待到山花烂漫时,还是那些经典在微笑. 有关内核的书籍可以用汗牛充栋来形容,不过只有一些经典的神作经住了考验.首先是5本 ...
- 一文看懂Linux内核!Linux内核架构和工作原理详解
linux内核相关视频解析: 5个方面分析linux内核架构,让你对内核不再陌生 90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理 手把手带你实现一个Linux内核 ...
- 深度:一文看懂Linux内核!Linux内核架构和工作原理详解
简介 作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址.目前支持模块的动态装卸(裁剪).Linux内核就是基于这个策略实现的.Linux进程1.采用层次结构,每个 ...
- 【Linux 内核】Linux 操作系统结构 ( Linux 内核在操作系统中的层级 | Linux 内核子系统及关系 | 进程调度 | 内存管理 | 虚拟文件系统 | 网络管理 | 进程间通信 )
文章目录 一.Linux 内核在操作系统中的层级 二.Linux 内核子系统 三.Linux 内核子系统之间的关系 一.Linux 内核在操作系统中的层级 Linux 内核 所在层级 : 整个计算机系 ...
- Linux内核和Linux发行版(了解)
Linux内核和Linux发行版(了解) Linux内核:Linux内核是一种开放源码的操作系统,由Linux Torvalds(Linux之父)负责维护,提供硬件抽象层.硬盘及文件系统控制及多任务功 ...
- 嵌入式烧写Linux内核,嵌入式linux 内核和根文件系统烧写方式简介
总体来说,嵌入式Linux内核和根文件的引导与PC机差不多. 嵌入式linux内核和根文件系统可以存放在各种可能的存储设备中,一般情况下我们将内核和根文件系统直接烧入到Flash中(包括NOR和NAN ...
- 【Linux 内核】Linux 内核源码目录说明 ① ( arch 目录 | block 目录 | certs 目录 | crypto 目录 | Documentation 目录 )
文章目录 一.arch 目录 二.block 目录 三.certs 目录 四.crypto 目录 五.Documentation 目录 在上一篇博客 [Linux 内核]Linux 内核源码结构 ( ...
最新文章
- 字符设备驱动程序 2
- pythontype(1+0xf*3.14)_numpy强制类型转换|图像线性增强|不同数据类型与图像的显示...
- MESSAGE_TYPE_X dump in RSM_DATASTATE_CHECK -6-
- NOS跨分区灾备设计与实现
- 混合云下割裂的Web安全管理挑战如何破?
- leetcode——给你两个非空链接表,代表两个非负整数。 数字以相反的顺序存储,并且它们的每个节点包含单个数字。 将两个数字相加,并将其作为链表返回。
- SQL查询学生信息表中的学生记录
- 中国大学MOOC(慕课)离线下载视频支持电脑播放
- php框架 tp laravel,TP框架和Laravel框架的区别是什么
- javascript解决猴子偷桃问题
- Koo叔说Shader-- 熟悉渲染管线
- 机器人设计必备的软件有哪些
- 关于lodop的学习小计
- ARCGIS 与SQL的衔接
- 【智能优化算法】改进的侏儒猫鼬优化算法(IDMO)附matlab代码
- 修改下拉状态栏点击屏幕录制后出现ANR。禁用Hotspot tethering菜单下的 “Wi-Fi hotspot。默认系统语言为英文。
- 开源组态HmiFuncDesigner之如何在ProjectManager新增通讯设备插件
- 3G门户Android面试题(2013年)
- 西门子PLC之间如何建立无线通讯?
- SQL SERVER 2005数据导入导出报“错误 0xc00470fe 数据流任务 产品级别对于 组件“源 - TestDB01$”(1) 而言不足”
热门文章
- 生成模型之flow-based model
- 【渝粤教育】电大中专营销策划原理与实务答案作业 题库
- java比特率转换,amr转换mp3所需的编码器、比特率、节录率、声音频道分别是什么?...
- warning C318: can‘t open file ‘STC15.h‘解决方法
- uva 10105(数论)
- 如何设计财务对账系统 —— 从0到1搭建对账中心实战
- WAV音乐文件无法修改标题
- Hadoop数字统计
- Linux led子系统分析之三 led设备驱动与ledtrigger驱动实现
- IOS清理缓存的几种方法