Linux内核中的循环缓冲区(circular buffer)为解决某些特殊情况下的竞争问题提供了一种免锁的方法。这种特殊的情况就是当生产者和消费者都只有一个,而在其它情况下使用它也是必须要加锁的。

循环缓冲区定义在include/linux/kfifo.h中,如下:

struct kfifo {
unsigned char *buffer; // buffer指向存放数据的缓冲区
unsigned int size;        // size是缓冲区的大小
unsigned int in;           // in是写指针下标
unsigned int out;        // out是读指针下标
spinlock_t *lock;         // lock是加到struct kfifo上的自旋锁
}; 
      (上面说的免锁不是免这里的锁,这个锁是必须的),防止多个进程并发访问此数据结构。当in==out时,说明缓冲区为空;当(in-out)==size时,说明缓冲区已满。
       为kfifo提供的接口可以分为两类:
       一类是满足上述情况下使用的,以双下划线开头,没有加锁的;
       另一类是在不满足的条件下,即需要额外加锁的情况下使用的。
       其实后一类只是在前一类的基础上进行加锁后的包装(也有一处进行了小的改进),实现中所加的锁是spin_lock_irqsave。

清空缓冲区的函数:
static inline void __kfifo_reset(struct kfifo *fifo);
static inline void kfifo_reset(struct kfifo *fifo);
这很简单,直接把读写指针都置为0即可。

向缓冲区里放入数据的接口是:
static inline unsigned int kfifo_put(struct kfifo *fifo, unsigned char *buffer, unsigned int len);
unsigned int __kfifo_put(struct kfifo *fifo, unsigned char *buffer, unsigned int len);

后者是在kernel/kfifo.c中定义的。这个接口是经过精心构造的,可以小心地避免一些边界情况。我们有必要一起来看一下它的具体实现。

 1: /**
 2: * __kfifo_put - puts some data into the FIFO, no locking version
 3: * @fifo: the fifo to be used.
 4: * @buffer: the data to be added.
 5: * @len: the length of the data to be added.
 6: *
 7: * This function copies at most @len bytes from the @buffer into
 8: * the FIFO depending on the free space, and returns the number of
 9: * bytes copied.
 10: *
 11: * Note that with only one concurrent reader and one concurrent
 12: * writer, you don't need extra locking to use these functions.
 13: */
 14: unsigned int __kfifo_put(struct kfifo *fifo,
 15:  const unsigned char *buffer, unsigned int len)
 16: {
 17: unsigned int l;
 18:  
 19: len = min(len, fifo->size - fifo->in + fifo->out);
 20:  
 21:  /*
 22: * Ensure that we sample the fifo->out index -before- we
 23: * start putting bytes into the kfifo.
 24: */
 25:  
 26: smp_mb();
 27:  
 28:  /* first put the data starting from fifo->in to buffer end */
 29: l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
 30: memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
 31:  
 32:  /* then put the rest (if any) at the beginning of the buffer */
 33: memcpy(fifo->buffer, buffer + l, len - l);
 34:  
 35:  /*
 36: * Ensure that we add the bytes to the kfifo -before-
 37: * we update the fifo->in index.
 38: */
 39:  
 40: smp_wmb();
 41:  
 42: fifo->in += len;
 43:  
 44:  return len;
 45: }
 46: EXPORT_SYMBOL(__kfifo_put);

 1: /**
 2: * kfifo_put - puts some data into the FIFO
 3: * @fifo: the fifo to be used.
 4: * @buffer: the data to be added.
 5: * @len: the length of the data to be added.
 6: *
 7: * This function copies at most @len bytes from the @buffer into
 8: * the FIFO depending on the free space, and returns the number of
 9: * bytes copied.
 10: */
 11: static inline unsigned int kfifo_put(struct kfifo *fifo,
 12:  const unsigned char *buffer, unsigned int len)
 13: {
 14: unsigned long      
 15: unsigned int ret;
 16:  
 17: spin_lock_irqsave(fifo->lock, flags);
 18:  
 19: ret = __kfifo_put(fifo, buffer, len);
 20:  
 21: spin_unlock_irqrestore(fifo->lock, flags);
 22:  
 23:  return ret;
 24: }

len = min(len, fifo->size - fifo->in + fifo->out);
      在 len(fifo->size - fifo->in + fifo->out) 之间取一个较小的值赋给len。注意,当 (fifo->in == fifo->out+fifo->size) 时,表示缓冲区已满,此时得到的较小值一定是0,后面实际写入的字节数也全为0。
      另一种边界情况是当 len 很大时(因为len是无符号的,负数对它来说也是一个很大的正数),这一句也能保证len取到一个较小的值,因为    fifo->in 总是大于等于 fifo->out ,所以后面的那个表达式 l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); 的值不会超过fifo->size的大小。
      smp_mb();  smp_wmb(); 是加内存屏障,这里不是我们讨论的范围,你可以忽略它。
      l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));    是把上一步决定的要写入的字节数len “切开”,这里又使用了一个技巧。注意:实际分配给 fifo->buffer 的字节数 fifo->size,必须是2的幂,否则这里就会出错。既然 fifo->size 是2的幂,那么 (fifo->size-1) 也就是一个后面几位全为1的数,也就能保证(fifo->in & (fifo->size - 1)) 总为不超过 (fifo->size - 1) 的那一部分,和 (fifo->in)% (fifo->size - 1) 的效果一样。
      这样后面的代码就不难理解了,它先向  fifo->in  到缓冲区末端这一块写数据,如果还没写完,在从缓冲区头开始写入剩下的,从而实现了循环缓冲。最后,把写指针后移 len 个字节,并返回len。
       从上面可以看出,fifo->in的值可以从0变化到超过fifo->size的数值,fifo->out也如此,但它们的差不会超过fifo->size。

从kfifo向外读数据的函数是:
static inline unsigned int kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len);
unsigned int __kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len);

 1:  
 2: /**
 3: * __kfifo_get - gets some data from the FIFO, no locking version
 4: * @fifo: the fifo to be used.
 5: * @buffer: where the data must be copied.
 6: * @len: the size of the destination buffer.
 7: *
 8: * This function copies at most @len bytes from the FIFO into the
 9: * @buffer and returns the number of copied bytes.
 10: *
 11: * Note that with only one concurrent reader and one concurrent
 12: * writer, you don't need extra locking to use these functions.
 13: */
 14: unsigned int __kfifo_get(struct kfifo *fifo,
 15: unsigned char *buffer, unsigned int len)
 16: {
 17: unsigned int l;
 18:  
 19: len = min(len, fifo->in - fifo->out);
 20:  
 21:  /*
 22: * Ensure that we sample the fifo->in index -before- we
 23: * start removing bytes from the kfifo.
 24: */
 25:  
 26: smp_rmb();
 27:  
 28:  /* first get the data from fifo->out until the end of the buffer */
 29: l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
 30: memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
 31:  
 32:  /* then get the rest (if any) from the beginning of the buffer */
 33: memcpy(buffer + l, fifo->buffer, len - l);
 34:  
 35:  /*
 36: * Ensure that we remove the bytes from the kfifo -before-
 37: * we update the fifo->out index.
 38: */
 39:  
 40: smp_mb();
 41:  
 42: fifo->out += len;
 43:  
 44:  return len;
 45: }
 46: EXPORT_SYMBOL(__kfifo_get);

 1:  
 2: /**
 3: * kfifo_get - gets some data from the FIFO
 4: * @fifo: the fifo to be used.
 5: * @buffer: where the data must be copied.
 6: * @len: the size of the destination buffer.
 7: *
 8: * This function copies at most @len bytes from the FIFO into the
 9: * @buffer and returns the number of copied bytes.
 10: */
 11: static inline unsigned int kfifo_get(struct kfifo *fifo,
 12: unsigned char *buffer, unsigned int len)
 13: {
 14: unsigned long flags;
 15: unsigned int ret;
 16:  
 17: spin_lock_irqsave(fifo->lock, flags);
 18:  
 19: ret = __kfifo_get(fifo, buffer, len);
 20:  
 21:  /*
 22: * optimization: if the FIFO is empty, set the indices to 0
 23: * so we don't wrap the next time
 24: */
 25:  if (fifo->in == fifo->out)
 26: fifo->in = fifo->out = 0;
 27:  
 28: spin_unlock_irqrestore(fifo->lock, flags);
 29:  
 30:  return ret;
 31: }

和上面的__kfifo_put类似,不难分析。

static inline unsigned int __kfifo_len(struct kfifo *fifo);
static inline unsigned int kfifo_len(struct kfifo *fifo);

 1:  
 2: /**
 3: * __kfifo_len - returns the number of bytes available in the FIFO, no locking version
 4: * @fifo: the fifo to be used.
 5: */
 6: static inline unsigned int __kfifo_len(struct kfifo *fifo)
 7: {
 8:  return fifo->in - fifo->out;
 9: }
 10:  
 11: /**
 12: * kfifo_len - returns the number of bytes available in the FIFO
 13: * @fifo: the fifo to be used.
 14: */
 15: static inline unsigned int kfifo_len(struct kfifo *fifo)
 16: {
 17: unsigned long flags;
 18: unsigned int ret;
 19:  
 20: spin_lock_irqsave(fifo->lock, flags);
 21:  
 22: ret = __kfifo_len(fifo);
 23:  
 24: spin_unlock_irqrestore(fifo->lock, flags);
 25:  
 26:  return ret;
 27: }

这两个函数返回缓冲区中实际的字节数,只要用fifo->in减去fifo->out即可。

kernel/kfifo.c中还提供了初始化kfifo,分配和释放kfifo的接口:

struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size, gfp_t gfp_mask, spinlock_t *lock);
struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock);

void kfifo_free(struct kfifo *fifo);

再一次强调,调用kfifo_init必须保证size是2的幂,而kfifo_alloc不必,它内部会把size向上圆到2的幂。kfifo_alloc和kfifo_free搭配使用,因为这两个函数会为fifo->buffer分配/释放内存空间。而kfifo_init只会接受一个已分配好空间的fifo->buffer,不能和kfifo->free搭配,用kfifo_init分配的kfifo只能用kfree释放。

循环缓冲区在驱动程序中使用较多,尤其是网络适配器。但这种免锁的方式在内核互斥中使用较少,取而代之的是另一种高级的互斥机制──RCU。

linux内核中的循环缓冲区相关推荐

  1. linux内核计算次方,linux内核中的循环缓冲去的设计与实现

    今天在看linux内核时候,发现内核中的循环缓冲区的设计与实现非常的完美.让后就想自己仿照着也写一个,用于加深理解. linux内核中对struct kfifo结构体的各种操作的源代码存放在: /ke ...

  2. Linux内核中无名管道pipe和有名管道fifo的分析

    1.管道(pipe) 管道是进程间通信的主要手段之一.一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端.管道是一种特殊的文件,它不属于某一种 ...

  3. Linux 内核中的数据结构:双链表,基数树,位图

    Linux 内核中的数据结构 rtoax 2021年3月 1. 双向链表 Linux 内核自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会从双向链表数据结构开始 ...

  4. 论文中文翻译——Double-Fetch情况如何演变为Double-Fetch漏洞:Linux内核中的双重获取研究

    本论文相关内容 论文下载地址--Web Of Science 论文中文翻译--How Double-Fetch Situations turn into Double-Fetch Vulnerabil ...

  5. TCP/IP协议栈在Linux内核中的运行时序分析

    本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看. 1 Linux概述 1.1 Linux操作系统架构简介 Linux操作系统总体上由Linu ...

  6. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  7. linux标准c和c编译器6,linux内核中GNU C和标准C的区别

    linux内核中GNU C和标准C的区别 今天看了一下午的linux内核编程方面的内容,发现linux 内核中GNU C与标准C有一些差别,特记录如下: linux 系统上可用的C编译器是GNU C编 ...

  8. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  9. 什么是Linux系统调用system call?(Linux内核中设置的一组用于实现各种系统功能的子程序)(区别于标准C库函数调用)核心态和用户态的概念、中断的概念、系统调用号、系统调用表

    文章目录 什么是系统调用? 为什么要用系统调用? 系统调用是怎么工作的? 如何使用系统调用? _syscall*()是什么? errno是什么? 调用性能问题 Linux系统调用列表 进程控制 文件系 ...

最新文章

  1. unzip 压缩包含路径,解压缩覆盖路径下的同名文件
  2. 用Jenkins自动化构建Android和iOS应用
  3. [智力考题]比尔盖茨只有3分的考题
  4. UGUI全面实践教程
  5. CTFshow 文件包含 web117
  6. 测试 MySQL 性能的几款工具
  7. IOS基础之绘图函数的使用
  8. 英伟达3060Ti安装GPU版本TensorFlow2.X
  9. VC++工程配置的大体流程 看图说话
  10. 使用 Maven Profile 和 Filtering 打各种环境的包(转)
  11. vax与vay的区别
  12. php 串口 主板,图解主板插槽:教你选对串口卡
  13. creo 6.0—02:单位的设置,默认绘图模板的绘制(重点)
  14. python3读取键盘输入_Python读取键盘输入
  15. HDFS HA机制 及 Secondary NameNode详解
  16. excel 分组求和
  17. python for循环与函数
  18. pymysql——excel数据导入mysql
  19. 全球变暖的影响与原因
  20. 统计java类含有多少个方法_35个Java代码优化的小技巧,你知道几个?

热门文章

  1. C++ class、struct区别
  2. (转)彻底学会使用epoll(一)——ET模式实现分析
  3. Visual C++中error spawning cl.exe解决办法
  4. asp.net日历控件My97DatePicker下载地址
  5. iOS运行时-使用Runtime向Category中添加属性以及运行时介绍
  6. java 线程之线程状态
  7. 面试官: 用css实现android系统的loading动画
  8. Flask 中内置的 Session
  9. vc/vs开发的应用程序添加dump崩溃日志转
  10. 如何给 mongodb 设置密码