Open vSwitch系列之数据结构解析深入分析ofpbuf

news2024/11/26 14:45:46

上一篇我们分析了hmap,hamp可以说是Open vSwitch中基石结构,很多Open vSwitch中数据结构都依赖hmap。本篇我们来分析一下ofpbuf,这个结构,我们从名字上就可得知,此数据结构用于存储数据的,比如收发OpenFlow报文。 我们首先来看一下,它数据结构定义。(有些内容我是直接写在代码注释中的)

/* Buffer for holding arbitrary data.  An ofpbuf is automatically reallocated
 * as necessary if it grows too large for the available memory.
 *
 * 'frame' and offset conventions:
 *
 * Network frames (aka "packets"): 'frame' MUST be set to the start of the
 *    packet, layer offsets MAY be set as appropriate for the packet.
 *    Additionally, we assume in many places that the 'frame' and 'data' are
 *    the same for packets.
 *
 * OpenFlow messages: 'frame' points to the start of the OpenFlow
 *    header, while 'l3_ofs' is the length of the OpenFlow header.
 *    When parsing, the 'data' will move past these, as data is being
 *    pulled from the OpenFlow message.
 *
 * Actions: When encoding OVS action lists, the 'frame' is used
 *    as a pointer to the beginning of the current action (see ofpact_put()).
 *
 * rconn: Reuses 'frame' as a private pointer while queuing.
 */

struct ofpbuf {//这个有一个预编译,为了简单起见,我们认为DPDK_NETDEV宏无效(关于DPDK网上有很多资料)。

#ifdef DPDK_NETDEV
    struct rte_mbuf mbuf;       /* DPDK mbuf */
#else
    void *base_;                 /* First byte of allocated space. 指向内存申请的起始位置。释放内存时候此变量传给free */
    void *data_;                 /* First byte actually in use. 指向当前可用内存起始位置。最开始base_和data_ 是一样的 */
    uint32_t size_;              /* Number of bytes in use. 表示内存已经使用的字节数 当size_ = allocated时候表示内存用完。 */
#endif
    uint32_t allocated;         /* Number of bytes allocated. 表示从系统中申请的内存块大小*/
    void *frame;                /* Packet frame start, or NULL. 这个字段可参考上面注释*/
    uint16_t l2_5_ofs;          /* MPLS label stack offset from 'frame', or
                                 * UINT16_MAX 2.5层 偏移量 */
    uint16_t l3_ofs;            /* Network-level header offset from 'frame',
                                  or UINT16_MAX. 3层网络层 偏移量*/
   uint16_t l4_ofs; /* Transport-level header offset from 'frame',
                                  or UINT16_MAX. 4层传输层 偏移量*/
    enum ofpbuf_source source;  /* Source of memory allocated as 'base'. 表示该内存来自堆、栈,主要用于内存释放。取值为ofpbuf_source枚举*/
    struct list list_node;      /* Private list element for use by owner. 链表节点。 用于将多个ofpbuf关联在一起 */
};
//枚举类型
enum OVS_PACKED_ENUM ofpbuf_source {
    OFPBUF_MALLOC,              /* Obtained via malloc(). */
    OFPBUF_STACK,               /* Un-movable stack space or static buffer. */
    OFPBUF_STUB,                /* Starts on stack, may expand into heap. */
    OFPBUF_DPDK,                /* buffer data is from DPDK allocated memory.
                                   ref to build_ofpbuf() in netdev-dpdk. */
};

下面是可能的存储结构图:

上图表示,分配16个字节空间,灰色部分为预留空间(4字节),蓝色为占用空间(5个字节),白色为剩余可用空间(7个字节)。

数据结构相对简单,我们看一下主要函数。由代码中的注释可知,数据结构ofpbuf支持内存空间自动扩充,可以理解为简单内存池。为了深入就理解ofpbuf,我们选择一个从堆中申请内存的例子(Test-sflow.c)进行分析(因为其他内存类型是不需要释放空间的),如下所示:

static void
test_sflow_main(int argc, char *argv[])
{
....
struct ofpbuf buf;
....
ofpbuf_init(&buf, MAX_RECV);
for (;;) {
    int retval;
    unixctl_server_run(server);
    ofpbuf_clear(&buf);
    do {
        retval = read(sock, ofpbuf_data(&buf), buf.allocated);
    } while (retval < 0 && errno == EINTR);
    if (retval > 0) {
       ofpbuf_put_uninit(&buf, retval);
       print_sflow(&buf);
       fflush(stdout);
    }
    if (exiting) {
        break;
    }
    poll_fd_wait(sock, POLLIN);
    unixctl_server_wait(server);
    poll_block();
  }//for exit
}

1、初始化opfbuf结构 我们可以先申请一个局部变量,然后将该变量地址和要申请的大小传给函数ofpbuf_init(OpenvSwitch代码好处是每个函数都是很小,耐心钻研一定可以看懂)。我们来看一下函数调用关系:

ofpbuf初始化流程,经过的函数依次是,ofpbuf_init,ofpbuf_use,ofpbuf_use__,ofpbuf_init__。(函数命名中最后是两个下划线代表是静态函数)现在我们来看一下各个函数实现。

static void
ofpbuf_init__(struct ofpbuf *b, size_t allocated, enum ofpbuf_source source)
{
    b->allocated = allocated;//设置申请的内存大小 即内存块大小
    b->source = source;//内存的类型,当前实例是malloc类型
    b->frame = NULL;
    b->l2_5_ofs = b->l3_ofs = b->l4_ofs = UINT16_MAX;
    list_poison(&b->list_node);
}
static void
ofpbuf_use__(struct ofpbuf *b, void *base, size_t allocated,
             enum ofpbuf_source source)
{
    ofpbuf_set_base(b, base);//设置base
    ofpbuf_set_data(b, base);//设置data  此时base和data保存的都是内存起始位置,只不过是data会变化,base不变
    ofpbuf_set_size(b, 0);//设置已经使用的内存大小 起初为0

    ofpbuf_init__(b, allocated, source);
}
/* Initializes 'b' as an empty ofpbuf that contains the 'allocated' bytes of
* memory starting at 'base'.  'base' should be the first byte of a region
* obtained from malloc().  It will be freed (with free()) if 'b' is resized or
* freed. */
void
ofpbuf_use(struct ofpbuf *b, void *base, size_t allocated)
{
    ofpbuf_use__(b, base, allocated, OFPBUF_MALLOC);//内存类型为malloc类型
}
/* Initializes 'b' as an empty ofpbuf with an initial capacity of 'size'
* bytes. */
void
ofpbuf_init(struct ofpbuf *b, size_t size)
{
    ofpbuf_use(b, size ? xmalloc(size) : NULL, size);
}

上面是初始化操作流程,逻辑和内容十分简单。我们现在来看一下put操作,即增加内存空间。在介绍put操作之前,我们先来看四个工具函数,也是非常小的函数:

/* Returns the byte following the last byte of data in use in 'b'.
* 返回第一个可存储数据地址 针对上图返回值是 (0x832200C + 5)
*/
static inline void *ofpbuf_tail(const struct ofpbuf *b)
{
    return (char *) ofpbuf_data(b) + ofpbuf_size(b); /* data_ 指向数据报文起始位置,即上面蓝色开始位置 */
}
/* Returns the byte following the last byte allocated for use (but not
* necessarily in use) by 'b'.
* <span style="font-family: Arial, Helvetica, sans-serif;">返回内存区最后一个字节地址  针对上图返回值为 (0x8322008 + 16)</span>
*/
static inline void *ofpbuf_end(const struct ofpbuf *b)
{
    return (char *) ofpbuf_base(b) + b->allocated; /* base_ 指向内存区起始位置 */
}
/* Returns the number of bytes of headroom in 'b', that is, the number of bytes
* of unused space in ofpbuf 'b' before the data that is in use.  (Most
* commonly, the data in a ofpbuf is at its beginning, and thus the ofpbuf's
* headroom is 0.)
* 头部剩余空间大小。 直接用data_ - base_ 就可以得到。
*/
static inline size_t ofpbuf_headroom(const struct ofpbuf *b)
{
    return (char*)ofpbuf_data(b) - (char*)ofpbuf_base(b);
}
/* Returns the number of bytes that may be appended to the tail end of ofpbuf
* 'b' before the ofpbuf must be reallocated.
* 尾部剩余空间大小。
*/
static inline size_t ofpbuf_tailroom(const struct ofpbuf *b)
{
    return (char*)ofpbuf_end(b) - (char*)ofpbuf_tail(b);
}

下面就是扩大内存的具体函数:

/* Appends 'size' bytes of data to the tail end of 'b', reallocating and
* copying its data if necessary.  Returns a pointer to the first byte of the
* new data, which is left uninitialized.
* 扩大size大小内存空间,但是不初始化
*/
void *
ofpbuf_put_uninit(struct ofpbuf *b, size_t size)
{
    void *p;
    ofpbuf_prealloc_tailroom(b, size); /* 在尾部,扩大内存 */
    p = ofpbuf_tail(b); /* 扩展内存后,保存第一个可用内存地址 */
    ofpbuf_set_size(b, ofpbuf_size(b) + size); /* 设置已用内存空间大小 */
    return p;
}
/* Appends 'size' zeroed bytes to the tail end of 'b'.  Data in 'b' is
* reallocated and copied if necessary.  Returns a pointer to the first byte of
* the data's location in the ofpbuf.
* 扩大size大小内存空间,初始化为0
*/
void *
ofpbuf_put_zeros(struct ofpbuf *b, size_t size)
{
    void *dst = ofpbuf_put_uninit(b, size);
    memset(dst, 0, size);
    return dst;
}
/* Appends the 'size' bytes of data in 'p' to the tail end of 'b'.  Data in 'b'
* is reallocated and copied if necessary.  Returns a pointer to the first
* byte of the data's location in the ofpbuf.
* 扩大size大小内存空间,用p进行初始化
*/
void *
ofpbuf_put(struct ofpbuf *b, const void *p, size_t size)
{
    void *dst = ofpbuf_put_uninit(b, size);
    memcpy(dst, p, size);
    return dst;
}

这三个函数功能都是类似的,在原有ofpbuf结构b中增大size大小的内存空间。 函数ofpbuf_put_uninit会被其他两个函数调用。我来分析一下这个函数。

ofpbuf_prealloc_tailroom 在尾部扩展内存,这个函数逻辑也是很简单

/* Returns the number of bytes that may be appended to the tail end of ofpbuf
* 'b' before the ofpbuf must be reallocated.
* 返回可用内存空间,即上图中白色空间大小
*/
static inline size_t ofpbuf_tailroom(const struct ofpbuf *b)
{
    return (char*)ofpbuf_end(b) - (char*)ofpbuf_tail(b);
}
/* Reallocates 'b' so that it has exactly 'new_headroom' and 'new_tailroom'
* bytes of headroom and tailroom, respectively.
* 内存扩充函数  我们只关注malloc的内存 即红色部分
*/
static void
ofpbuf_resize__(struct ofpbuf *b, size_t new_headroom, size_t new_tailroom)
{
    void *new_base, *new_data;
    size_t new_allocated;
    new_allocated = new_headroom + ofpbuf_size(b) + new_tailroom;
    switch (b->source) {
    case OFPBUF_DPDK:
        OVS_NOT_REACHED();
        case OFPBUF_MALLOC:
        if (new_headroom == ofpbuf_headroom(b)) {//调用realloc申请内存
           new_base = xrealloc(ofpbuf_base(b), new_allocated);
        } else {
            new_base = xmalloc(new_allocated);//调用malloc申请内存并且修改ofpbuf中相关数据
            ofpbuf_copy__(b, new_base, new_headroom, new_tailroom); /* 将数据复制到新的内存空间中 需要注意头部剩余空间和使用空间。*/
            free(ofpbuf_base(b));
        }
        break;</span>
    case OFPBUF_STACK:
        OVS_NOT_REACHED();
    case OFPBUF_STUB:
        b->source = OFPBUF_MALLOC;
        new_base = xmalloc(new_allocated);
        ofpbuf_copy__(b, new_base, new_headroom, new_tailroom);
        break;
    default:
        OVS_NOT_REACHED();
    }
    // 重新设置allocated和base_ 指针
    b->allocated = new_allocated;
    ofpbuf_set_base(b, new_base);
    // 重新设置data_ 指针
    new_data = (char *) new_base + new_headroom;
    if (ofpbuf_data(b) != new_data) {
        if (b->frame) {
            uintptr_t data_delta = (char *) new_data - (char *) ofpbuf_data(b);
            b->frame = (char *) b->frame + data_delta;
        }
        ofpbuf_set_data(b, new_data);
    }
}
/* Ensures that 'b' has room for at least 'size' bytes at its tail end,
* reallocating and copying its data if necessary.  Its headroom, if any, is
* preserved.
* 尾部扩充内存 首先需要判断剩余内存是否满足需求,如果size大于剩余可用空间则需要重新申请内存
* 为了避免内存碎片和快速申请,每次至少申请64字节
*/
void
ofpbuf_prealloc_tailroom(struct ofpbuf *b, size_t size)
{
    if (size > ofpbuf_tailroom(b)) {
        ofpbuf_resize__(b, ofpbuf_headroom(b), MAX(size, 64));
    }
}

与pull对应函数是push,此类函数主要是在头部扩充内存,这里我们不在进行讨论。函数ofpbuf_pull主要增大灰色空间大小,即将蓝色区域向后移动size大小。

/* Removes 'size' bytes from the head end of 'b', which must contain at least
* 'size' bytes of data.  Returns the first byte of data removed. */
static inline void *ofpbuf_pull(struct ofpbuf *b, size_t size)
{
    void *data = ofpbuf_data(b);
    ovs_assert(ofpbuf_size(b) >= size);
    ofpbuf_set_data(b, (char*)ofpbuf_data(b) + size);
    ofpbuf_set_size(b, ofpbuf_size(b) - size);
    return data;
}

最后我们来看一下释放函数,这个函数也是非常简单的。

/* Frees memory that 'b' points to. */
void
ofpbuf_uninit(struct ofpbuf *b)
{
    if (b) {
        if (b->source == OFPBUF_MALLOC) {
            free(ofpbuf_base(b));
        }
        ovs_assert(b->source != OFPBUF_DPDK);
    }
}

上面就是本博客主要介绍的内存,ofpbuf相对简单,下面我们会分析Open vSwitch会话相关的数据结构struct connmgr,struct ofconn,struct ofproto等,这部分数据结构属于Open vSwitch管理层。对于学习Open vSwitch是非常重要。

原文链接:https://cloud.tencent.com/developer/article/1082501

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1543_AURIX_TC275_CPU子系统_CPU内核实现特性

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这个章节看的信息应该是针对内核设计实现上TC275的具体实现特点&#xff0c;应该是覆盖了很多内核中的实施相关的特性的。 1. 在上下文功能支持上&#xff0c;P和更灵活一些。E核只支持DSP…

[附源码]Python计算机毕业设计SSM辽宁科技大学二手车交易平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

非常强,批处理框架 Spring Batch 就该这么用!(场景实战)

前言 概念词就不多说了&#xff0c;我简单地介绍下 &#xff0c; spring batch 是一个 方便使用的 较健全的 批处理 框架。 为什么说是方便使用的&#xff0c;因为这是 基于spring的一个框架&#xff0c;接入简单、易理解、流程分明。 为什么说是较健全的&#xff0c; 因为它…

NVIDIA 7th SkyHackathon(八)使用 Flask 与 Vue 开发 Web

1.页面效果 Web 采用 flaskvue 开发&#xff0c;效果图如下 2.后端 import sys import subprocess import os from PIL import Image from datetime import datetime from ASR_metrics import utils as metricsfrom werkzeug.wrappers import Request, Response from …

卡尔曼滤波:过滤随机游走

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 我们知道&#xff0c;随机游走序列是到当前时间为止白噪声实现(white noise realization)的简单求和。换句话说&#xff0c;随机游走序列中的对下一个时间点值的估计&#xff08;预测&#xff09;是通…

为什么程序员买不起房子?

很多人都说程序员的收入那么高&#xff0c;为什么程序员还是买不起房呢&#xff1f;其实不是程序员不想买&#xff0c;是真的买不起…… 拿北京为例。北京的房价可谓是越来越奇葩&#xff0c;不仅仅是因为银行政策导致贷款越来越难审批下来&#xff0c;更重要的是进入地产市场…

音频3A算法详解

一、音频3A技术背景 手机电脑等智能设备的普及,AI智能、5G等技术的不断发展,语音通信成为了最方便快捷的远程交流方式,会议全向麦克风、会议一体化终端等会议系统逐渐成为企业经营中的重要工具。语音质量决定了企业远程沟通协作效果,其中噪音和回声能够严重影响到语音预处理…

图扑软件获评 2022 年“火炬瞪羚企业”

厦门火炬高新区 2022 年“火炬瞪羚企业”名单公布&#xff0c;图扑软件经过层层遴选&#xff0c;成功入围&#xff0c;获评 2022 年“火炬瞪羚企业”称号。 在 2021 年&#xff0c;图扑软件已经凭借领先的技术实力和发展潜力&#xff0c;入选福建省数字经济领域“瞪羚”创新企业…

连续学习入门(二):连续学习的三种类型

说明&#xff1a;本系列文章若无特别说明&#xff0c;则在技术上将 Continual Learning&#xff08;连续学习&#xff09;等同于 Incremental Learning&#xff08;增量学习&#xff09;、Lifelong Learning&#xff08;终身学习&#xff09;&#xff0c;关于 Continual Learni…

Express操作MongoDB【一.Express框架通过Mongoose模块操作MongoDB数据库;二.在接口中间件中使用Mongoose模块】

目录 一.Express框架通过Mongoose模块操作MongoDB数据库 1.MongoDB数据库&#xff1a; &#xff08;1&#xff09;存放数据的格式&#xff1a;key:value &#xff08;2&#xff09;数据库&#xff08;database&#xff09;---集合&#xff08;collection&#xff09;---文档…

使用vite 搭建vue 3的项目

一、目标&#xff1a; 使用vite搭建一个Vue 3 的项目&#xff0c;并启动成功。 二、准备工作 首先你要有Node.js、VSCode编辑器、Chrome浏览器 关于下载的问题Node.js可以去官网下载 链接&#xff1a;http://nodejs.cn/download/ 下载左边的长期支持版本就好&#xff0c;最新版…

基于Haar-Like特征的人脸检测算法研究-附Matlab代码

⭕⭕ 目 录 ⭕⭕✳️ 一、引言✳️ 二、Haar-Like 特征✳️ 三、人脸检测实验验证✳️ 四、参考文献✳️ 五、Matlab代码获取✳️ 一、引言 脸是每个人最重要的外貌特征&#xff0c;随着科技推动社会不断向前发展&#xff0c;人脸识别也逐渐融入人们的生活中&#xff0c;例如在…

活动预告丨EMNLP 2022半监督和强化对话系统研讨会12月7日线上召开!

由清华大学和中国移动联合承办的EMNLP 2022 SereTOD Workshop “Towards Semi-Supervised and Reinforced Task-Oriented Dialog Systems&#xff08;迈向半监督和强化的任务型对话系统&#xff09;”&#xff0c;即将与EMNLP 2022主会同步举办。因受疫情影响&#xff0c;研讨会…

【蓝桥备战】前缀和+差分+高精度

文章目录前缀和差分大整数加减乘除前缀和 前缀和&#xff0c;即preSum[i] nums[i-1] nums[i-2] nums[0]。一般地&#xff0c;我们会让preSum[0] 0。 图&#xff1a;preSum[3] nums[2] nums[1] nums[0]。 构造前缀和数组对我们来说是简单的&#xff0c;只需要会用以下…

c++中的内存分区模型

内存分区模型 c程序在执行时&#xff0c;将内存大方向划分为4个区域1、代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理2、全局区&#xff1a;存放全局变量和静态变量以及常量3、栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数值&…

手把手教你使用Vue3指定状态管理库--Pinia

什么是 Pinia Pinia 与 Vuex 一样&#xff0c;是作为 Vue 的“状态存储库”&#xff0c;用来实现 跨页面/组件 形式的数据状态共享。 在平时的开发过程中&#xff0c;Vue 组件之间可以通过 Props 和 Events 实现组件之间的消息传递&#xff0c;对于跨层级的组件也可以通过 Ev…

Java并发编程实战读书笔记三

第七章 取消和关闭 Java没有提供任何机制来安全的终止线程&#xff0c;虽然 Thread.stop 和 suspend 等方法提供了这样的机制&#xff0c;但由于存在着一些严重的陷&#xff0c;因此应该避免使用 7.1任务取消 7.1.1 中断 取消任务中生产者使用了队列的put操作导致阻塞后任务…

day14_类中成员之构造器

由来 我们发现我们new完对象时&#xff0c;所有成员变量都是默认值&#xff0c;如果我们需要赋别的值&#xff0c;需要挨个为它们再赋值&#xff0c;太麻烦了。我们能不能在new对象时&#xff0c;直接为当前对象的某个或所有成员变量直接赋值呢。可以&#xff0c;Java给我们提…

工作两年,没想到靠Python搞副业让我实现了财务自由

前言 国庆假期和好友聚会&#xff0c;聊了各自近两年的变化&#xff0c;朋友的经历让我大吃一惊&#xff01; 2年前他还是月薪5千的小编&#xff0c;现在轻松实现月入5万的小目标。 &#xff08;文末送读者福利&#xff09; 原来是利用空余时间学会了Python编程&#xff0c…

TPM零知识学习二—— 相关链接和页面

TPM2社区的主页地址为&#xff1a;https://tpm2-software.github.io/ 页面如下&#xff1a; 主页中提供了很多有用的链接&#xff0c;包括&#xff1a; Software | tpm2-software community 页面如下&#xff1a; External | tpm2-software community 页面如下&#xff1a;…