OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9208|回复: 10

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

[复制链接]

78

主题

100

帖子

1

精华

高级会员

Rank: 4

积分
523
金钱
523
注册时间
2016-8-25
在线时间
44 小时
发表于 2018-7-17 15:01:56 | 显示全部楼层 |阅读模式
本帖最后由 大胖森 于 2018-7-17 16:40 编辑


前言
大家晚上好,我是杰杰,上个星期,研究了一下机智云的源码,也不能说是研究吧,就是看了看,人家既然能拿来做商业用,还是有很厉害的地方的,如果还不知道什么叫环形缓冲区(环形队列)的同学,请看——STM32进阶之串口环形缓冲区实现
好啦。多余的话不多说,看看他们的东西比我写的好在哪吧,原理都是一样的,但是效率会比我的搞,可能应用的地方也不一样,所以,先看看吧。

ringbuffer.h
先看看头文件:ringbuffer.h。
主要是用宏实现了一个求最小值的函数。
还有就是定义了一个环形缓冲区的结构体。
  1. #define min(a, b) (a)<(b)?(a):(b)                   ///< Calculate the minimum value

  2. typedef struct {
  3.     size_t rbCapacity;
  4.     uint8_t  *rbHead;
  5.     uint8_t  *rbTail;
  6.     uint8_t  *rbBuff;
  7. }rb_t;
复制代码


看英文就能知道意思了,rb是ringbuff的缩写,意思就是环形缓冲区,
结构体中rbCapacity是缓冲区的容量,也就是大小。
结构体中rbHead是缓冲区的头指针,
rbTail是缓冲区的尾指针,
rBuff是缓冲区的首地址,在创建的时候就用到。
ringbuffer.c
环形缓冲区的创建
下面来看看源文件:
  1. int8_t ICACHE_FLASH_ATTR rbCreate(rb_t* rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     rb->rbHead = rb->rbBuff;
  8.     rb->rbTail = rb->rbBuff;
  9.     return 0;
  10. }
复制代码


这是个创建环形缓冲区的函数,就是初始化了环形缓冲区的头尾指针,这个函数的通用性很强,因为很多时候不只创建一个缓冲区。每个缓冲区的首地址都保存在了rbBuff,这个在后面的通用性会很好用。但是杰杰还是觉得不够好,因为我们在结构体中定义了缓冲区的容量,但是在这里并没有给他初始化,我觉得应该传入应该参数,给缓冲区的容量进行初始化一下。但是无所谓啦。
环形缓冲区的删除
  1. int8_t ICACHE_FLASH_ATTR rbDelete(rb_t* rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     rb->rbBuff = NULL;
  8.     rb->rbHead = NULL;
  9.     rb->rbTail = NULL;
  10.     rb->rbCapacity = 0;
  11.         return 0;
  12. }
复制代码

把这些指针指向NULL,但是环形缓冲区本身地址的数据是不会被清除的,只是表明了这些地址可以被重复使用了而已。
  1. int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     return rb->rbCapacity;
  8. }
复制代码

获取环形缓冲区的容量
  1. int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     return rb->rbCapacity;
  8. }
复制代码

因为可能有多个环形缓冲区,但是容量我们不一定会知道,所以还是写一个获取它容量的函数比较好。
环形缓冲区可读数据大小
  1. int32_t ICACHE_FLASH_ATTR rbCanRead(rb_t *rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     if (rb->rbHead == rb->rbTail)
  8.     {
  9.         return 0;
  10.     }

  11.     if (rb->rbHead < rb->rbTail)
  12.     {
  13.         return rb->rbTail - rb->rbHead;
  14.     }

  15.     return rbCapacity(rb) - (rb->rbHead - rb->rbTail);
  16. }
复制代码

如果缓冲区是没有被创建的,那么返回-1,表示非法,如果环形缓冲区的首尾都在一个位置,那么表面环形缓冲区没有数据,那么是不可读的,否则就返回正常的数据,rb->rbTail - rb->rbHead / rbCapacity(rb) - (rb->rbHead - rb->rbTail),请用数学的方法理解这段代码。
获取环形缓冲区可写数据大小
同理获取可写数据也是一样的
  1. int32_t ICACHE_FLASH_ATTR rbCanWrite(rb_t *rb)
  2. {
  3.     if(NULL == rb)
  4.     {
  5.         return -1;
  6.     }

  7.     return rbCapacity(rb) - rbCanRead(rb);
  8. }
复制代码

环形缓冲区读数据
  1. int32_t ICACHE_FLASH_ATTR rbRead(rb_t *rb, void *data, size_t count)
  2. {
  3.     int32_t copySz = 0;

  4.     if(NULL == rb)
  5.     {
  6.         return -1;
  7.     }

  8.     if(NULL == data)
  9.     {
  10.         return -1;
  11.     }

  12.     if (rb->rbHead < rb->rbTail)
  13.     {
  14.         copySz = min(count, rbCanRead(rb));
  15.         memcpy(data, rb->rbHead, copySz);
  16.         rb->rbHead += copySz;
  17.         return copySz;
  18.     }
  19.     else
  20.     {
  21.         if (count < rbCapacity(rb)-(rb->rbHead - rb->rbBuff))
  22.         {
  23.             copySz = count;
  24.             memcpy(data, rb->rbHead, copySz);
  25.             rb->rbHead += copySz;
  26.             return copySz;
  27.         }
  28.         else
  29.         {
  30.             copySz = rbCapacity(rb) - (rb->rbHead - rb->rbBuff);
  31.             memcpy(data, rb->rbHead, copySz);
  32.             rb->rbHead = rb->rbBuff;
  33.             copySz += rbRead(rb, (char*)data+copySz, count-copySz);
  34.             return copySz;
  35.         }
  36.     }
  37. }
复制代码

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

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

  3. rb->rbHead += copySz;
复制代码

最后一种情况就是,需要分段读取了,先把头指针到缓冲区最后一个地址的这部分读取了,再加上从缓冲区首地址开始读取count-copySz那么长数据的数据,就ok了。然后把两端数据拼接起来。数据保存在data中。
  1. copySz = rbCapacity(rb) - (rb->rbHead - rb->rbBuff);
  2. memcpy(data, rb->rbHead, copySz);
  3. rb->rbHead = rb->rbBuff;
  4. copySz += rbRead(rb, (char*)data+copySz, count-copySz);
复制代码

环形缓冲区写数据
  1. int32_t ICACHE_FLASH_ATTR rbWrite(rb_t *rb, const void *data, size_t count)
  2. {
  3.     int32_t tailAvailSz = 0;

  4.     if((NULL == rb)||(NULL == data))
  5.     {
  6.         return -1;
  7.     }

  8.     if (count >= rbCanWrite(rb))
  9.     {
  10.         return -2;
  11.     }

  12.     if (rb->rbHead <= rb->rbTail)
  13.     {
  14.         tailAvailSz = rbCapacity(rb) - (rb->rbTail - rb->rbBuff);
  15.         if (count <= tailAvailSz)
  16.         {
  17.             memcpy(rb->rbTail, data, count);
  18.             rb->rbTail += count;
  19.             if (rb->rbTail == rb->rbBuff+rbCapacity(rb))
  20.             {
  21.                 rb->rbTail = rb->rbBuff;
  22.             }
  23.             return count;
  24.         }
  25.         else
  26.         {
  27.             memcpy(rb->rbTail, data, tailAvailSz);
  28.             rb->rbTail = rb->rbBuff;

  29.             return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);
  30.         }
  31.     }
  32.     else
  33.     {
  34.         memcpy(rb->rbTail, data, count);
  35.         rb->rbTail += count;
  36.         return count;
  37.     }
  38. }
复制代码

与读书同理的,将一定长度的数据从某段地(data)址写入环形缓冲区。如果数据地址非法或者是可写数据长度不够,那么就会返回错误代码。先看后面的
  1.     else
  2.     {
  3.         memcpy(rb->rbTail, data, count);
  4.         rb->rbTail += count;
  5.         return count;
  6.     }
复制代码

如果写数据的地址是地址的话,那么是可以直接写的,注意的是,写数据的地址并非读数据的地址,刚好相反的,可读数据的地址是绝对不允许写的。同理,假如写书的地址不是递增的话,那么,也是分成两段,假如写入数据的长度小于从尾指针到环形缓冲区最后一个地址的长度,那么,写入的这段数据其实其地址也是递增的,同样是可以直接写的,然后更新一下尾指针。
  1. memcpy(rb->rbTail, data, count);
  2.   rb->rbTail += count;
  3.   if (rb->rbTail == rb->rbBuff+rbCapacity(rb))
  4. {
  5.     rb->rbTail = rb->rbBuff;
  6. }
复制代码

否则,也需要分段写入,先写入从尾指针到环形缓冲区最后一个地址的长度,然后从环形缓冲区的首地址开始再写入剩下的数据长度count-tailAvailSz,
  1. memcpy(rb->rbTail, data, tailAvailSz);
  2. rb->rbTail = rb->rbBuff;
  3. return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);
复制代码

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

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

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

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


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

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

3

主题

19

帖子

0

精华

初级会员

Rank: 2

积分
132
金钱
132
注册时间
2018-8-25
在线时间
33 小时
发表于 2018-11-18 20:24:50 | 显示全部楼层
本帖最后由 LevenC 于 2018-11-18 20:34 编辑

好好读读Linux Kernel源码不就好了吗?

环形队列函数介绍
- https://www.kernel.org/doc/htmldocs/kernel-api/kfifo.html

环形队列实现:
- kfifo.c
- https://git.kernel.org/pub/scm/l ... it/tree/lib/kfifo.c

环形队列头文件
- kfifo.h
- https://git.kernel.org/pub/scm/l ... clude/linux/kfifo.h

对于STM32应用环形队列而言,搞明白以下三个足以了。
kfifo_init : 使用预先分配好的缓冲区初始化队列
kfifo_in   : 入队
kfifo_out : 出队
回复 支持 1 反对 0

使用道具 举报

160

主题

966

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2095
金钱
2095
注册时间
2014-3-7
在线时间
490 小时
发表于 2018-7-18 09:16:20 | 显示全部楼层
我帮你顶一下,看了一下确实有点门道!
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165352
金钱
165352
注册时间
2010-12-1
在线时间
2108 小时
发表于 2018-7-22 02:18:40 | 显示全部楼层
不错,谢谢分享
回复 支持 反对

使用道具 举报

4

主题

23

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
213
金钱
213
注册时间
2018-7-13
在线时间
15 小时
发表于 2018-7-27 23:20:25 | 显示全部楼层
楼主有机智云移植到战舰V3教程吗 卡这里了
回复 支持 反对

使用道具 举报

0

主题

65

帖子

0

精华

高级会员

Rank: 4

积分
523
金钱
523
注册时间
2017-12-21
在线时间
76 小时
发表于 2018-8-30 01:33:19 | 显示全部楼层
不错,学习了
回复 支持 反对

使用道具 举报

109

主题

5562

帖子

0

精华

资深版主

Rank: 8Rank: 8

积分
10541
金钱
10541
注册时间
2017-2-18
在线时间
1908 小时
发表于 2018-8-30 11:57:45 | 显示全部楼层
学习了
回复 支持 反对

使用道具 举报

78

主题

100

帖子

1

精华

高级会员

Rank: 4

积分
523
金钱
523
注册时间
2016-8-25
在线时间
44 小时
 楼主| 发表于 2018-9-5 16:49:57 | 显示全部楼层
木一 发表于 2018-7-27 23:20
楼主有机智云移植到战舰V3教程吗 卡这里了

战舰V3的移植 变了


1)ESP8266烧写固件中有几个参数 变了,可参考机智云文档中心版本的固件烧写方式
2)MCU代码自动生成升级后,有几个函数位置发生了变化

所以 老版本的战舰V3移植方式不在适用了
回复 支持 反对

使用道具 举报

49

主题

340

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
5246
金钱
5246
注册时间
2012-8-25
在线时间
1024 小时
发表于 2018-9-6 08:02:53 | 显示全部楼层
Mark,好好学习天天向上
回复 支持 反对

使用道具 举报

78

主题

100

帖子

1

精华

高级会员

Rank: 4

积分
523
金钱
523
注册时间
2016-8-25
在线时间
44 小时
 楼主| 发表于 2019-4-24 10:49:49 | 显示全部楼层
LevenC 发表于 2018-11-18 20:24
好好读读Linux Kernel源码不就好了吗?

环形队列函数介绍

赞一个
回复 支持 反对

使用道具 举报

1

主题

13

帖子

0

精华

初级会员

Rank: 2

积分
151
金钱
151
注册时间
2019-8-5
在线时间
39 小时
发表于 2019-8-20 14:34:51 | 显示全部楼层
真的不错,感谢楼主的分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-24 15:22

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表