栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

服务器正文16:缓冲区设计(ringbuffer、用libevent配合ringbuffer做无锁队列)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

服务器正文16:缓冲区设计(ringbuffer、用libevent配合ringbuffer做无锁队列)

文章目录
    • 一、为什么用户层需要设计网络缓冲区
      • 1)设计读缓冲区的必要
      • 2)设计写缓冲区的必要
      • 3)普通buf+pos的问题
      • 4)ringbuffer(只解决了普通buf移动频繁的问题)
        • (1)头文件
        • (2).c文件
    • 二、ringbuffer以及多线程换将下数据安全思考
      • 1)1读1写->内存屏障
      • 2) 多线程(多读多写->需要加锁且加内存屏障)
      • 3)不需要加内存屏障的情况(加锁加了pthread_spinlock,自旋锁就不加)
    • 三、libevent中event buffer的设计
      • 1)libevent的chainbuffer同时解决了固定内存分配所要考虑的问题(要求:生产速度要小于消费速度)和数据移动频繁的问题
      • 2)libevent怎么解决的数据移动频繁

  • 使用场景举例
    kFIFO、 无锁队列、网络缓冲区设计
一、为什么用户层需要设计网络缓冲区 1)设计读缓冲区的必要

因为TCP无法判定从内核缓冲区读出来的数据包是一个完整的数据包,得用特殊字符来分隔数据包

2)设计写缓冲区的必要

如果写缓冲区数据满了(linux内核发送数据比我们往缓冲区写数据慢),写失败,那就要把数据在用户层缓存起来

3)普通buf+pos的问题

1)数据移动比较频繁
2)用户层缓冲区设置的是16k(固定分配的内存,基本要求:消费者速度必须得快于生产者)

4)ringbuffer(只解决了普通buf移动频繁的问题) (1)头文件
#ifndef _RINGBUFFER_H__
#define _RINGBUFFER_H__

#include 
#include 
#include 
#include 

#define min(lth , rth) ((lth) < (rth) ? (lth): (rth))

typedef struct
{
    uint8_t* buf;       //buffer
    uint32_t size;      //大小
    uint32_t read_pos;  //读的位置,也是循环的缓冲区,从0到2^32-1
    uint32_t write_pos; //写的位置
}ringbuffer_t;

//接口总结:1.初始化,2.判断是否为空,3.判断满,4.释放内存
//基础接口
void rb_init(ringbuffer_t* t ,uint32_t sz);
int32_t rb_is_empty(ringbuffer_t* t);
int32_t  rb_is_full(ringbuffer_t* t);
void rb_free(ringbuffer_t* t);
uint32_t rb_length(ringbuffer_t* t);
uint32_t rb_reamin(ringbuffer_t* t);

//读写数据接口
uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz);
uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz);

//转换成2的n次幂
static inline uint32_t roundup_power_of_two(uint32_t sz)
{
    if(sz < 2) return sz = 2;

    //5 -> 8   101 -> 1000 
    int i = 0;
    for(i = 0;sz != 0;++i)
        sz>>1;
    return 1 << i;
}

//判断是否是2的n次幂
static inline int32_t  is_power_of_two(uint32_t sz)
{
    if(sz < 2) return -1;

    //m%n = m & (n-1)
    //m%n = m - n * floor(m/n)
    return (sz & (sz - 1)) == 0;
}


#endif
(2).c文件
#include "ringbuffer.h"

int32_t rb_is_empty(ringbuffer_t* t)
{
    return t->read_pos == t->write_pos;
}

int32_t  rb_is_full(ringbuffer_t* t)
{
    //优化前
    //return (t->read_pos+t->size)%t->size == t->write_pos;

    //优化前提:
    //1)缓冲区的大小必须是2的n次幂 (UInt3_t大小为2^32-1,范围是0到2^32 -1)

    return t->size == t->write_pos - t->read_pos;
}


void rb_init(ringbuffer_t* t ,uint32_t sz)
{
    t->buf = (uint8_t*)malloc(sz*sizeof(uint8_t*));
    t->size = sz;
    t->read_pos = t->write_pos = UINT32_MAX -2;
    return;
}

void rb_free(ringbuffer_t* t)
{
    if(t->buf)
    {
        free(t->buf);
        t->buf = NULL;
    }

    t->read_pos = t->write_pos = t->size = 0;

    return;
}

uint32_t rb_length(ringbuffer_t* t)
{
    return t->write_pos - t->read_pos;
}

uint32_t rb_reamin(ringbuffer_t* t)
{
    return t->size - rb_length(t);
}

uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz)
{
    if(src_sz > rb_reamin(dst_t))
        return -1;
    
    //dst_t->write_pos & (dst_t->size - 1)  把写坐标弄到这个圆环的范围之内
    //看哪个剩余空间最小
    //安全的拷贝
    uint32_t i = min(src_sz,dst_t->size - dst_t->write_pos & (dst_t->size - 1));

    //先拷贝后面的,再拷贝前面的
    memcpy(dst_t->buf + (dst_t->write_pos & (dst_t->size - 1)), src_buf,i);
    memcpy(dst_t->buf,src_buf + i,src_sz - i);//这里src_sz - i是负数是不会拷贝的

    dst_t->write_pos += src_sz;
    return src_sz;
}

uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz)
{
    if(rb_is_empty(src_t))
        return -1;
    uint32_t i = 0;
    dst_sz = min(dst_sz,rb_length(src_t) ); 
    //rb_length(src_t)为实际的大小
    //dst_sz为预期的大小

    i = min(dst_sz,(src_t->size - src_t->read_pos & (src_t->size - 1)));
    //src_t->read_pos & (src_t->size - 1))表示read_pos在size的位置
    //src_t->size - src_t->read_pos & (src_t->size - 1)表示求出右边空余位置的大小

    memcpy(dst_buf,src_t->buf + (src_t->read_pos & (src_t->size - 1)),i);
    memcpy(dst_buf + i,src_t->buf,dst_sz - i);

    src_t->read_pos +=dst_sz;

    return dst_sz;

}

int main()
{
    ringbuffer_t rb;
    rb_init(&rb,9);

    uint32_t put1 = rb_write(&rb,(void*)("mark"),4);
    printf("put1 = %u,wr = %u,rd = %u,length = %un",put1,rb.write_pos,rb.read_pos,rb_length(&rb));

    return 0;
}


二、ringbuffer以及多线程换将下数据安全思考 1)1读1写->内存屏障
uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz)
{
    if(src_sz > rb_reamin(dst_t))
        return -1;
    
    //内存屏障
#ifdef USE_MEMPROTECT
//确保开始移动buffer的时候,其他线程可以读到最新的read_pos
    smp_mb();   //atomic_thread_fence()
#endif

    //dst_t->write_pos & (dst_t->size - 1)  把写坐标弄到这个圆环的范围之内
    //看哪个剩余空间最小
    //安全的拷贝
    uint32_t i = min(src_sz,dst_t->size - dst_t->write_pos & (dst_t->size - 1));

    //先拷贝后面的,再拷贝前面的
    memcpy(dst_t->buf + (dst_t->write_pos & (dst_t->size - 1)), src_buf,i);
    memcpy(dst_t->buf,src_buf + i,src_sz - i);//这里src_sz - i是负数是不会拷贝的

#ifdef USE_MEMPROTECT
//确保write_pos不会被优化到前面去
    smp_wmb();//放在dst_t->write_pos += src_sz;之前,确保memcpy正确拷贝
#endif

    dst_t->write_pos += src_sz;

    return src_sz;
}

uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz)
{
    if(rb_is_empty(src_t))
        return -1;

#ifdef USE_MEMPROTECT
    smp_mb();   //atomic_thread_fence()
#endif

    uint32_t i = 0;
    dst_sz = min(dst_sz,rb_length(src_t) ); 
    //rb_length(src_t)为实际的大小
    //dst_sz为预期的大小

    i = min(dst_sz,(src_t->size - src_t->read_pos & (src_t->size - 1)));
    //src_t->read_pos & (src_t->size - 1))表示read_pos在size的位置
    //src_t->size - src_t->read_pos & (src_t->size - 1)表示求出右边空余位置的大小

    memcpy(dst_buf,src_t->buf + (src_t->read_pos & (src_t->size - 1)),i);
    memcpy(dst_buf + i,src_t->buf,dst_sz - i);

#ifdef USE_MEMPROTECT
    smp_wmb();//放在dst_t->write_pos += src_sz;之前,确保memcpy正确拷贝
#endif

    src_t->read_pos +=dst_sz;

    return dst_sz;

}
2) 多线程(多读多写->需要加锁且加内存屏障) 3)不需要加内存屏障的情况(加锁加了pthread_spinlock,自旋锁就不加) 三、libevent中event buffer的设计

1:24:13
https://www.bilibili.com/video/BV1ZL411M7Qz?spm_id_from=333.337.search-card.all.click

1)libevent的chainbuffer同时解决了固定内存分配所要考虑的问题(要求:生产速度要小于消费速度)和数据移动频繁的问题 2)libevent怎么解决的数据移动频繁

misalign表示缓冲区已读的字段,后面的数据不用移动到前面去

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/964998.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号