kfifo是內核裏面的一個First In First Out數據結構,它採用環形循環隊列的數據結構來實現;它提供一個無邊界的字節流服務,最重要的一點是,它使用並行無鎖編程技術,即當它用於只有一個入隊線程和一個出隊線程的場情時,兩個線程能夠併發操做,而不須要任何加鎖行爲,就能夠保證kfifo的線程安全。git
具體什麼是環形緩衝區,請看我之前的文章
關於kfifo的相關概念我不會介紹,有興趣能夠看他的相關文檔,我只將其實現過程移植重寫,移植到適用stm32開發板上,而且按照我我的習慣從新命名, RingBuff->意爲環形緩衝區
環形緩衝區的結構體成員變量,具體含義看註釋。
buffer: 用於存放數據的緩存
size: buffer空間的大小
in, out: 和buffer一塊兒構成一個循環隊列。 in指向buffer中隊頭,並且out指向buffer中的隊尾github
typedef struct ringbuff { uint8_t *buffer; /* 數據區域 */ uint32_t size; /* 環形緩衝區大小 */ uint32_t in; /* 數據入隊指針 (in % size) */ uint32_t out; /* 數據出隊指針 (out % size) */ #if USE_MUTEX MUTEX_T *mutex; /* 支持rtos的互斥 */ #endif }RingBuff_t ;
建立一個環形緩衝區,爲了適應後續對緩衝區入隊出隊的高效操做,環形緩衝區的大小應爲2^n字節,
若是不是這個大小,則系統默認裁剪以對應緩衝區字節。
固然還能夠優化,不過我目前並未作,思路以下:若是系統支持動態分配內存,則向上對齊,避免浪費內存空間,不然就按照我默認的向下對齊,當內存越大,對齊致使內存泄漏則會越多。對齊採用的函數是roundup_pow_of_two
。若是系統支持互斥量,那麼還將建立一個互斥量用來作互斥訪問,防止多線程同時使用致使數據丟失。編程
/************************************************************ * @brief Create_RingBuff * @param rb:環形緩衝區句柄 * buffer:環形緩衝區的數據區域 * size:環形緩衝區的大小,緩衝區大小要爲2^n * @return err_t:ERR_OK表示建立成功,其餘表示失敗 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 用於建立一個環形緩衝區 ***********************************************************/ err_t Create_RingBuff(RingBuff_t* rb, uint8_t *buffer, uint32_t size ) { if((rb == NULL)||(buffer == NULL)||(size == 0)) { PRINT_ERR("data is null!"); return ERR_NULL; } PRINT_DEBUG("ringbuff size is %d!",size); /* 緩衝區大小必須爲2^n字節,系統會強制轉換, 不然可能會致使指針訪問非法地址。 空間大小越大,強轉時丟失內存越多 */ if(size&(size - 1)) { size = roundup_pow_of_two(size); PRINT_DEBUG("change ringbuff size is %d!",size); } rb->buffer = buffer; rb->size = size; rb->in = rb->out = 0; #if USE_MUTEX /* 建立信號量不成功 */ if(!create_mutex(rb->mutex)) { PRINT_ERR("create mutex fail!"); ASSERT(ASSERT_ERR); return ERR_NOK; } #endif PRINT_DEBUG("create ringBuff ok!"); return ERR_OK; }
/************************************************************ * @brief roundup_pow_of_two * @param size:傳遞進來的數據長度 * @return size:返回處理以後的數據長度 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 用於處理數據,使數據長度必須爲 2^n * 若是不是,則轉換,丟棄多餘部分,如 * roundup_pow_of_two(66) -> 返回 64 ***********************************************************/ static unsigned long roundup_pow_of_two(unsigned long x) { return (1 << (fls(x-1)-1)); //向下對齊 //return (1UL << fls(x - 1)); //向上對齊,用動態內存可用使用 }
刪除一個環形緩衝區,刪除以後,緩衝區真正存儲地址是不會被改變的(目前我是使用自定義數組作緩衝區的),可是刪除以後,就沒法對緩衝區進行讀寫操做。而且若是支持os的話,建立的互斥量會被刪除。數組
/************************************************************ * @brief Delete_RingBuff * @param rb:環形緩衝區句柄 * @return err_t:ERR_OK表示成功,其餘表示失敗 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 刪除一個環形緩衝區 ***********************************************************/ err_t Delete_RingBuff(RingBuff_t *rb) { if(rb == NULL) { PRINT_ERR("ringbuff is null!"); return ERR_NULL; } rb->buffer = NULL; rb->size = 0; rb->in = rb->out = 0; #if USE_MUTEX if(!deleta_mutex(rb->mutex)) { PRINT_DEBUG("deleta mutex is fail!"); return ERR_NOK; } #endif return ERR_OK; }
向環形緩衝區寫入指定數據,支持線程互斥訪問。用戶想要寫入緩衝區的數據長度不必定是真正入隊的長度,在完成的時候還要看看返回值是否與用戶須要的長度一致~
這個函數頗有意思,也是比較高效的入隊操做,將指定區域的數據拷貝到指定的緩衝區中,過程看註釋便可緩存
/************************************************************ * @brief Write_RingBuff * @param rb:環形緩衝區句柄 * @param wbuff:寫入的數據起始地址 * @param len:寫入數據的長度(字節) * @return len:實際寫入數據的長度(字節) * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 這個函數會從buff空間拷貝len字節長度的數據到 rb環形緩衝區中的空閒空間。 ***********************************************************/ uint32_t Write_RingBuff(RingBuff_t *rb, uint8_t *wbuff, uint32_t len) { uint32_t l; #if USE_MUTEX /* 請求互斥量,成功才能進行ringbuff的訪問 */ if(!request_mutex(rb->mutex)) { PRINT_DEBUG("request mutex fail!"); return 0; } else /* 獲取互斥量成功 */ { #endif len = min(len, rb->size - rb->in + rb->out); /* 第一部分的拷貝:從環形緩衝區寫入數據直至緩衝區最後一個地址 */ l = min(len, rb->size - (rb->in & (rb->size - 1))); memcpy(rb->buffer + (rb->in & (rb->size - 1)), wbuff, l); /* 若是溢出則在緩衝區頭寫入剩餘的部分 若是沒溢出這句代碼至關於無效 */ memcpy(rb->buffer, wbuff + l, len - l); rb->in += len; PRINT_DEBUG("write ringBuff len is %d!",len); #if USE_MUTEX } /* 釋放互斥量 */ release_mutex(rb->mutex); #endif return len; }
讀取緩衝區數據到指定區域,用戶指定讀取長度,用戶想要讀取的長度不必定是真正讀取的長度,在讀取完成的時候還要看看返回值是否與用戶須要的長度一致~也支持多線程互斥訪問。
也是緩衝區出隊的高效操做。過程看代碼註釋便可安全
/************************************************************ * @brief Read_RingBuff * @param rb:環形緩衝區句柄 * @param wbuff:讀取數據保存的起始地址 * @param len:想要讀取數據的長度(字節) * @return len:實際讀取數據的長度(字節) * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 這個函數會從rb環形緩衝區中的數據區域拷貝len字節 長度的數據到rbuff空間。 ***********************************************************/ uint32_t Read_RingBuff(RingBuff_t *rb, uint8_t *rbuff, uint32_t len) { uint32_t l; #if USE_MUTEX /* 請求互斥量,成功才能進行ringbuff的訪問 */ if(!request_mutex(rb->mutex)) { PRINT_DEBUG("request mutex fail!"); return 0; } else { #endif len = min(len, rb->in - rb->out); /* 第一部分的拷貝:從環形緩衝區讀取數據直至緩衝區最後一個 */ l = min(len, rb->size - (rb->out & (rb->size - 1))); memcpy(rbuff, rb->buffer + (rb->out & (rb->size - 1)), l); /* 若是溢出則在緩衝區頭讀取剩餘的部分 若是沒溢出這句代碼至關於無效 */ memcpy(rbuff + l, rb->buffer, len - l); rb->out += len; PRINT_DEBUG("read ringBuff len is %d!",len); #if USE_MUTEX } /* 釋放互斥量 */ release_mutex(rb->mutex); #endif return len; }
這些就比較簡單了,看看緩衝區可讀可寫的數據有多少數據結構
/************************************************************ * @brief CanRead_RingBuff * @param rb:環形緩衝區句柄 * @return uint32:可讀數據長度 0 / len * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 可讀數據長度 ***********************************************************/ uint32_t CanRead_RingBuff(RingBuff_t *rb) { if(NULL == rb) { PRINT_ERR("ringbuff is null!"); return 0; } if(rb->in == rb->out) return 0; if(rb->in > rb->out) return (rb->in - rb->out); return (rb->size - (rb->out - rb->in)); } /************************************************************ * @brief CanRead_RingBuff * @param rb:環形緩衝區句柄 * @return uint32:可寫數據長度 0 / len * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 可寫數據長度 ***********************************************************/ uint32_t CanWrite_RingBuff(RingBuff_t *rb) { if(NULL == rb) { PRINT_ERR("ringbuff is null!"); return 0; } return (rb->size - CanRead_RingBuff(rb)); }
這裏的代碼我是用於測試的,隨便寫的多線程
RingBuff_t ringbuff_handle; uint8_t rb[64]; uint8_t res[64]; Create_RingBuff(&ringbuff_handle, rb, sizeof(rb)); Write_RingBuff(&ringbuff_handle, res, datapack.data_length); PRINT_DEBUG("CanRead_RingBuff = %d!",CanRead_RingBuff(&ringbuff_handle)); PRINT_DEBUG("CanWrite_RingBuff = %d!",CanWrite_RingBuff(&ringbuff_handle)); Read_RingBuff(&ringbuff_handle, res, datapack.data_length);
此處模仿了文件系統的互斥操做併發
#if USE_MUTEX #define MUTEX_TIMEOUT 1000 /* 超時時間 */ #define MUTEX_T mutex_t /* 互斥量控制塊 */ #endif /*********************************** mutex **************************************************/ #if USE_MUTEX /************************************************************ * @brief create_mutex * @param mutex:建立信號量句柄 * @return 建立成功爲1,0爲不成功。 * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 建立一個互斥量,用戶在os中互斥使用ringbuff, * 支持的os有rtt、win3二、ucos、FreeRTOS、LiteOS ***********************************************************/ static err_t create_mutex(MUTEX_T *mutex) { err_t ret = 0; // *mutex = rt_mutex_create("test_mux",RT_IPC_FLAG_PRIO); /* rtt */ // ret = (err_t)(*mutex != RT_NULL); // *mutex = CreateMutex(NULL, FALSE, NULL); /* Win32 */ // ret = (err_t)(*mutex != INVALID_HANDLE_VALUE); // *mutex = OSMutexCreate(0, &err); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // *mutex = xSemaphoreCreateMutex(); /* FreeRTOS */ // ret = (err_t)(*mutex != NULL); // ret = LOS_MuxCreate(&mutex); /* LiteOS */ // ret = (err_t)(ret != LOS_OK); return ret; } /************************************************************ * @brief deleta_mutex * @param mutex:互斥量句柄 * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 刪除一個互斥量,支持的os有rtt、win3二、ucos、FreeRTOS、LiteOS ***********************************************************/ static err_t deleta_mutex(MUTEX_T *mutex) { err_t ret; // ret = rt_mutex_delete(mutex); /* rtt */ // ret = CloseHandle(mutex); /* Win32 */ // OSMutexDel(mutex, OS_DEL_ALWAYS, &err); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // vSemaphoreDelete(mutex); /* FreeRTOS */ // ret = 1; // ret = LOS_MuxDelete(&mutex); /* LiteOS */ // ret = (err_t)(ret != LOS_OK); return ret; } /************************************************************ * @brief request_mutex * @param mutex:互斥量句柄 * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 請求一個互斥量,獲得互斥量的線程才容許進行訪問緩衝區 * 支持的os有rtt、win3二、ucos、FreeRTOS、LiteOS ***********************************************************/ static err_t request_mutex(MUTEX_T *mutex) { err_t ret; // ret = (err_t)(rt_mutex_take(mutex, MUTEX_TIMEOUT) == RT_EOK);/* rtt */ // ret = (err_t)(WaitForSingleObject(mutex, MUTEX_TIMEOUT) == WAIT_OBJECT_0); /* Win32 */ // OSMutexPend(mutex, MUTEX_TIMEOUT, &err)); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // ret = (err_t)(xSemaphoreTake(mutex, MUTEX_TIMEOUT) == pdTRUE); /* FreeRTOS */ // ret = (err_t)(LOS_MuxPend(mutex,MUTEX_TIMEOUT) == LOS_OK); /* LiteOS */ return ret; } /************************************************************ * @brief release_mutex * @param mutex:互斥量句柄 * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 釋放互斥量,當線程使用完資源必須釋放互斥量 * 支持的os有rtt、win3二、ucos、FreeRTOS、LiteOS ***********************************************************/ static void release_mutex(MUTEX_T *mutex) { // rt_mutex_release(mutex);/* rtt */ // ReleaseMutex(mutex); /* Win32 */ // OSMutexPost(mutex); /* uC/OS-II */ // xSemaphoreGive(mutex); /* FreeRTOS */ // LOS_MuxPost(mutex); /* LiteOS */ } #endif /*********************************** mutex **************************************************/
最後送一份debug的簡便操做源碼,由於前文不少時候會調用PRINT_ERR
PRINT_DEBUG
函數
#ifndef _DEBUG_H #define _DEBUG_H /************************************************************ * @brief debug.h * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 此文件用於打印日誌信息 ***********************************************************/ /** * @name Debug print * @{ */ #define PRINT_DEBUG_ENABLE 1 /* 打印調試信息 */ #define PRINT_ERR_ENABLE 1 /* 打印錯誤信息 */ #define PRINT_INFO_ENABLE 0 /* 打印我的信息 */ #if PRINT_DEBUG_ENABLE #define PRINT_DEBUG(fmt, args...) do{(printf("\n[DEBUG] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_DEBUG(fmt, args...) #endif #if PRINT_ERR_ENABLE #define PRINT_ERR(fmt, args...) do{(printf("\n[ERR] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_ERR(fmt, args...) #endif #if PRINT_INFO_ENABLE #define PRINT_INFO(fmt, args...) do{(printf("\n[INFO] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_INFO(fmt, args...) #endif /**@} */ //針對不一樣的編譯器調用不一樣的stdint.h文件 #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> #endif /* 斷言 Assert */ #define AssertCalled(char,int) printf("\nError:%s,%d\r\n",char,int) #define ASSERT(x) if((x)==0) AssertCalled(__FILE__,__LINE__) typedef enum { ASSERT_ERR = 0, /* 錯誤 */ ASSERT_SUCCESS = !ASSERT_ERR /* 正確 */ } Assert_ErrorStatus; typedef enum { FALSE = 0, /* 假 */ TRUE = !FALSE /* 真 */ }ResultStatus; #endif /* __DEBUG_H */
相關代碼能夠在公衆號後臺獲取。歡迎關注「物聯網IoT開發」公衆號