发新帖本帖赏金 10.00元(功能说明)我要提问
返回列表

基于Linux的kfifo移植到STM32(支持os的互斥访问)

[复制链接]
3446|12
手机看帖
扫描二维码
随时随地手机跟帖
caijie001|  楼主 | 2018-12-27 08:45 | 显示全部楼层 |阅读模式
本帖最后由 caijie001 于 2018-12-27 09:37 编辑

基于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 **************************************************/
游客,如果您要查看本帖隐藏内容请回复


   

打赏榜单

21ic小喇叭 打赏了 10.00 元 2018-12-28
理由:继续分享~~

相关帖子

caijie001|  楼主 | 2018-12-27 08:45 | 显示全部楼层
1楼留给自己~

使用特权

评论回复
123tt| | 2018-12-27 19:50 | 显示全部楼层
想问一下,什麽是入队线程?什麽是出队线程?
如果两个线程,读/写一个ringBuffer,就一定要用mutex。那怎麽做到你说的无锁编程技术?

使用特权

评论回复
caijie001|  楼主 | 2018-12-28 08:37 | 显示全部楼层
123tt 发表于 2018-12-27 19:50
想问一下,什麽是入队线程?什麽是出队线程?
如果两个线程,读/写一个ringBuffer,就一定要用mutex。那怎 ...

#if USE_MUTEX
  /* 请求互斥量,成功才能进行ringbuff的访问 */
  if(!request_mutex(rb->mutex))
  {
    PRINT_DEBUG("request mutex fail!");
    return 0;
  }
  else  /* 获取互斥量成功 */
  {
#endif

通过宏定义就选择要不要使用互斥量了啊~

使用特权

评论回复
123tt| | 2018-12-28 22:52 | 显示全部楼层
caijie001 发表于 2018-12-28 08:37
#if USE_MUTEX
  /* 请求互斥量,成功才能进行ringbuff的访问 */
  if(!request_mutex(rb->mutex))

我有仔细看过代码,也知道这个宏定义。
所以你说的无锁编程技术,就是指这个宏定义?

楼主能否说明一下代码中哪一部份能体现出这无锁编程技术的思路?让我们学学,叁考叁考

使用特权

评论回复
caijie001|  楼主 | 2018-12-29 08:40 | 显示全部楼层
123tt 发表于 2018-12-28 22:52
我有仔细看过代码,也知道这个宏定义。
所以你说的无锁编程技术,就是指这个宏定义?

那是Linux中kfifo的介绍~在32中根本没法做到两个线程并行,可以支持非互斥访问,因为读写指针的操作是不一样的,可以进行非互斥的读写操作(个人感觉是这样子的)

使用特权

评论回复
ypdxng| | 2018-12-29 08:48 | 显示全部楼层
先看看,能不能用。

使用特权

评论回复
caijie001|  楼主 | 2018-12-29 11:29 | 显示全部楼层
ypdxng 发表于 2018-12-29 08:48
先看看,能不能用。

肯定可以用啊~不能用发出来干嘛~

使用特权

评论回复
sunwumcu| | 2018-12-29 13:31 | 显示全部楼层
学习下,谢谢分享!

使用特权

评论回复
123tt| | 2018-12-29 19:32 | 显示全部楼层
caijie001 发表于 2018-12-29 08:40
那是Linux中kfifo的介绍~在32中根本没法做到两个线程并行,可以支持非互斥访问,因为读写指针的操作是不一 ...

百度了一下kfifo,终於搞明白了一点点了。
它说的无销技述,其实就是运用了内存屏障这一概念。

有兴趣的朋友可看这里:https://www.linuxidc.com/Linux/2016-12/137936.htm

linux的代码果然博大精深,谢谢楼主分享。

使用特权

评论回复
iibull| | 2019-3-13 14:25 | 显示全部楼层
试试能不能用啊

使用特权

评论回复
bear1| | 2019-7-23 11:06 | 显示全部楼层
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));
}

这里整复杂了。

使用特权

评论回复
shengyanbo| | 2019-12-26 09:20 | 显示全部楼层
学习了

使用特权

评论回复
发新帖 本帖赏金 10.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:21ic公开课专区:http://open.21ic.com/ 21ic资料下载中心:http://dl.21ic.com/ 21ic项目外包中心:http://project.21ic.com/ 杰杰欢迎大家有空常来赛事专区逛逛

131

主题

3790

帖子

63

粉丝