用户态缓存:环形缓冲区(Ring Buffer)

目录

环形缓冲区(Ring Buffer)简介

为什么选择环形缓冲区?

代码解析

1. 头文件与类型定义

1.1 头文件保护符

1.2 包含必要的标准库

1.3 类型定义

2. 环形缓冲区结构体

2.1 结构体成员解释

3. 辅助宏与内联函数

3.1 min 宏

3.2 is_power_of_two 内联函数

3.3 roundup_power_of_two 内联函数

4. 环形缓冲区的基本操作

4.1 创建新缓冲区

4.2 获取缓冲区长度

4.3 释放缓冲区

4.4 添加数据到缓冲区

4.5 从缓冲区移除数据

4.6 清空缓冲区的一部分数据

4.7 在缓冲区中搜索特定字符串

4.8 获取写入缓冲区的可写指针

5. 其他辅助函数

5.1 判断缓冲区是否为空

5.2 判断缓冲区是否已满

5.3 获取缓冲区中剩余的空间

6. 代码中的关键概念与实现

6.1 环形地址计算

6.2 缓冲区大小为2的幂次方

6.3 双指针机制

7. 综合应用

7.1 在用户态缓存区中的应用

7.2 处理生产者与消费者速度不匹配

7.3 结合之前的内容

8. 总结


环形缓冲区(Ring Buffer)简介

环形缓冲区是一种高效的数据结构,广泛应用于生产者-消费者模型中。在网络通信中,尤其是用户态缓存区中,环形缓冲区通过循环使用固定大小的内存区域,减少数据移动和内存管理开销,提升数据传输效率。

为什么选择环形缓冲区?

  • 减少数据移动:数据在缓冲区中循环写入和读取,避免了频繁的数据拷贝操作。
  • 高效缓存管理:适用于高并发场景,能够快速响应数据的读写请求。
  • 简化内存管理:固定大小的缓冲区结构简化了内存分配和释放。

代码解析

让我们逐步解析环形缓冲区代码,理解其各个部分的功能和实现细节。

1. 头文件与类型定义

#ifndef _ringbuffer_h
#define _ringbuffer_h#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <limits.h>  // for uint_max
#include <stdint.h>
#include <unistd.h>typedef struct ringbuffer_s buffer_t;buffer_t * buffer_new(uint32_t sz);uint32_t buffer_len(buffer_t *r);void buffer_free(buffer_t *r);int buffer_add(buffer_t *r, const void *data, uint32_t sz);int buffer_remove(buffer_t *r, void *data, uint32_t sz);int buffer_drain(buffer_t *r, uint32_t sz);int buffer_search(buffer_t *r, const char* sep, const int seplen);uint8_t * buffer_write_atmost(buffer_t *r);#endif
1.1 头文件保护符
#ifndef _ringbuffer_h
#define _ringbuffer_h
...
#endif
  • 作用:防止头文件被多次包含,避免重复定义错误。
1.2 包含必要的标准库
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <limits.h>  // for uint_max
#include <stdint.h>
#include <unistd.h>
  • 标准库:提供了内存管理(mallocfree)、字符串操作(memcpy)、固定宽度整数类型(uint32_tuint8_t)等功能。
1.3 类型定义
typedef struct ringbuffer_s buffer_t;
  • 作用:为 struct ringbuffer_s 定义一个别名 buffer_t,简化后续代码的书写。

2. 环形缓冲区结构体

struct ringbuffer_s {uint32_t size;uint32_t tail;uint32_t head;uint8_t * buf;
};
2.1 结构体成员解释
  • size (uint32_t):缓冲区的总大小,以字节为单位。通常为2的幂次方,便于使用位运算实现环形效果。

  • tail (uint32_t):指向缓冲区的写入位置。每次添加数据时,tail 会递增。

  • head (uint32_t):指向缓冲区的读取位置。每次移除数据时,head 会递增。

  • buf (uint8_t *):指向实际数据存储区的指针。数据以字节形式存储。

3. 辅助宏与内联函数

#define min(lth, rth) ((lth)<(rth)?(lth):(rth))static inline int is_power_of_two(uint32_t num) {if (num < 2) return 0;return (num & (num - 1)) == 0;
}static inline uint32_t roundup_power_of_two(uint32_t num) {if (num == 0) return 2;int i = 0;for (; num != 0; i++)num >>= 1;return 1U << i;
}
3.1 min
#define min(lth, rth) ((lth)<(rth)?(lth):(rth))
  • 作用:返回两个值中的较小者,简化代码中的条件判断。
3.2 is_power_of_two 内联函数
static inline int is_power_of_two(uint32_t num) {if (num < 2) return 0;return (num & (num - 1)) == 0;
}
  • 作用:判断一个数是否为2的幂次方。
  • 原理:2的幂次方数在二进制中只有一个1,其减1后所有位都会变为1,按位与结果为0。
3.3 roundup_power_of_two 内联函数
static inline uint32_t roundup_power_of_two(uint32_t num) {if (num == 0) return 2;int i = 0;for (; num != 0; i++)num >>= 1;return 1U << i;
}
  • 作用:将一个数向上舍入到最近的2的幂次方。
  • 实现:通过不断右移操作,计算出需要的位数,然后使用位移生成对应的2的幂次方数。

4. 环形缓冲区的基本操作

4.1 创建新缓冲区
buffer_t * buffer_new(uint32_t sz) {if (!is_power_of_two(sz)) sz = roundup_power_of_two(sz);buffer_t * buf = (buffer_t *)malloc(sizeof(buffer_t) + sz);if (!buf) {return NULL;}buf->size = sz;buf->head = buf->tail = 0;buf->buf = (uint8_t *)(buf + 1);return buf;
}
  • 功能:分配并初始化一个新的环形缓冲区。
  • 步骤
    1. 检查传入的大小 sz 是否为2的幂次方。如果不是,则向上舍入到最近的2的幂次方数。
    2. 分配内存:sizeof(buffer_t) + szbuffer_t结构体和实际数据存储区 buf 一起分配。
    3. 初始化结构体成员:
      • size:设置为调整后的大小 sz
      • headtail:初始化为0,表示缓冲区为空。
      • buf:指向结构体之后的内存区域,即实际的数据存储区。
4.2 获取缓冲区长度
uint32_t buffer_len(buffer_t *r) {return rb_len(r);
}static uint32_t
rb_len(buffer_t *r) {return r->tail - r->head;
}
  • 功能:返回缓冲区中当前存储的数据长度。
  • 实现:通过 tail - head 计算当前缓冲区中有效数据的字节数。
4.3 释放缓冲区
void buffer_free(buffer_t *r) {free(r);r = NULL;
}
  • 功能:释放之前分配的缓冲区内存。
  • 注意:设置指针为 NULL 只是为了防止悬挂指针,但在函数外部无效。
4.4 添加数据到缓冲区
int buffer_add(buffer_t *r, const void *data, uint32_t sz) {if (sz > rb_remain(r)) {return -1;}uint32_t i;i = min(sz, r->size - (r->tail & (r->size - 1)));memcpy(r->buf + (r->tail & (r->size - 1)), data, i);memcpy(r->buf, data+i, sz-i);r->tail += sz;return 0;
}static uint32_t
rb_remain(buffer_t *r) {return r->size - r->tail + r->head;
}
  • 功能:将数据添加到缓冲区中。
  • 步骤
    1. 检查剩余空间
      • 使用 rb_remain(r) 计算缓冲区中剩余的可用空间。
      • 如果要添加的数据大小 sz 超过剩余空间,返回错误 -1
    2. 计算可写入的字节数 i
      • 使用 min(sz, r->size - (r->tail & (r->size - 1))) 计算可以连续写入的最大字节数,避免跨越缓冲区末尾。
    3. 数据拷贝
      • 第一部分:将前 i 字节的数据拷贝到缓冲区当前位置。
      • 第二部分:如果有剩余的数据(sz - i),将其从缓冲区的起始位置开始拷贝,形成环形。
    4. 更新 tail:将 tail 指针增加 sz,标记新数据的结束位置。
    5. 返回成功:返回 0 表示数据添加成功。
4.5 从缓冲区移除数据
int buffer_remove(buffer_t *r, void *data, uint32_t sz) {assert(!rb_isempty(r));uint32_t i;sz = min(sz, r->tail - r->head);i = min(sz, r->size - (r->head & (r->size - 1)));memcpy(data, r->buf+(r->head & (r->size - 1)), i);memcpy(data+i, r->buf, sz-i);r->head += sz;return sz;
}static uint32_t
rb_isempty(buffer_t *r) {return r->head == r->tail;
}
  • 功能:从缓冲区中移除并读取数据。
  • 步骤
    1. 断言缓冲区不为空:使用 assert(!rb_isempty(r)) 确保缓冲区中有数据可读。
    2. 调整读取大小:将 sz 限制为缓冲区中实际存储的数据量 r->tail - r->head
    3. 计算可连续读取的字节数 i
      • 使用 min(sz, r->size - (r->head & (r->size - 1))) 计算可以连续读取的最大字节数,避免跨越缓冲区末尾。
    4. 数据拷贝
      • 第一部分:将前 i 字节的数据从缓冲区当前位置拷贝到目标缓冲区。
      • 第二部分:如果有剩余的数据(sz - i),将其从缓冲区的起始位置开始拷贝。
    5. 更新 head:将 head 指针增加 sz,标记数据的读取位置。
    6. 返回读取的字节数:返回实际读取的数据大小 sz
4.6 清空缓冲区的一部分数据
int buffer_drain(buffer_t *r, uint32_t sz) {if (sz > rb_len(r))sz = rb_len(r);r->head += sz;return sz;
}
  • 功能:从缓冲区中清除 sz 字节的数据,而不读取到用户空间。
  • 步骤
    1. 调整清除大小:将 sz 限制为缓冲区中实际存储的数据量 rb_len(r)
    2. 更新 head:将 head 指针增加 sz,标记数据的清除位置。
    3. 返回清除的字节数:返回实际清除的数据大小 sz
4.7 在缓冲区中搜索特定字符串
int buffer_search(buffer_t *r, const char* sep, const int seplen) {int i;for (i = 0; i <= rb_len(r)-seplen; i++) {int pos = (r->head + i) & (r->size - 1);if (pos + seplen > r->size) {if (memcmp(r->buf+pos, sep, r->size-pos))return 0;if (memcmp(r->buf, sep+r->size-pos, pos+seplen-r->size) == 0) {return i+seplen;}}if (memcmp(r->buf+pos, sep, seplen) == 0) {return i+seplen;}}return 0;
}
  • 功能:在缓冲区中搜索特定的分隔符 sep,用于界定数据包的边界(如协议解析)。
  • 步骤
    1. 遍历缓冲区:从 head 开始,逐个字节检查是否匹配 sep
    2. 计算当前检查的位置 pos:使用 (r->head + i) & (r->size - 1) 实现环形地址计算。
    3. 处理跨越缓冲区末尾的情况
      • 如果 pos + seplen > r->size,表示分隔符跨越缓冲区末尾,需要分两部分比较。
      • 第一部分:比较从 pos 到缓冲区末尾的部分。
      • 第二部分:比较从缓冲区起始位置到剩余长度的部分。
    4. 匹配成功:如果找到匹配的分隔符,返回分隔符的位置(偏移量 i + seplen)。
    5. 未找到匹配:返回 0,表示未找到。
4.8 获取写入缓冲区的可写指针
uint8_t * buffer_write_atmost(buffer_t *r) {uint32_t rpos = r->head & (r->size - 1);uint32_t wpos = r->tail & (r->size - 1);if (wpos < rpos) {uint8_t* temp = (uint8_t *)malloc(r->size * sizeof(uint8_t));memcpy(temp, r->buf+rpos, r->size - rpos);memcpy(temp+r->size-rpos, r->buf, wpos);free(r->buf);r->buf = temp;return r->buf;}return r->buf + rpos;
}
  • 功能:获取当前缓冲区中可写入数据的位置指针,最多可写入的字节数。
  • 步骤
    1. 计算读取和写入位置
      • rposhead 指针在缓冲区中的当前位置。
      • wpostail 指针在缓冲区中的当前位置。
    2. 判断是否需要重新排列缓冲区
      • 如果 wpos < rpos,表示写入位置已环绕到缓冲区的起始位置,需要将数据重新排列,使得写入位置连续。
      • 重新排列
        • 分配新的缓冲区 temp,大小与原缓冲区相同。
        • 将从 rpos 到缓冲区末尾的数据复制到 temp 的起始位置。
        • 将从缓冲区起始位置到 wpos 的数据复制到 temp 的剩余位置。
        • 释放原缓冲区内存,并将 buf 指针指向新的缓冲区 temp
        • 返回新的缓冲区起始位置。
    3. 直接返回写入位置
      • 如果不需要重新排列,直接返回 buf + rpos,即当前可写入的位置。

5. 其他辅助函数

5.1 判断缓冲区是否为空
static uint32_t
rb_isempty(buffer_t *r) {return r->head == r->tail;
}
  • 功能:检查缓冲区是否为空。
  • 返回值1 表示为空,0 表示不为空。
5.2 判断缓冲区是否已满
static uint32_t
rb_isfull(buffer_t *r) {return r->size == (r->tail - r->head);
}
  • 功能:检查缓冲区是否已满。
  • 返回值1 表示已满,0 表示未满。
5.3 获取缓冲区中剩余的空间
static uint32_t
rb_remain(buffer_t *r) {return r->size - r->tail + r->head;
}
  • 功能:计算缓冲区中剩余的可用空间。
  • 返回值:剩余空间的字节数。

6. 代码中的关键概念与实现

6.1 环形地址计算

在环形缓冲区中,headtail 指针是以字节为单位递增的。当指针超过缓冲区的大小时,通过位运算(& (r->size - 1))将其映射回缓冲区的起始位置,实现环形效果。

(r->tail & (r->size - 1))
  • 条件r->size 通常为2的幂次方,这样 (r->size - 1) 就是一个全1的二进制数,可以用来快速计算模运算。
6.2 缓冲区大小为2的幂次方

为了简化环形地址的计算,缓冲区的大小通常设置为2的幂次方。这不仅提高了效率,还使得位运算成为可能,从而加快了数据的读写操作。

6.3 双指针机制
  • head:指向下一个读取位置。
  • tail:指向下一个写入位置。
  • 优势:通过维护两个指针,可以高效地管理生产者(写入)和消费者(读取)之间的数据流动,避免数据冲突和竞争条件。

7. 综合应用

7.1 在用户态缓存区中的应用

在用户态缓存区中,环形缓冲区用于存储和管理网络数据的读写操作。生产者(如内核协议栈)将数据添加到缓冲区,消费者(如应用程序)从缓冲区读取数据。通过环形缓冲区的高效管理,确保数据传输的流畅性和可靠性。

7.2 处理生产者与消费者速度不匹配

当生产者(如内核协议栈)生成数据的速度快于消费者(应用程序)的处理速度时,缓冲区可以暂存这些数据,避免数据丢失。同样,当消费者处理数据的速度快于生产者生成数据的速度时,缓冲区也能有效地管理数据流动,确保数据的连续性。

7.3 结合之前的内容

用户态缓存:高效数据交互与性能优化icon-default.png?t=O83Ahttps://blog.csdn.net/weixin_43925427/article/details/142354725?fromshare=blogdetail&sharetype=blogdetail&sharerId=142354725&sharerefer=PC&sharesource=weixin_43925427&sharefrom=from_link在之前的讲解中,我们提到了读写缓存区在网络通信中的重要性,以及不同的缓冲区设计(固定内存块、环形缓冲区、链式缓冲区)对性能和效率的影响。环形缓冲区通过减少数据移动和优化内存管理,提升了数据传输的效率和系统的整体性能。

8. 总结

通过详细解析这段环形缓冲区的代码,我们深入理解了环形缓冲区的结构和工作原理:

  • 高效的数据管理:通过固定大小的缓冲区和双指针机制,环形缓冲区实现了高效的数据读写操作。
  • 减少数据移动:利用环形地址计算和分段拷贝,避免了大量的数据拷贝和移动操作,提升了性能。
  • 灵活的空间管理:通过动态调整和优化(如 buffer_write_atmost 函数),环形缓冲区能够适应不同的数据量需求,保持高效运行。
  • 可靠的数据传输:在生产者和消费者速度不匹配的情况下,环形缓冲区通过暂存和管理数据,确保数据的完整性和可靠性。

参考:

0voice · GitHub

GitHub - TryTryTL/buffer_design

用户态缓存:高效数据交互与性能优化-CSDN博客

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

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

相关文章

Ubuntu24.04中安装Electron

1. 安装Nodejs 使用代理服务从github下载并执行Nodejs安装脚本(假设代理服务器为192.168.2.150:10792) curl -x 192.168.2.150:10792 -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash #注意&#xff0c;Nodejs官网的安装命令少了下面这一行: …

OpenHarmony(鸿蒙南向)——平台驱动指南【MIPI CSI】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 CSI&#xff08;Camera Serial Interface&#xf…

深度学习基础及技巧

机器学习中的监督学习 监督学习是通过对数据进行分析&#xff0c;找到数据的表达模型&#xff0c;对新输入的数据套用该模型做决策 主要分为训练和预测两个阶段 训练阶段&#xff1a;根据原始数据进行特征提取&#xff0c;然后使用决策树、随机森林等模型算法分析数据之间的特…

AuthorizationPolicy 是一个重要的配置对象,用于定义服务间的访问控制和权限管理。

在 Istio 服务网格中&#xff0c;AuthorizationPolicy 是一个重要的配置对象&#xff0c;用于定义服务间的访问控制和权限管理。它允许管理员根据一系列规则来决定服务间的访问权限&#xff0c;从而增强系统的安全性。 AuthorizationPolicy 的作用 AuthorizationPolicy 主要用…

vue项目npm run serve 报错,Error: read ECONNRESET at TCP.onStreamRead

背景&#xff1a;vue2的项目&#xff0c;之前npm run serve一直可以正常使用&#xff0c;突然每次启动都会报错了&#xff0c;报错信息如下&#xff1a; node:events:492 throw er; // Unhandled error event ^ Error: read ECONNRESET at TCP.onStreamRead (n…

使用python搭建Web项目

使用python搭建Web项目 服务器基本配置安装python配置仓库镜像源安装conda管理python环境&#xff08;可选&#xff09; 使用django 框架搭建web项目迁移数据库到mysql项目依赖 服务器基本配置 安装python // 检测python版本 python --version python3 --version // 安装pyth…

【动态规划-多重背包】【hard】力扣2585. 获得分数的方法数

考试中有 n 种类型的题目。给你一个整数 target 和一个下标从 0 开始的二维整数数组 types &#xff0c;其中 types[i] [counti, marksi] 表示第 i 种类型的题目有 counti 道&#xff0c;每道题目对应 marksi 分。 返回你在考试中恰好得到 target 分的方法数。由于答案可能很…

Leetcode 136 只出现一次的数字

题目链接&#xff1a;136. 只出现一次的数字 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性…

计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI

首先安装需要的python库&#xff0c; 安装完之后利用navicat导入数据库文件bili100.sql到mysql中&#xff0c; 再在pycharm编译器中连接mysql数据库&#xff0c;并在设置文件中将密码修改成你的数据库密码。最后运行app.py&#xff0c;打开链接&#xff0c;即可运行。 B站爬虫数…

Java语言程序设计基础篇_编程练习题**18.31 (替换单词)

目录 题目&#xff1a;**18.31 (替换单词) 习题思路 代码示例 运行结果 替换前 替换后 题目&#xff1a;**18.31 (替换单词) 编写一个程序&#xff0c;递归地用一个新单词替换某个目录下的所有文件中出现的某个单词。从命令行如下传递参数&#xff1a; java Exercise18…

CSS中的字体样式、文本样式、列表样式以及背景和渐变

一、字体样式和文本样式 1.span标签 span标签的作用&#xff1a;能让某几个文字或者是词语凸显出来 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-…

【ComfyUI】生成图细节更清晰——Consistency_Decoder

原文&#xff1a;https://github.com/openai/consistencydecoder comfyui: https://github.com/gameltb/Comfyui_Consistency_Decoder_VAE 博文资料下载&#xff1a;https://pan.baidu.com/s/1SwfA4T6iMsA8IrRrGXm4sg?pwd0925 安装 【秋葉aaaki】comfyui一键运行包 夸克网盘…

Vue3 + TS 实现同一项目同一链接,pc端打开是web应用,手机打开是H5应用

前言&#xff1a; 我自己搭建的项目基本都是用 postcss-px-to-viewport 插件进行适配的&#xff1b; 最近在做一个项目&#xff0c;需求是同样的功能&#xff0c;用户可以在电脑上打开操作使用&#xff0c;也可以在手机上登录进去操作使用&#xff0c;但是跳转链接是同一个&am…

LVS-DR实战案例,实现四层负载均衡

环境准备&#xff1a;三台虚拟机&#xff08;NET模式或者桥接模式&#xff09; 192.168.88.200 &#xff08;web1&#xff09;(安装nginx服务器作为测试) 192.168.88.201 &#xff08;服务器&#xff09;&#xff08;用于部署lvs-dr&#xff09; 192.168.88.202 (web2)…

猫头虎分享:Python库 Jinja2 的简介、安装、用法详解入门教程

猫头虎分享&#xff1a;Python库 Jinja2 的简介、安装、用法详解入门教程 &#x1f42f; 摘要 今天有粉丝问猫哥&#xff1a;“如何使用Jinja2进行Python模板渲染&#xff1f;”这是一个非常常见的问题&#xff0c;特别是在开发Web应用时。Jinja2是一个强大的模板引擎&#x…

一篇带你搞定数据结构散列表

数据结构入门学习&#xff08;全是干货&#xff09;——散列表 1 散列表 1.1 引子&#xff1a;散列的基本思路 C语言变量名的管理&#xff1a; 定义/声明&#xff1a;先定义后使用。插入与查找&#xff1a; 插入&#xff1a;新变量定义。查找&#xff1a;检查变量是否已定义。…

2024年10月计划(工作为主,Ue5独立游戏为辅,)

我发现一点&#xff0c;就是工作很忙&#xff0c;比如中秋也在远程加班&#xff0c;周末有时也远程加班&#xff0c;国庆节甚至也差点去甲方工作。甚至有可能驻场。可见&#xff0c;小公司确实不能去。 好在&#xff0c;9月份时&#xff0c;通过渲染 除了上班时间外&#xff0…

Remotion:使用前端技术开发视频

前言 最近做文章突然想到很多文章其实也可以用视频的方式来展现&#xff0c;以现在短视频的火爆程度&#xff0c;肯定能让更多的人看到。 恰巧最近看了很多关于动画的前端 js 库&#xff0c;那如果将这些动画帧连续起来&#xff0c;岂不是就成了一个视频吗&#xff1f; 而且…

mysql 用户相关操作

用户账号的crud # create user account CREATE USER usernamehost IDENTIFIED BY password;# delete user account DROP USER usernamehost;# alter ALTER USER usernamehost IDENTIFIED BY new_password;# select select user, host from mysql.user;用户权限的crud # creat…

smartctl 命令:查看硬盘健康状态

一、命令简介 ​smartctl​ 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态&#xff0c;并能提供一些潜在故障的预警信息。通过查看 SMART 数据&#xff0c;用户可以了解硬…