Linux kernel里面从来就不缺少简洁,优雅和高效的代码

比如,通过限定写入的数据不能溢出和内存屏障实现在单线程写单线程读的情况下不使用锁。因为锁是使用在共享资源可能存在冲突的情况下。还用设置buffer缓冲区的大小为2的幂次方,以简化求模运算,这样求模运算就演变为 (fifo->in & (fifo->size - 1))。通过使用unsigned int为kfifo的下标,可以不用考虑每次下标超过size时对下表进行取模运算赋值,这里使用到了无符号整数的溢出回零的特性。由于指示读写指针的下标一直在增加,没有进行取模运算,知道其溢出,在这种情况下写满和读完就是不一样的标志,写满是两者指针之差为fifo->size,读完的标志是两者指针相等。后面有一篇博客还介绍了VxWorks下的环形缓冲区的实现机制点击打开链接,从而可以看出linux下的fifo的灵巧性和高效性。

kfifo主要有以下特点:

  • 保证缓冲空间的大小为2的次幂,不是的向上取整为2的次幂。
  • 使用无符号整数保存输入(in)和输出(out)的位置,在输入输出时不对in和out的值进行模运算,而让其自然溢出,并能够保证in-out的结果为缓冲区中已存放的数据长度,这也是最能体现kfifo实现技巧的地方;
  • 使用内存屏障(Memory Barrier)技术,实现单消费者和单生产者对kfifo的无锁并发访问,多个消费者、生产者的并发访问还是需要加锁的。

本文主要以下三个部分:

  • 关于2的次幂问题,判断是不是2的次幂以及向上取整为2的次幂
  • Linux内核中kfifo的实现及简要分析
  • 根据kfifo实现的循环缓冲区,并进行一些测试

关于内存屏障的本文不作过多分析,可以参考WikiMemory Barrier。另外,本文所涉及的整数都默认为无符号整数,不再做一一说明。

1. 2的次幂

  • 判断一个数是不是2的次幂
    kfifo要保证其缓存空间的大小为2的次幂,如果不是则向上取整为2的次幂。其对于2的次幂的判断方式也是很巧妙的。如果一个整数n是2的次幂,则二进制模式必然是1000...,而n-1的二进制模式则是0111...,也就是说n和n-1的每个二进制位都不相同,例如:8(1000)和7(0111);n不是2的次幂,则n和n-1的二进制必然有相同的位都为1的情况,例如:7(0111)和6(0110)。这样就可以根据 n & (n-1)的结果来判断整数n是不是2的次幂,实现如下:
/*
判断n是否是2的幂
若n为2的次幂, 则 n & (n-1) == 0,也就是n和n-1的各个位都不相同。例如 8(1000)和7(0111)
若n不是2的次幂, 则 n & (n-1) != 0,也就是n和n-1的各个位肯定有相同的,例如7(0111)和6(0110)
*/
static inline bool is_power_of_2(uint32_t n)
{
return (n != 0 && ((n & (n - 1)) == 0));
}
  • 将数字向上取整为2的次幂
    如果设定的缓冲区大小不是2的次幂,则向上取整为2的次幂,例如:设定为5,则向上取为8。上面提到整数n是2的次幂,则其二进制模式为100...,故如果正数k不是n的次幂,只需找到其最高的有效位1所在的位置(从1开始计数)pos,然后1 << pos即可将k向上取整为2的次幂。实现如下:
static inline uint32_t roundup_power_of_2(uint32_t a)
{
if (a == 0)
return 0; uint32_t position = 0;
for (int i = a; i != 0; i >>= 1)
position++; return static_cast<uint32_t>(1 << position);
} fifo->in & (fifo->size - 1) 再比如这种写法取模,获取已用的大小。这样用逻辑与的方式相较于加减法更有效率
二:
Linux内核中kfifo实现技巧,主要集中在放入数据的put方法和取数据的get方法。代码如下:
 unsigned int __kfifo_put(struct kfifo *fifo, unsigned char *buffer, unsigned int len)
{
unsigned int l; len = min(len, fifo->size - fifo->in + fifo->out); /*
* Ensure that we sample the fifo->out index -before- we
* start putting bytes into the kfifo.
*/ smp_mb(); /* first put the data starting from fifo->in to buffer end */
l = min(len, fifo->size - (fifo->in & (fifo->size - )));
memcpy(fifo->buffer + (fifo->in & (fifo->size - )), buffer, l); /* then put the rest (if any) at the beginning of the buffer */
memcpy(fifo->buffer, buffer + l, len - l); /*
* Ensure that we add the bytes to the kfifo -before-
* we update the fifo->in index.
*/ smp_wmb(); fifo->in += len; return len;
} unsigned int __kfifo_get(struct kfifo *fifo,unsigned char *buffer, unsigned int len)
{
unsigned int l; len = min(len, fifo->in - fifo->out); /*
* Ensure that we sample the fifo->in index -before- we
* start removing bytes from the kfifo.
*/ smp_rmb(); /* first get the data from fifo->out until the end of the buffer */
l = min(len, fifo->size - (fifo->out & (fifo->size - )));
memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - )), l); /* then get the rest (if any) from the beginning of the buffer */
memcpy(buffer + l, fifo->buffer, len - l); /*
* Ensure that we remove the bytes from the kfifo -before-
* we update the fifo->out index.
*/ smp_mb(); fifo->out += len; return len;
}

put返回实际保存到缓冲区中的数据长度,get返回的是实际取到的数据长度。在上面代码中,需要注意到在写入、取出时候的两次min运算。关于kfifo的分析,已有很多资料了,也可参考眉目传情之匠心独运的kfifo 。

Linux内核实现的kfifo的有以下特点:

  • 使用内存屏障 Memory Barrier
  • 初始化缓冲区空间时要保证缓冲区的大小为2的次幂
  • 使用无符号整数保存in和out(输入输出的指针),并且在放入取出数据的时候不做模运算,让其自然溢出。

优点:

  1. 实现单消费者和单生产者的无锁并发访问。多消费者和多生产者的时候还是需要加锁的。
  2. 使用与运算in & (size-1)代替模运算
  3. 在更新in或者out的值时不做模运算,而是让其自动溢出。这应该是kfifo实现最牛叉的地方了,利用溢出后的值参与运算,并且能够保证结果的正确。溢出运算保证了以下几点:

    • in - out为缓冲区中的数据长度
    • size - in + out 为缓冲区中空闲空间
    • in == out时缓冲区为空
    • size == (in - out)时缓冲区满了

讲解最详细的一篇https://blog.csdn.net/linyt/article/details/53355355
另外一个https://www.cnblogs.com/wangguchangqing/p/6070286.html

linux内核无锁缓冲队列kfifo原理的更多相关文章

  1. Linux 内核:匠心独运之无锁环形队列kfifo

    Linux 内核:匠心独运之无锁环形队列 Kernel version Linux 2.6.12   Author Toney   Email vip_13031075266@163.com   Da ...

  2. 一个无锁消息队列引发的血案(六)——RingQueue(中) 休眠的艺术 [续]

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

  3. 一个无锁消息队列引发的血案(五)——RingQueue(中) 休眠的艺术

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

  4. 一个无锁消息队列引发的血案(四)——月:RingQueue(上) 自旋锁

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

  5. 一个无锁消息队列引发的血案(三)——地:q3.h 与 RingBuffer

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

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

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

  7. Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

    在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔 ...

  8. Linux内核中锁机制之信号量、读写信号量

    在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程 ...

  9. 大话Linux内核中锁机制之完成量、互斥量

    大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内 ...

随机推荐

  1. STM32 M0之SPI

    从M3到M0,可能SPI的接口函数大致类似,但是细节略有不同 仔细观察寄存器描述,虽然个别存在差异,但是真心不知道竟然有太多的“玄机” 这次的问题主要出在了数据宽度上: 1. M3/M4的数据宽度支持 ...

  2. Reactor系列(十)collectMap集合

    #java#reactor#collect#hashMap# 转换成Map 视频讲解: https://www.bilibili.com/video/av80048104/ FluxMonoTestC ...

  3. SQL SERVER MONTH函数

    定义: MONTH函数返回指定日期的月的部分 语法: MONTH(date) 参数: ①date参数是合法的日期表达式. 返回值: int型数据 例:  声明:本文是本人查阅网上及书籍等各种资料,再加 ...

  4. CodeBlocks 配置

    CodeBlocks 配置 Code::Blocks 17.12 时间:2019.6 下载网址 http://www.codeblocks.org/downloads/26 ,这里选择的是 mingw ...

  5. DP_Wooden Sticks

    There is a pile of n wooden sticks. The length and weight of each stick are known in advance. The st ...

  6. 数据分析—win7+ipython+notebook安装

    先安装python 3.x 然后 cmd 执行 pip3 ipython 然后 cmd 执行 pip3 install jupyter notebook 然后 cmd 执行 jupyter noteb ...

  7. 适合新手的160个creakme(二)

    先跑一下,然后找出关键字符串 关键字符串是You Get Wrong和Try Again,不过IDA好像识别不出来这个字符串,在Ollydbg中右键Search For,寻找所有字符串,可以找到这些字 ...

  8. AlertManager 部署及使用

    熟悉了 Grafana 的报警功能,但是 Grafana 的报警功能目前还比较弱,只支持 Graph 的图表的报警.今天来给大家介绍一个功能更加强大的报警工具:AlertManager. 简介 之前我 ...

  9. AOP与IOC区别

    Spring核心知识 Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Devel ...

  10. iot平台异构对接文档

    iot平台异构对接文档 准备工作 平台提供的XAgent开发指南.pdf demo程序xagent-ptp-demo 平台上添加产品得到产品id和key 部署时需要插件的基础程序<xlink-x ...