gro,将同一个flow的一定时间范围之内的skb进行合并,减少协议栈的消耗,用于收包性能提升。gro网上的资料很多,但是都很少谈到gro的改进,刚好身边有个同事也想改这块的内容,

所以将最近看的gro内容总结一下,作为记录。

gro的层次,很少有资料提到,可能是大牛们觉得太简单,但我还是记录一下,毕竟我基础不好。

先看关键的数据结构,然后分析流程:

为了在skb中记录相关的gro信息,使用了skb的cb字段。

crash>  napi_gro_cb
struct napi_gro_cb {
void *frag0;
unsigned int frag0_len;
int data_offset;
u16 flush;
u16 flush_id;
u16 count;
u16 gro_remcsum_start;
unsigned long age;
u16 proto;
u8 encap_mark : 1;
u8 csum_valid : 1;
u8 csum_cnt : 3;
u8 is_ipv6 : 1;
u8 free : 2;
u8 same_flow : 1;
u8 recursion_counter : 4;
u8 is_atomic : 1;
__wsum csum;
struct sk_buff *last;
}
SIZE: 48

48字节的cb字段,被用完了。

所有的packet 级别的gro的类型,放在一个公共链表头 offload_base 变量中管理,我测试的系统中的packet级别的gro类型有:

crash> list packet_offload.list  -H offload_base -s packet_offload
ffffffff81b41bc0
struct packet_offload {
type = ,
priority = ,
callbacks = {
gso_segment = 0xffffffff816155b0 <inet_gso_segment>,
gro_receive = 0xffffffff816159a0 <inet_gro_receive>,
gro_complete = 0xffffffff816148c0 <inet_gro_complete>
},
list = {
next = 0xffffffff81b43b40 <ipv6_packet_offload+>,
prev = 0xffffffff81b3f0e0 <offload_base>
}
}
ffffffff81b43b20
struct packet_offload {
type = ,
priority = ,
callbacks = {
gso_segment = 0xffffffff8168c670 <ipv6_gso_segment>,
gro_receive = 0xffffffff8168c300 <ipv6_gro_receive>,
gro_complete = 0xffffffff8168c120 <ipv6_gro_complete>
},
list = {
next = 0xffffffff81b3f7c0 <eth_packet_offload+>,
prev = 0xffffffff81b41be0 <ip_packet_offload+>
}
}
ffffffff81b3f7a0
struct packet_offload {
type = ,
priority = ,
callbacks = {
gso_segment = 0x0,
gro_receive = 0xffffffff815bbd60 <eth_gro_receive>,
gro_complete = 0xffffffff815bbbe0 <eth_gro_complete>
},
list = {
next = 0xffffffff81b3f0e0 <offload_base>,
prev = 0xffffffff81b43b40 <ipv6_packet_offload+>
}
}

所有的inet层的gro回调,都存储在inet_offloads 数组中,根据当前加载的模块,本机器对应支持的gro就有:

p inet_offloads
inet_offloads = $ =
{0x0, 0x0, 0x0, 0x0, 0xffffffff8176fd80 <ipip_offload>, 0x0, 0xffffffff8176f220 <tcpv4_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff8176f560 <udpv4_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff81777680 <sit_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff81770be0 <gre_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
。。。。
0x0, 0x0, 0x0}

gro的调用查找过程如下:

从dev层,根据到来的skb,可以根据skb->protocol 作为type的类型,比如type是 .type = cpu_to_be16(ETH_P_IP),然后才会进入 ip_packet_offload 这个层次,

在 offload_base这个链表头找到对应的type,然后获取对应的callback.gro_receive 函数。

找到了对应的inet_gro_receive,就进入了packet层,那么根据iph->protocol,就在 net_offload 数组中,找到对应协议类型的gro结构,比如找到的是 tcpv4_offload。

那么针对tcp的gro,其i40e驱动的调用顺序就是:

i40e_napi_poll--->|i40e_clean_tx_irq

--->|i40e_clean_rx_irq-->napi_gro_receive-->dev_gro_receive-->inet_gro_receive-->tcp4_gro_receive

对应的堆栈如下:

[root@localhost caq]# stap -d i40e netif_rx.stp
System Call Monitoring Started ( seconds)...
WARNING: DWARF expression stack underflow in CFI
0xffffffff816041a0 : tcp4_gro_receive+0x0/0x1b0 [kernel]
0xffffffff81615be9 : inet_gro_receive+0x249/0x290 [kernel]
0xffffffff815951b0 : dev_gro_receive+0x2b0/0x3e0 [kernel]
0xffffffff815955d8 : napi_gro_receive+0x38/0x130 [kernel]-------------gro处理开始
0xffffffffc01f4bde : i40e_clean_rx_irq+0x3fe/0x990 [i40e]
0xffffffffc01f5440 : i40e_napi_poll+0x2d0/0x710 [i40e]
0xffffffff81594cf3 : net_rx_action+0x173/0x380 [kernel]
0xffffffff8109404d : __do_softirq+0xfd/0x290 [kernel]
0xffffffff816c8afc : call_softirq+0x1c/0x30 [kernel]
0xffffffff8102d435 : do_softirq+0x65/0xa0 [kernel]
0xffffffff81094495 : irq_exit+0x175/0x180 [kernel]
0xffffffff816c9da6 : __irqentry_text_start+0x56/0xf0 [kernel]
0xffffffff816bc362 : ret_from_intr+0x0/0x15 [kernel]

理清楚了大的流程,我们再来看目前的gro小的流程。在收到一个skb的时候,我们把它和napi_struct中的gro_list的skb进行比较,看能否合并,当然合并的前提是同一个flow的,

除此之外,除了满足同一个flow,还有很多要求。

那这个gro_list最大多长呢?

/* Instead of increasing this, you should create a hash table. */
#define MAX_GRO_SKBS 8

才8个,这8个skb跟新进来的skb是flow相同的概率其实真不高,比如在tcp4_gro_receive中,我想跟踪它接着调用的 skb_gro_receive,无奈由于合并的几率太低而无法跟到,

毕竟还有一个在gro_list中停留的时间限制,为一个jiffies。后来修改了jiffies并且修改了合并的条件才能抓到。

当然,根据作者的注释,与其将这8改大,不如改成一个hash表,不同的skb先哈希到一个flow链,然后在链中比较看能否合并,这样对于gro流程需要改动为:

1.创建flow的hash表,让skb中看到flow,然后在flow的冲突链中找对应的gro_list,然后看能否合并。

2.percpu模式,不适用napi_struct来管理gro_list.

3.修改合并条件,比如对于tcp的ack来说,目前不带数据的ack无法合并,因为才54个字节,而以太网发出的时候会填充,导致不满足如下条件:

flush = (u16)((ntohl(*(__be32 *)iph) ^ skb_gro_len(skb)) | (id & ~IP_DF));

但对于流媒体服务器来说,纯ack占入向的比例很高,需要将条件改动,由于ack还涉及到快发流程的进入和退出,所以ack合并还是有一些工作要做的。

4.修改间隔,目前限制死了是一个jiffies,比如服务器8M左右的发送码率,收到的ack间隔可以释放放大,不然合并几率也比较低,当然这个是以tcp的send_buf中的数据占用更多内存为前提的。

所以需要一个导出到/proc文件系统的间隔字段来控制。

5.对于低速发送码率的服务器来说,可以关闭gro,对于lvs服务器来说,应该关闭gro。

linux 3.10 gro的理解和改进的更多相关文章

  1. linux io的cfq代码理解

    内核版本: 3.10内核. CFQ,即Completely Fair Queueing绝对公平调度器,原理是基于时间片的角度去保证公平,其实如果一台设备既有单队列,又有多队列,既有快速的NVME,又有 ...

  2. Linux系统启动那些事—基于Linux 3.10内核【转】

    转自:https://blog.csdn.net/shichaog/article/details/40218763 Linux系统启动那些事—基于Linux 3.10内核 csdn 我的空间的下载地 ...

  3. Linux文件系统十问---深入理解文件存储方式(rhel6.5,EXT4)【转】

    本文转载自:https://blog.csdn.net/tongyijia/article/details/52832236 前几天在红黑联盟上看了一篇博客<Linux文件系统十问—深入理解文件 ...

  4. 【转帖】linux内存管理原理深入理解段式页式

    linux内存管理原理深入理解段式页式 https://blog.csdn.net/h674174380/article/details/75453750 其实一直没弄明白 linux 到底是 段页式 ...

  5. linux 学习10 shell 基础

    10.1 Shell概述 .Shell是什么 Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动.挂起.停止甚至是编写一 ...

  6. Linux系统性能10条命令监控

    Linux系统性能10条命令监控 概述 通过执行以下命令,可以在1分钟内对系统资源使用情况有个大致的了解. uptime dmesg | tail vmstat 1 mpstat -P ALL 1 p ...

  7. 使用john破解ubuntu(linux)9.10密码

    Title:使用john破解ubuntu(linux)9.10密码 --2011-11-23 15:00 ubuntu 9.10的账户密码加密方式改用sha512了,默认的john是破不了的,还好官方 ...

  8. Linux系统管理10——进程和计划任务管理

    Linux系统管理10——进程和计划任务管理 一.程序和进程的关系 1.程序 ·保存在硬盘.光盘等介质中的可执行代码和数据 ·静态保存的代码 2.进程 ·在CPU及内存中运行的程序代码 ·动态执行的代 ...

  9. Linux 4.10.8 根文件系统制作(一)---环境搭建

    一.工具 制作工具为busybox 下载地址:https://busybox.net/ 解压: 二.制作文件系统 进入目录,执行make menuconfig: 2.1 busybox setting ...

随机推荐

  1. javaScript read blob

    http://www.cnblogs.com/wangfajing/p/7202139.html?utm_source=itdadao&utm_medium=referral

  2. Go语言 数据类型,流程控制

    Go语言 数据类型,流程控制 人生苦短,Let's Go ! package main // 必须要有一个main包 import "fmt" func main() { fmt. ...

  3. 递归&栈帧空间

    递归函数: 自己调用自己的函数 def digui(n): print(n) if n > 0: digui(n-1) print(n) digui(5) 执行结果: 5 4 3 2 1 0 0 ...

  4. 使用命名管道的OVERLAPPED方式实现非阻塞模式编程 .

    命令管道是进程间通讯的一种常用方式,对于命令管道的介绍可以参考别的资料和书籍,这里推荐一个<VC++下命名管道编程的原理及实现>这篇博文,写得比较清楚.但是都是介绍了阻塞模式的编程,我这里 ...

  5. 03 Linux的目录结构与常见文件管理

    Linux目录结构 根文件系统rootfs用来实现整个文件的管理: 而Linux下的所有文件都需要通过根文件系统才能访问: FHS:Filesystem Hierarchy Standard文件层次化 ...

  6. BZOJ 3473: 字符串 (广义后缀自动机)

    /* 广义后缀自动机, 每次加入维护 该right集合的set, 然后可以更新所有的parent,最终能够出现在k个串中right集合也就是set大小大于等于k的部分 这样的话就给了我们要跳的节点加了 ...

  7. 1-hadoop安装、ssh、节点退役与服役

    1.准备 四台虚拟机 ①卸载openjdk ②安装jdk 2. 配置静态ip: ip : 每个机器在internet上的唯一标识 子网掩码: 必须结合IP地址一起使用,将某个IP地址划分成网络地址和主 ...

  8. Ubuntu下安装virtualbox: RTR3InitEx failed with rc=-1912 (rc=-1912)

    下载好合适的安装包: http://www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html 然后进行安装,配 ...

  9. 自媒体运营排版利器----Markdown here

    Markdown Here ​ 下载chrome插件直接下载 使用:打开网页文章编辑器,比如cnblog 用markdown语法写文章,之后点击编译 可以设置好css语法,以后每次可以套用同样的模板 ...

  10. java的list遍历

    for(String str : list) {//增强for循环,其内部实质上还是调用了迭代器遍历方式,这种循环方式还有其他限制,不建议使用. System.out.println(str); } ...