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开发”公众号