杰杰带你解读【机智云】环形缓冲区源码

本文转载自:  https://mp.weixin.qq.com/s/iBrIu6RyEx_s-MVVkv1RRQ

前言

大家晚上好,我是杰杰,上个星期,研究了一下机智云的源码,也不能说是研究吧,就是看了看,人家既然能拿来做商业用,还是有很厉害的地方的,如果还不知道什么叫环形缓冲区(环形队列)的同学,请看——STM32进阶之串口环形缓冲区实现

好啦。多余的话不多说,看看他们的东西比我写的好在哪吧,原理都是一样的,但是效率会比我的搞,可能应用的地方也不一样,所以,先看看吧。

ringbuffer.h

先看看头文件:ringbuffer.h。

主要是用宏实现了一个求最小值的函数。

还有就是定义了一个环形缓冲区的结构体。

#define min(a, b) (a)<(b)?(a):(b)                   ///< Calculate the minimum value

typedef struct {

size_t rbCapacity;

uint8_t  *rbHead;

uint8_t  *rbTail;

uint8_t  *rbBuff;

}rb_t;

复制代码

看英文就能知道意思了,rb是ringbuff的缩写,意思就是环形缓冲区,

结构体中rbCapacity是缓冲区的容量,也就是大小。

结构体中rbHead是缓冲区的头指针,

rbTail是缓冲区的尾指针,

rBuff是缓冲区的首地址,在创建的时候就用到。

ringbuffer.c

环形缓冲区的创建

下面来看看源文件:

int8_t ICACHE_FLASH_ATTR rbCreate(rb_t* rb)

{

if(NULL == rb)

{

return -1;

}

rb->rbHead = rb->rbBuff;

rb->rbTail = rb->rbBuff;

return 0;

}

复制代码

这是个创建环形缓冲区的函数,就是初始化了环形缓冲区的头尾指针,这个函数的通用性很强,因为很多时候不只创建一个缓冲区。每个缓冲区的首地址都保存在了rbBuff,这个在后面的通用性会很好用。但是杰杰还是觉得不够好,因为我们在结构体中定义了缓冲区的容量,但是在这里并没有给他初始化,我觉得应该传入应该参数,给缓冲区的容量进行初始化一下。但是无所谓啦。

环形缓冲区的删除

int8_t ICACHE_FLASH_ATTR rbDelete(rb_t* rb)

{

if(NULL == rb)

{

return -1;

}

rb->rbBuff = NULL;

rb->rbHead = NULL;

rb->rbTail = NULL;

rb->rbCapacity = 0;

return 0;

}

复制代码

把这些指针指向NULL,但是环形缓冲区本身地址的数据是不会被清除的,只是表明了这些地址可以被重复使用了而已。

int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)

{

if(NULL == rb)

{

return -1;

}

return rb->rbCapacity;

}

复制代码

获取环形缓冲区的容量

int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)

{

if(NULL == rb)

{

return -1;

}

return rb->rbCapacity;

}

复制代码

因为可能有多个环形缓冲区,但是容量我们不一定会知道,所以还是写一个获取它容量的函数比较好。

环形缓冲区可读数据大小

int32_t ICACHE_FLASH_ATTR rbCanRead(rb_t *rb)

{

if(NULL == rb)

{

return -1;

}

if (rb->rbHead == rb->rbTail)

{

return 0;

}

if (rb->rbHead < rb->rbTail)

{

return rb->rbTail - rb->rbHead;

}

return rbCapacity(rb) - (rb->rbHead - rb->rbTail);

}

复制代码

如果缓冲区是没有被创建的,那么返回-1,表示非法,如果环形缓冲区的首尾都在一个位置,那么表面环形缓冲区没有数据,那么是不可读的,否则就返回正常的数据,rb->rbTail - rb->rbHead / rbCapacity(rb) - (rb->rbHead - rb->rbTail),请用数学的方法理解这段代码。

获取环形缓冲区可写数据大小

同理获取可写数据也是一样的

int32_t ICACHE_FLASH_ATTR rbCanWrite(rb_t *rb)

{

if(NULL == rb)

{

return -1;

}

return rbCapacity(rb) - rbCanRead(rb);

}

复制代码

环形缓冲区读数据

int32_t ICACHE_FLASH_ATTR rbRead(rb_t *rb, void *data, size_t count)

{

int32_t copySz = 0;

if(NULL == rb)

{

return -1;

}

if(NULL == data)

{

return -1;

}

if (rb->rbHead < rb->rbTail)

{

copySz = min(count, rbCanRead(rb));

memcpy(data, rb->rbHead, copySz);

rb->rbHead += copySz;

return copySz;

}

else

{

if (count < rbCapacity(rb)-(rb->rbHead - rb->rbBuff))

{

copySz = count;

memcpy(data, rb->rbHead, copySz);

rb->rbHead += copySz;

return copySz;

}

else

{

copySz = rbCapacity(rb) - (rb->rbHead - rb->rbBuff);

memcpy(data, rb->rbHead, copySz);

rb->rbHead = rb->rbBuff;

copySz += rbRead(rb, (char*)data+copySz, count-copySz);

return copySz;

}

}

}

复制代码

如果是缓冲区没被创建或者是读数据地址非法(NULL)都将返回错误。如果rb->rbHead < rb->rbTail,就是可读数据的地址是递增的,那么可以直接读数据,读取的最大数据不能超过缓冲区可读最大数据,所以要用copySz = min(count, rbCanRead(rb));限制一下读取数据的大小,因为是直接拷贝数据,所以,在较多数据面前的话,这种做法很好,比如像网络上的数据,更是适合用这种方法。读完之后把rbHead 头指针重新更新,rb->rbHead += copySz;因为环形缓冲区在数据存储(软件地址上)是环形的,所以,假如数据地址不是递增的,那么无法直接拷贝,需要分段拷贝,count < rbCapacity(rb)-(rb->rbHead - rb->rbBuff)如果要读取的数据小于从环形缓冲区的首地址开始到环形缓冲区大小的地址,那么这段地址还是递增的,所以可以直接拷贝过去,并且把头指针更新一下。

copySz = count;

memcpy(data, rb->rbHead, copySz);

rb->rbHead += copySz;

复制代码

最后一种情况就是,需要分段读取了,先把头指针到缓冲区最后一个地址的这部分读取了,再加上从缓冲区首地址开始读取count-copySz那么长数据的数据,就ok了。然后把两端数据拼接起来。数据保存在data中。

copySz = rbCapacity(rb) - (rb->rbHead - rb->rbBuff);

memcpy(data, rb->rbHead, copySz);

rb->rbHead = rb->rbBuff;

copySz += rbRead(rb, (char*)data+copySz, count-copySz);

复制代码

环形缓冲区写数据

int32_t ICACHE_FLASH_ATTR rbWrite(rb_t *rb, const void *data, size_t count)

{

int32_t tailAvailSz = 0;

if((NULL == rb)||(NULL == data))

{

return -1;

}

if (count >= rbCanWrite(rb))

{

return -2;

}

if (rb->rbHead <= rb->rbTail)

{

tailAvailSz = rbCapacity(rb) - (rb->rbTail - rb->rbBuff);

if (count <= tailAvailSz)

{

memcpy(rb->rbTail, data, count);

rb->rbTail += count;

if (rb->rbTail == rb->rbBuff+rbCapacity(rb))

{

rb->rbTail = rb->rbBuff;

}

return count;

}

else

{

memcpy(rb->rbTail, data, tailAvailSz);

rb->rbTail = rb->rbBuff;

return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);

}

}

else

{

memcpy(rb->rbTail, data, count);

rb->rbTail += count;

return count;

}

}

复制代码

与读书同理的,将一定长度的数据从某段地(data)址写入环形缓冲区。如果数据地址非法或者是可写数据长度不够,那么就会返回错误代码。先看后面的

else

{

memcpy(rb->rbTail, data, count);

rb->rbTail += count;

return count;

    }

复制代码

如果写数据的地址是地址的话,那么是可以直接写的,注意的是,写数据的地址并非读数据的地址,刚好相反的,可读数据的地址是绝对不允许写的。同理,假如写书的地址不是递增的话,那么,也是分成两段,假如写入数据的长度小于从尾指针到环形缓冲区最后一个地址的长度,那么,写入的这段数据其实其地址也是递增的,同样是可以直接写的,然后更新一下尾指针。

memcpy(rb->rbTail, data, count);

rb->rbTail += count;

if (rb->rbTail == rb->rbBuff+rbCapacity(rb))

{

rb->rbTail = rb->rbBuff;

}

复制代码

否则,也需要分段写入,先写入从尾指针到环形缓冲区最后一个地址的长度,然后从环形缓冲区的首地址开始再写入剩下的数据长度count-tailAvailSz,

memcpy(rb->rbTail, data, tailAvailSz);

rb->rbTail = rb->rbBuff;

return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);

复制代码

好了,至此,源码基本分析完毕,现在说说为什么比我的源码写得好,

第一点,代码的效率,我写的源码是一个个数据的写入,而机智云是一系列数据的写入。读数据也是一样,一系列数据读出,而我的源码则是一个个数据读出,并且使用了求模的运算防止指针越界,这在运算中效率是不够高的。

第二代码的健壮性,还是机智云的好,我的代码是没有检查是否真正有有效的数据写入。同样的代码读出也是检查了读出数据的地址是否真正有效,防止数据非法丢失。总的来说,需要不断成长,还是要研究研究别人商业上用的源码,虽然说很多原理我们都知道,但是亲自写的话,不一定能写得出来,

还有就是,重用现有源码比创新的效率更高,因为并不是所有人都能另走捷径,做开拓者的,我们用已有的好东西足以。

END

需要源码的同学可以在公众号回复“机智云源码”晚安!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容