Ring Buffer的高级用法(类似内核KFIFO)

先上代码

代码参考Linux内核Kfifo.

数据结构

数据结构中定义的缓存区大小一定要是2的n,当然也可以用动态分配来分配缓存区的大小,但是使用该高级用法一定要遵循分配的缓存区大小是2的n次方;至于为什么要这样先留个悬念,后面见原因;

#define MIN(a, b) (((a) < (b)) ? (a) : (b)) /* 取a和b中最小值 */

#define RING_BUFFER_SIZE       4096	//大小一定要为2的n次方才能使用该高级用法 

typedef struct {
    char buffer[RING_BUFFER_SIZE];  /* 缓冲区 ,大小一定要为2的n次方才能使用该高级用法 */
    unsigned int size;              /* 大小  注意要用unsigned类型*/
    unsigned int in;                /* 入口位置  注意要用unsigned类型*/
  	unsigned int out;               /* 出口位置  注意要用unsigned类型*/
} RingBuffer_t;

数据入队操作

先来看数据入队的操作,分以下几种情况分析:
1、ring_buf_p->in 、ring_buf_p->out均小于size;
只有开始使用循环队列的阶段才会是这种情况,先分析size = MIN(size, ring_buf_p->size - ring_buf_p->in + ring_buf_p->out);这句代码;代码补全为size = MIN(size, ring_buf_p->size - (ring_buf_p->in - ring_buf_p->out)); 由于ring_buf_p->in为入指针,ring_buf_p->out为出指针,则(ring_buf_p->in - ring_buf_p->out)即为循环缓存区已经被使用的大小,而 ring_buf_p->size - (ring_buf_p->in - ring_buf_p->out)即为循环缓存区剩余未使用的大小,与即将要写入的数据大小取二者中较小的,保证填入的数据不会出现越界或覆盖原有的数据。我们在看看len = MIN(size, ring_buf_p->size - (ring_buf_p->in & (ring_buf_p->size - 1)));这条语句,有的人可能不太理解 (ring_buf_p->in & (ring_buf_p->size - 1)))是什么意思,其实就是和ring_buf_p->in % ring_buf_p->size 的作用是一样的,就是取余;但是 (ring_buf_p->in & (ring_buf_p->size - 1)))的代码执行效率要比ring_buf_p->in % ring_buf_p->size高很多,在一下对实时性要求很高的使用场景下,代码的执行效率是要求很苛刻的;这是又要分两种情况讨论,第一种size小于等于ring_buf_p->size - (ring_buf_p->in & (ring_buf_p->size - 1));这说明循环缓存区的后半部分的未使用大小足够放下要写入的数据大小,数据只要一次就能完全写完进循环缓存区;第二种size大于ring_buf_p->size - (ring_buf_p->in & (ring_buf_p->size - 1));这说明循环缓存区的后半部分的未使用大小无法放下要写入的数据大小,数据只要分两次才能写入循环缓存区;第一次写入将后半部分剩余的缓存区大小使用完,第二次写入将剩余的未写入的数据大小从循环缓存区的首地址开始写入(这也就是循环缓冲区的作用,使用较小的实际物理内存实现了线性缓存);
2、ring_buf_p->in大于size 、ring_buf_p->out小于size;或 ring_buf_p->in 、ring_buf_p->out均大于于size;
这种情况才是体现改高级用法的时候,数据的写入和读取导致入指针域出指针的大小超过size的大小,先说明数据结构定义时为为什么要要求指针和大小的数据类型一定要为unsigned,因为在本高级用法中,没有用size的大小区取限制指针的大小的,入指针与出指针的大小均可以达到对于数据大小的最大值,而我们知道无符号类型的数据,大小超过最大值时,会出现溢出,导致数值又会从零开始变化,比如unsigned char, 254 + = 1,就是255 ,而255在计算机中的二进制存储为11111111,所以255+1,在计算机的存储就会变成100000000,而由于unsigned char只有八位,就会出现“溢出”的现象,所以255+1的结果为0,高级用法就是利用了无符号类型的数据特性。而至于为什么要使用大小要使用2的n次方的原因也是因为,所有的无符号数据类型的数值个数为2的n次方个,例如我们使用的指针类型为unsigned char, size的大小也使用2的8次方,也就是256,unsigned char的数据范围为0~255正好与数据中的每个字节一一对应。而当使用的size大小为2的7次方,也就是128时,size的也是可以整除unsigned char可以数据范围个数的,所以unsigned char的是任一个数对size可以取余都会落在每一个直接所对应的所有上。而且size使用2的n次方可以使用(ring_buf_p->in & (ring_buf_p->size - 1))取余这样的高级用法

unsigned int RingBufferPut(RingBuffer_t *ring_buf_p, void *buffer, hd_u32_t size)
{
   unsigned int len = 0;
   if(ring_buf_p == NULL ||  buffer == NULL) {
       return -1;
   }
   size = MIN(size, ring_buf_p->size - ring_buf_p->in + ring_buf_p->out);
   /* first put the data starting from fifo->in to buffer end */
   len  = MIN(size, ring_buf_p->size - (ring_buf_p->in & (ring_buf_p->size - 1)));
   memcpy(ring_buf_p->buffer + (ring_buf_p->in & (ring_buf_p->size - 1)), buffer, len);
   /* then put the rest (if any) at the beginning of the buffer */
   if (size - len > 0) {
       memcpy(ring_buf_p->buffer, (char *)buffer + len, size - len);
   }
   ring_buf_p->in += size;
   return size;
}

数据出队操作

先来看数据入队的操作,参考入队的解释说明

unsigned int RingBufferGet(RingBuffer_t *ring_buf_p, void *buffer, hd_u32_t size)
{
  unsigned int len = 0;
  if(ring_buf_p == NULL || buffer == NULL) {
  	return -1;
  }
  size  = MIN(size, ring_buf_p->in - ring_buf_p->out);
  /* first get the data from fifo->out until the end of the buffer */
  len = MIN(size, ring_buf_p->size - (ring_buf_p->out & (ring_buf_p->size - 1)));
  memcpy(buffer, ring_buf_p->buffer + (ring_buf_p->out & (ring_buf_p->size - 1)), len);
  /* then get the rest (if any) from the beginning of the buffer */
  if (size - len > 0) {
      memcpy((char *)buffer + len, ring_buf_p->buffer, size - len);
  }
  ring_buf_p->out += size;
  return size;
}

获取队列中可读数据的大小

unsigned int RingBufferLen(const RingBuffer_t *ring_buf_p)
{
   return (ring_buf_p->in - ring_buf_p->out);
}

清空循环队列

void RingBufferClear(RingBuffer_t *ring_buf_p)
{
  ring_buf_p->in = 0;
  ring_buf_p->out = 0;
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以参考以下代码实现一个简单的Linux kfifo demo: ``` #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/kfifo.h> #define FIFO_SIZE 1024 MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A Linux kfifo demo"); static char *fifo_buffer; static struct kfifo my_fifo; static int my_open(struct inode *inode, struct file *file) { printk(KERN_INFO "my device opened\n"); return 0; } static int my_close(struct inode *inode, struct file *file) { printk(KERN_INFO "my device closed\n"); return 0; } static ssize_t my_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret; if (kfifo_is_empty(&my_fifo)) { return -EAGAIN; } ret = kfifo_to_user(&my_fifo, user_buf, count, ppos); if (ret) { printk(KERN_INFO "Read %ld bytes\n", (long)ret); } else { printk(KERN_ERR "Failed to read\n"); } return ret; } static ssize_t my_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret; if (kfifo_avail(&my_fifo) < count) { return -ENOMEM; } ret = kfifo_from_user(&my_fifo, user_buf, count, ppos); if (ret) { printk(KERN_INFO "Wrote %ld bytes\n", (long)ret); } else { printk(KERN_ERR "Failed to write\n"); } return ret; } static struct file_operations my_fops = { .owner = THIS_MODULE, .open = my_open, .release = my_close, .read = my_read, .write = my_write, }; static int __init my_init(void) { int ret; fifo_buffer = kmalloc(FIFO_SIZE, GFP_KERNEL); if (!fifo_buffer) { return -ENOMEM; } ret = kfifo_init(&my_fifo, fifo_buffer, FIFO_SIZE); if (ret) { kfree(fifo_buffer); return ret; } ret = register_chrdev(0, "my_device", &my_fops); if (ret < 0) { kfifo_free(&my_fifo); kfree(fifo_buffer); return ret; } printk(KERN_INFO "my device registered with major number %d\n", ret); return 0; } static void __exit my_exit(void) { unregister_chrdev(0, "my_device"); kfifo_free(&my_fifo); kfree(fifo_buffer); printk(KERN_INFO "my device unregistered\n"); } module_init(my_init); module_exit(my_exit); ``` 该代码实现了一个简单的字符设备,它使用了Linux内核中提供的kfifo数据结构来实现队列的功能,用户可以通过该设备的文件描述符进行读写操作,读取数据会将队列中的数据写入用户缓冲区,写入数据会将用户缓冲区中的数据写入队列中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值