linux协议栈学习 第七节 GRO的实现

GRO (generic receive offload)

  • 概述:

    GRO是在协议栈接收报文时进行减负的一种处理方式,该方式在设计上考虑了多种协议报文。主要原理是在接收端通过把多个相关的报文(比如TCP分段报文)组装成一个大的报文后再传送给协议栈进行处理,因为内核协议栈对报文的处理都是对报文头部进行处理,如果相关的多个报文合并后只有一个报文头,这样就减少了协议栈处理报文个数,加快协议栈对报文的处理速度。

    GRO功能对到本机的报文能起到一定的加速作用,但如果linux 运行在转发设备上,一般不需要使用GRO功能,这时使用GRO功能反而会降低处理速度。

    GRO功能和只是针对NAPI类型的驱动,网卡驱动支持GRO要调用内核提供的GRO函数进行收包。并且该功能和网络设备硬件无关,是纯软件实现的。

    设计需求:

    1、需要根据一定的规则进行报文的合并。需要支持各种协议的合并规则。linux实现中是要求支持GRO功能的协议自己实现自己的合并函数,gro根据报文类型调用相应的合并函数。

    2、对等待合并的报文应该进行缓存。等合并好后再送进协议栈进行处理。linux实现中在每个NAPI实例中放有一个等待合并的skb队列gro_list。

    数据结构:

    1、NAPI中GRO相关的字段:

    view sourceprint?
    1.struct napi_struct 
    2.
    3.    unsigned int    gro_count;  //gro_list 上挂的skb 个数。 
    4.    struct sk_buff  *gro_list; //等待合并的skb 链表 
    5.}

    2、每个协议中定义自己的GRO接收合并函数和合并后处理函数。

    接收合并函数定义:

    struct sk_buff**(*gro_receive)(struct sk_buff **head,struct sk_buff *skb);

    参数:

    head:等待合并的skb链表头

    skb:接收到的skb。

    返回值:

    如果为空,表示报文被合并后不需要现在送入协议栈。

    如果不为空,表示返回的报文需要立即送入协议栈。

    合并后处理函数定义:

    int(*gro_complete)(struct sk_buff *skb);

    该函数对合并好的报文进行进一步加工,比如更新校验和。

    3、GRO功能使用skb结构体内私有空间cb[48]来存放gro所用到的一些信息。

    定义结构体struct napi_gro_cb

    view sourceprint?
    01.struct   napi_gro_cb 
    02.
    03.    /*指向存在skb_shinfo(skb)->frag[0].page页的数据的头部, 
    04.      GRO使用过程中,如果skb是线性的,就置为空。 
    05.      如果是非线性的并且报文头部全部存在非线性区中, 
    06.      就指向页中的数据起始部分 
    07.    */
    08.    void *frag0; 
    09.                                                                                                                                                                                                                                                                                                                                 
    10.    /*第一页中数据的长度,如果frag0 字段不为空, 
    11.      就设置该字段,否则为0。( Length of frag0.) 
    12.    */
    13.    unsigned int frag0_len; 
    14.                                                                                                                                                                                                                                                                                                                                  
    15.    /*This indicates where we are processing 
    16.      relative to skb->data. 
    17.      表明skb->data到GRO需要处理的数据区的偏移量。 
    18.      因为在GRO合并处理过程中skb->data是不能被改变的, 
    19.      所以需要使用该字段来记录一下偏移量。 
    20.      GRO处理过程中根据该记录值快速找到要处理的数据部分。 
    21.      比如进入ip层进行GRO处理,这时skb->data指向ip 头, 
    22.      而ip层的gro 正好要处理ip头,这时偏移量就为0. 
    23.      进入传输层后进行GRO处理,这时skb->data还指向ip头, 
    24.      而tcp层gro要处理tcp头,这时偏移量就是ip头部长度。 
    25.    */
    26.    int data_offset; 
    27.                                                                                                                                                                                                                                                                                                                                  
    28.    /*This is non-zero if the packet may be of the same flow. 
    29.      标记挂在napi->gro_list上的报文是否跟现在的报文进行匹配。 
    30.      每层的gro_receive都设置该标记位。 
    31.      接收到一个报文后,使用该报文和挂在napi->gro_list 上 
    32.      的报文进行匹配。 
    33.      在链路层,使用dev 和 mac头进行匹配,如果一样表示两个报文是通一个 
    34.      设备发过来的,就标记napi->gro_list上对应的skb的same为1. 
    35.      到网络层,再进一步进行匹配时,只需跟napi->list上刚被链路层标记 
    36.      same为1的报文进行网络层的匹配即可,不需再跟每个报文进行匹配。 
    37.      如果网络层不匹配,就清除该标记。 
    38.      到传输层,也是只配置被网络层标记same为1 的报文即可。 
    39.      这样设计为的是减少没必要的匹配操作 
    40.    */
    41.    int same_flow; 
    42.                                                                                                                                                                                                                                                                                                                                  
    43.    /*This is non-zero if the packet cannot be merged 
    44.      with the new skb. 
    45.      如果该字段不为0,表示该数据报文没必要再等待合并, 
    46.      可以直接送进协议栈进行处理了 
    47.    */
    48.    int flush; 
    49.                                                                                                                                                                                                                                                                                                                                           
    50.    /*该报文被合并过的次数 ,Number of segments aggregated. */
    51.    int count; 
    52.                                                                                                                                                                                                                                                                                                                                  
    53.    /* Free the skb? ,是否该被丢弃*/
    54.    int free
    55.}; 
    56.#define NAPI_GRO_CB(skb) ((struct napi_gro_cb *)(skb)->cb)

    NAPI_GRO_CB(skb) 的初始化:

    skb_reset_offset() 来重置gro 的 cb区域。如果是skb非线性的,并且本身不包含数据(包括头也没有),而所有的数据都保存在skb_shared_info中(支持S/G的网卡有可能会这么做)。

    因为合并报文时需要报文头的信息,这时报文头是存在skb_shared_info的frags[0]中的,我们使用指针指向正确的报文头部。

    view sourceprint?
    01.void skb_gro_reset_offset(struct sk_buff *skb) 
    02.
    03.    NAPI_GRO_CB(skb)->data_offset = 0; 
    04.    NAPI_GRO_CB(skb)->frag0 = NULL; 
    05.    NAPI_GRO_CB(skb)->frag0_len = 0; 
    06.                                                                                                                                                                                                                                 
    07.    /*如果skb 不包括数据并且skb_shinfo(skb)->frags[0].page 不在 
    08.      高端内存中,表示报文头存在skb_shinfo(skb)->frags[0].page中 
    09.    */
    10.    if (skb->mac_header == skb->tail && 
    11.        !PageHighMem(skb_shinfo(skb)->frags[0].page)) 
    12.    
    13.        NAPI_GRO_CB(skb)->frag0 = 
    14.            page_address(skb_shinfo(skb)->frags[0].page) + 
    15.            skb_shinfo(skb)->frags[0].page_offset; 
    16.        NAPI_GRO_CB(skb)->frag0_len = 
    17.            skb_shinfo(skb)->frags[0].size; 
    18.    
    19.}

    GRO在如下地方将报文送进协议栈进行处理:

    1、当napi的循环执行完毕时,也就是执行napi_complete的时候,调用napi_gro_flush来把能送协议栈的报文送给协议栈。一般调用napi_complete时,是NAPI一次轮询就处理完了全部的报文,这时短期内网卡可能不会进行报文的接收,所有要把napi->gro_list上的报文都送到协议栈,不用再等待合并后再送了。

    view sourceprint?
    01.void napi_complete(struct napi_struct *n) 
    02.
    03.    ...... 
    04.    /*把napi->gro_list上的所有报文调用napi_gro_complete都 
    05.      送给协议栈,并清空grp_list 
    06.    */
    07.    napi_gro_flush(n); 
    08.    ...... 
    09.
    10.void napi_gro_flush(struct napi_struct *napi) 
    11.
    12.    struct sk_buff *skb, *next; 
    13.    for (skb = napi->gro_list; skb; skb = next) 
    14.    
    15.        next = skb->next; 
    16.        skb->next = NULL; 
    17.        napi_gro_complete(skb); 
    18.    
    19.    napi->gro_count = 0; 
    20.    napi->gro_list = NULL; 
    21.}

    2、在napi_skb_finish里,他会通过判断__napi_gro_receive的返回值,来决定是需要将数据包立即送进进协议栈还是保存起来。

    view sourceprint?
    01.int napi_skb_finish(int ret, struct sk_buff *skb) 
    02.
    03.    int err = NET_RX_SUCCESS; 
    04.    switch (ret) 
    05.    
    06.        /*如果返回NORMAL,就把报文送给协议栈进行处理*/
    07.        case GRO_NORMAL: 
    08.            return netif_receive_skb(skb); 
    09.        /*如果报文经过检查被丢弃了,释放内存并直接返回*/
    10.        case GRO_DROP: 
    11.            err = NET_RX_DROP; 
    12.                                                                                                                                                                                
    13.       /*如果报文被合并了,这时报文已经被copy走了, 
    14.         释放该报文占用的内存 
    15.        */
    16.        case GRO_MERGED_FREE: 
    17.            kfree_skb(skb); 
    18.            break
    19.    
    20.    return err; 
    21.}

    GRO 的收包函数:

    支持GRO功能的网卡驱动必须支持NAPI接口并调用GRO的专用接收函数napi_gro_receive()来把报文送给协议栈进行处理。

    view sourceprint?
    1.int napi_gro_receive(struct napi_struct *napi, 
    2.                     struct sk_buff *skb) 
    3.
    4.    skb_gro_reset_offset(skb); 
    5.    return napi_skb_finish(__napi_gro_receive(napi, skb), skb); 
    6.}

    napi_skb_finish根据__napi_gro_receive(napi, skb)函数返回的结果,来处理报文。如果合并完成或不需要gro处理,返回GRO_NORMAL。

    __napi_gro_receive()算是链路层上实现的gro_receive函数,详解见下文。

linux协议栈学习 第七节 GRO的实现相关推荐

  1. linux 协议栈学习 第八节 链路层GRO的处理

    linux 协议栈学习 第八节 链路层GRO的处理 链路层的接收匹配函数__napi_gro_receive(napi, skb): 该函数对报文进行匹配,并不合并报文. 匹配规则必须同时满足以下两个 ...

  2. Linux内核学习(七):linux kernel内核启动(一):概述篇

    Linux内核学习(七):linux kernel内核启动(一):概述篇 这一篇让我们来大致的了解一下Linux内核的启动过程 这篇文章不涉及源码,重在让你知道这个linux内核的启动过程,源码详细的 ...

  3. Vue.js实战之系统学习第七节

    想看上一节的请点击: Vue.js实战之系统学习第六节 接下来我们要学习第七节了,时间过的好快. 组件详解 组件是Vue.js的核心功能,也是整个框架设计最精彩的地方,当然也是最难掌握的.本章节将带你 ...

  4. linux 网卡gso,linux内核网络协议栈学习笔记:关于GRO/GSO/LRO/TSO等patch的分析和测试...

    TSO,全称是TCP Segmentation Offload,我们知道通常以太网的MTU是1500,除去TCP/IP的包头,TCP的MSS (Max Segment Size)大小是1460,通常情 ...

  5. linux初级学习笔记七:linux用户管理,密码和组命令详解!(视频序号:04_1)

    本节学习的命令: 用户管理命令:useradd,userdel,id,finger,usermod,chsh,chfn,passwd,pwck, 组管理命令:groupadd,groupmod,gro ...

  6. linux回炉学习(七)

    Linux回炉重造(七) 1.Ubuntu 安装ubuntu成功后,都是普通用户权限,并没有最高root权限,如果需要使用root 权限的时候,通常都会在命令前面加上 sudo . 我们一般使用su命 ...

  7. linux多线程学习(七)——实现“生产者和消费者”

    在上一篇文章中,利用信号量实现了线程间的互斥,这一篇将要利用信号量的互斥同步机制来实现一个经典实例,就是"生产者和消费者". 1.简单描述生产者和消费者的问题. 有一个缓冲区和两个 ...

  8. Linux网络学习第七部分:ip协议+数据链路层详解

    IP协议与TCP协议的关系 上一部分我们详细的讲了TCP的发送与接收,但事实上数据并不是从传输层TCP直接发送到对端主机的传输层TCP缓冲区,而是需要经过网络层ip协议的加工,在经过数据链路层加工(后 ...

  9. BurpSuite学习第七节--Sequencer+Decoder

    由于剩下几个模块核心内容较少,因此一次介绍两个模快 一.Sequencer的介绍 Burp Sequencer是一种用于分析数据项样本中随机性质的工具.您可以使用它来测试应用程序的会话令牌或其他意图不 ...

最新文章

  1. 教你识别:虚拟内存和物理内存的区别
  2. 欢迎大家讨论:只想要一个最简单的XXX系统,功能最简化的XXX系统
  3. 网络请求中的cookie与set-Cookie的交互模式和作用
  4. python如何创建问答窗口_在tkin中创建一个新的单独窗口
  5. zincrby redis python_【Redis数据结构 序】使用redis-py操作Redis数据库
  6. 供应链商品域DDD实践
  7. 【浏览器】浏览器下载CSV文件的方法
  8. js实现螺旋矩阵算法
  9. ftp文件传输 vscode_在vscode中配置ftp
  10. 列出搜索过的数据(类似京东顶部搜索框)
  11. NGUI的输入框制作(attach- input filed script的使用)
  12. C语言编写游戏的程序教程,用C语言写贪吃蛇游戏图文教程
  13. oracle 客户端配置
  14. 如何在亿级数据中判断一个元素是否存在?
  15. 融云 java_融云开发者文档
  16. 高压直流电源为什么要“接地”?如何“接地”?
  17. 病毒木马查杀实战第014篇:U盘病毒之手动查杀
  18. 有哪些实用的图片去水印的方法?这三个方法让你实现去图片水印
  19. php中如何导出表格,PHP如何实现表格Excel的导出
  20. HDU2159.FATE-完全背包

热门文章

  1. 前端—react项目及版本更新对比
  2. AVUE一些简单用法——avue-crud子表单
  3. 什么是seo、vue中如何优化seo ?
  4. 垃圾图像分类 ResNet34 python
  5. 小心!如果遇到这些情况,你的流量可能被“劫持”了!
  6. MySQL BETWEEN 语法
  7. (Note)HTTP常见状态码(Status Code)
  8. 关于Proteus的复制粘贴快键
  9. [已解决]显示器分屏只能复制无法扩展
  10. ViewPager、Fragment和TabLayout实现切页效果