本文轉(zhuǎn)載自:??https://mp.weixin.qq.com/s/iBrIu6RyEx_s-MVVkv1RRQ
前言
大家晚上好,我是杰杰煌妈,上個星期儡羔,研究了一下機(jī)智云的源碼,也不能說是研究吧璧诵,就是看了看汰蜘,人家既然能拿來做商業(yè)用,還是有很厲害的地方的之宿,如果還不知道什么叫環(huán)形緩沖區(qū)(環(huán)形隊列)的同學(xué)族操,請看——STM32進(jìn)階之串口環(huán)形緩沖區(qū)實現(xiàn)
好啦。多余的話不多說比被,看看他們的東西比我寫的好在哪吧色难,原理都是一樣的,但是效率會比我的搞等缀,可能應(yīng)用的地方也不一樣枷莉,所以,先看看吧尺迂。
ringbuffer.h
先看看頭文件:ringbuffer.h笤妙。
主要是用宏實現(xiàn)了一個求最小值的函數(shù)冒掌。
還有就是定義了一個環(huán)形緩沖區(qū)的結(jié)構(gòu)體。
#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;
復(fù)制代碼
看英文就能知道意思了蹲盘,rb是ringbuff的縮寫股毫,意思就是環(huán)形緩沖區(qū)洁墙,
結(jié)構(gòu)體中rbCapacity是緩沖區(qū)的容量伐厌,也就是大小沽瞭。
結(jié)構(gòu)體中rbHead是緩沖區(qū)的頭指針绳匀,
rbTail是緩沖區(qū)的尾指針,
而rBuff是緩沖區(qū)的首地址箍铭,在創(chuàng)建的時候就用到汰规。
ringbuffer.c
環(huán)形緩沖區(qū)的創(chuàng)建
下面來看看源文件:
int8_t ICACHE_FLASH_ATTR rbCreate(rb_t* rb)
{
if(NULL == rb)
{
return -1;
}
rb->rbHead = rb->rbBuff;
rb->rbTail = rb->rbBuff;
return 0;
}
復(fù)制代碼
這是個創(chuàng)建環(huán)形緩沖區(qū)的函數(shù)根穷,就是初始化了環(huán)形緩沖區(qū)的頭尾指針醇蝴,這個函數(shù)的通用性很強(qiáng)吩坝,因為很多時候不只創(chuàng)建一個緩沖區(qū)。每個緩沖區(qū)的首地址都保存在了rbBuff哑蔫,這個在后面的通用性會很好用。但是杰杰還是覺得不夠好弧呐,因為我們在結(jié)構(gòu)體中定義了緩沖區(qū)的容量闸迷,但是在這里并沒有給他初始化,我覺得應(yīng)該傳入應(yīng)該參數(shù)俘枫,給緩沖區(qū)的容量進(jìn)行初始化一下腥沽。但是無所謂啦。
環(huán)形緩沖區(qū)的刪除
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;
}
復(fù)制代碼
把這些指針指向NULL鸠蚪,但是環(huán)形緩沖區(qū)本身地址的數(shù)據(jù)是不會被清除的今阳,只是表明了這些地址可以被重復(fù)使用了而已。
int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)
{
if(NULL == rb)
{
return -1;
}
return rb->rbCapacity;
}
復(fù)制代碼
獲取環(huán)形緩沖區(qū)的容量
int32_t ICACHE_FLASH_ATTR rbCapacity(rb_t *rb)
{
if(NULL == rb)
{
return -1;
}
return rb->rbCapacity;
}
復(fù)制代碼
因為可能有多個環(huán)形緩沖區(qū)茅信,但是容量我們不一定會知道盾舌,所以還是寫一個獲取它容量的函數(shù)比較好。
環(huán)形緩沖區(qū)可讀數(shù)據(jù)大小
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);
}
復(fù)制代碼
如果緩沖區(qū)是沒有被創(chuàng)建的蘸鲸,那么返回-1妖谴,表示非法,如果環(huán)形緩沖區(qū)的首尾都在一個位置酌摇,那么表面環(huán)形緩沖區(qū)沒有數(shù)據(jù)膝舅,那么是不可讀的,否則就返回正常的數(shù)據(jù)窑多,rb->rbTail - rb->rbHead / rbCapacity(rb) - (rb->rbHead - rb->rbTail)仍稀,請用數(shù)學(xué)的方法理解這段代碼。
獲取環(huán)形緩沖區(qū)可寫數(shù)據(jù)大小
同理獲取可寫數(shù)據(jù)也是一樣的
int32_t ICACHE_FLASH_ATTR rbCanWrite(rb_t *rb)
{
if(NULL == rb)
{
return -1;
}
return rbCapacity(rb) - rbCanRead(rb);
}
復(fù)制代碼
環(huán)形緩沖區(qū)讀數(shù)據(jù)
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;
}
}
}
復(fù)制代碼
如果是緩沖區(qū)沒被創(chuàng)建或者是讀數(shù)據(jù)地址非法(NULL)都將返回錯誤埂息。如果rb->rbHead < rb->rbTail技潘,就是可讀數(shù)據(jù)的地址是遞增的遥巴,那么可以直接讀數(shù)據(jù),讀取的最大數(shù)據(jù)不能超過緩沖區(qū)可讀最大數(shù)據(jù)崭篡,所以要用copySz = min(count, rbCanRead(rb));限制一下讀取數(shù)據(jù)的大小挪哄,因為是直接拷貝數(shù)據(jù),所以琉闪,在較多數(shù)據(jù)面前的話迹炼,這種做法很好,比如像網(wǎng)絡(luò)上的數(shù)據(jù)颠毙,更是適合用這種方法斯入。讀完之后把rbHead 頭指針重新更新,rb->rbHead += copySz;因為環(huán)形緩沖區(qū)在數(shù)據(jù)存儲(軟件地址上)是環(huán)形的蛀蜜,所以刻两,假如數(shù)據(jù)地址不是遞增的,那么無法直接拷貝滴某,需要分段拷貝磅摹,count < rbCapacity(rb)-(rb->rbHead - rb->rbBuff)如果要讀取的數(shù)據(jù)小于從環(huán)形緩沖區(qū)的首地址開始到環(huán)形緩沖區(qū)大小的地址,那么這段地址還是遞增的霎奢,所以可以直接拷貝過去户誓,并且把頭指針更新一下。
copySz = count;
memcpy(data, rb->rbHead, copySz);
rb->rbHead += copySz;
復(fù)制代碼
最后一種情況就是幕侠,需要分段讀取了帝美,先把頭指針到緩沖區(qū)最后一個地址的這部分讀取了,再加上從緩沖區(qū)首地址開始讀取count-copySz那么長數(shù)據(jù)的數(shù)據(jù)晤硕,就ok了悼潭。然后把兩端數(shù)據(jù)拼接起來。數(shù)據(jù)保存在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);
復(fù)制代碼
環(huán)形緩沖區(qū)寫數(shù)據(jù)
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;
}
}
復(fù)制代碼
與讀書同理的舰褪,將一定長度的數(shù)據(jù)從某段地(data)址寫入環(huán)形緩沖區(qū)。如果數(shù)據(jù)地址非法或者是可寫數(shù)據(jù)長度不夠疏橄,那么就會返回錯誤代碼抵知。先看后面的
else
{
memcpy(rb->rbTail, data, count);
rb->rbTail += count;
return count;
? ? }
復(fù)制代碼
如果寫數(shù)據(jù)的地址是地址的話,那么是可以直接寫的软族,注意的是刷喜,寫數(shù)據(jù)的地址并非讀數(shù)據(jù)的地址,剛好相反的立砸,可讀數(shù)據(jù)的地址是絕對不允許寫的掖疮。同理,假如寫書的地址不是遞增的話颗祝,那么浊闪,也是分成兩段恼布,假如寫入數(shù)據(jù)的長度小于從尾指針到環(huán)形緩沖區(qū)最后一個地址的長度,那么搁宾,寫入的這段數(shù)據(jù)其實其地址也是遞增的折汞,同樣是可以直接寫的,然后更新一下尾指針盖腿。
memcpy(rb->rbTail, data, count);
rb->rbTail += count;
if (rb->rbTail == rb->rbBuff+rbCapacity(rb))
{
rb->rbTail = rb->rbBuff;
}
復(fù)制代碼
否則爽待,也需要分段寫入,先寫入從尾指針到環(huán)形緩沖區(qū)最后一個地址的長度翩腐,然后從環(huán)形緩沖區(qū)的首地址開始再寫入剩下的數(shù)據(jù)長度count-tailAvailSz鸟款,
memcpy(rb->rbTail, data, tailAvailSz);
rb->rbTail = rb->rbBuff;
return tailAvailSz + rbWrite(rb, (char*)data+tailAvailSz, count-tailAvailSz);
復(fù)制代碼
好了,至此茂卦,源碼基本分析完畢何什,現(xiàn)在說說為什么比我的源碼寫得好,
第一點(diǎn)等龙,代碼的效率处渣,我寫的源碼是一個個數(shù)據(jù)的寫入,而機(jī)智云是一系列數(shù)據(jù)的寫入蛛砰。讀數(shù)據(jù)也是一樣霍比,一系列數(shù)據(jù)讀出,而我的源碼則是一個個數(shù)據(jù)讀出暴备,并且使用了求模的運(yùn)算防止指針越界,這在運(yùn)算中效率是不夠高的们豌。
第二代碼的健壯性涯捻,還是機(jī)智云的好,我的代碼是沒有檢查是否真正有有效的數(shù)據(jù)寫入望迎。同樣的代碼讀出也是檢查了讀出數(shù)據(jù)的地址是否真正有效障癌,防止數(shù)據(jù)非法丟失”缱穑總的來說涛浙,需要不斷成長,還是要研究研究別人商業(yè)上用的源碼摄欲,雖然說很多原理我們都知道轿亮,但是親自寫的話,不一定能寫得出來胸墙,
還有就是我注,重用現(xiàn)有源碼比創(chuàng)新的效率更高,因為并不是所有人都能另走捷徑迟隅,做開拓者的但骨,我們用已有的好東西足以励七。
END
需要源碼的同學(xué)可以在公眾號回復(fù)“機(jī)智云源碼”晚安!