基於Linux的kfifo移植到STM32(支援os的互斥訪問)

  • 2019 年 10 月 16 日
  • 筆記

基於Linux的kfifo移植到STM32(支援os的互斥訪問)

關於kfifo

kfifo是內核裡面的一個First In First Out數據結構,它採用環形循環隊列的數據結構來實現;它提供一個無邊界的位元組流服務,最重要的一點是,它使用並行無鎖編程技術,即當它用於只有一個入隊執行緒和一個出隊執行緒的場情時,兩個執行緒可以並發操作,而不需要任何加鎖行為,就可以保證kfifo的執行緒安全。

具體什麼是環形緩衝區,請看我以前的文章

說明

關於kfifo的相關概念我不會介紹,有興趣可以看他的相關文檔,我只將其實現過程移植重寫,移植到適用stm32開發板上,並且按照我個人習慣重新命名,RingBuff->意為環形緩衝區

RingBuff_t

環形緩衝區的結構體成員變數,具體含義看注釋。
buffer: 用於存放數據的快取
size: buffer空間的大小
in, out: 和buffer一起構成一個循環隊列。 in指向buffer中隊頭,而且out指向buffer中的隊尾

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 ;

Create_RingBuff

創建一個環形緩衝區,為了適應後續對緩衝區入隊出隊的高效操作,環形緩衝區的大小應為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;  }

roundup_pow_of_two

/************************************************************    * @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));         //向上對齊,用動態記憶體可用使用  }

Delete_RingBuff

刪除一個環形緩衝區,刪除之後,緩衝區真正存儲地址是不會被改變的(目前我是使用自定義數組做緩衝區的),但是刪除之後,就無法對緩衝區進行讀寫操作。並且如果支援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;  }

Write_RingBuff

向環形緩衝區寫入指定數據,支援執行緒互斥訪問。用戶想要寫入緩衝區的數據長度不一定是真正入隊的長度,在完成的時候還要看看返回值是否與用戶需要的長度一致~
這個函數很有意思,也是比較高效的入隊操作,將指定區域的數據拷貝到指定的緩衝區中,過程看注釋即可

/************************************************************    * @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;  }

Read_RingBuff

讀取緩衝區數據到指定區域,用戶指定讀取長度,用戶想要讀取的長度不一定是真正讀取的長度,在讀取完成的時候還要看看返回值是否與用戶需要的長度一致~也支援多執行緒互斥訪問。
也是緩衝區出隊的高效操作。過程看程式碼注釋即可

/************************************************************    * @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);

支援多個os的互斥量操作

此處模仿了文件系統的互斥操作

#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、win32、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、win32、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、win32、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、win32、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.h

最後送一份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,%drn",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開發」公眾號!