linux内核无锁缓冲队列kfifo原理
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(输入输出的指针),并且在放入取出数据的时候不做模运算,让其自然溢出。
优点:
- 实现单消费者和单生产者的无锁并发访问。多消费者和多生产者的时候还是需要加锁的。
- 使用与运算
in & (size-1)代替模运算 在更新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原理的更多相关文章
- Linux 内核:匠心独运之无锁环形队列kfifo
Linux 内核:匠心独运之无锁环形队列 Kernel version Linux 2.6.12 Author Toney Email vip_13031075266@163.com Da ...
- 一个无锁消息队列引发的血案(六)——RingQueue(中) 休眠的艺术 [续]
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- 一个无锁消息队列引发的血案(五)——RingQueue(中) 休眠的艺术
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- 一个无锁消息队列引发的血案(四)——月:RingQueue(上) 自旋锁
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- 一个无锁消息队列引发的血案(三)——地:q3.h 与 RingBuffer
目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...
- Linux内核中锁机制之完成量、互斥量
在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...
- Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁
在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔 ...
- Linux内核中锁机制之信号量、读写信号量
在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程 ...
- 大话Linux内核中锁机制之完成量、互斥量
大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内 ...
随机推荐
- Linux中移动,复制,删除,打包排除某个目录或文件
移动,复制,删除排除某个文件或目录 cp !(file1|dir2) /data/ 复制文件到/data/,排除file1和dir2 mv !(file1|dir2) /data/ 移动文件到/dat ...
- 阿里云Centos 7.6安装Redis以及开启远程连接
下载地址:http://redis.io/download,下载最新稳定版本. 本教程使用的最新文档版本为 5.0.5,下载并安装: $ wget http://download.redis.io/r ...
- 关于使用Arduino做开发的理解
转载自arduino中文社区 https://www.arduino.cn/thread-5414-1-1.html 见到很多人对Arduino的开发方法 .应用场景有误解,特别开个帖子说明下. 误解 ...
- OpenCV.CV_FOURCC
1.c++ - how to use CV_CAP_PROP_FOURCC_ - Stack Overflow.html(https://stackoverflow.com/questions/223 ...
- [转帖]Kubernetes中安装Helm及使用
Kubernetes中安装Helm及使用 2018年07月02日 17:41:09 灬勿忘丶心安 阅读数 3699更多 分类专栏: K8S 版权声明:本文为博主原创文章,遵循CC 4.0 BY-S ...
- PowerShell使用Clear-Content命令删除、清空文件内容的例子
本文介绍PowerShell中如何将文件的内容进行删除或清空,使用VBScript要先创建FSO,然后再打开文件,再修改文件:PowerShell清空文件只要一句Clear-Content. 所谓清空 ...
- PostgreSQL练习
学生表 Studentcreate table Student(Sid varchar(6), Sname varchar(10), Sage datetime, Ssex varchar(10)); ...
- 关于STM32的IAP与APP互相跳转
关于STM32的IAP与APP互相跳转 之前做了一个不带系统的IAP与APP互相跳转,在网上找了资料后,很顺畅就完成了,后来在IAR集成开发环境下,IAP无系统,APP用UCOS系统做互相跳转出现了很 ...
- Oracle笔记2
24.复杂查询的三道题 # 案例一:列出与SCOTT从事相同工作的所有员工及部门名称.人数.平均工资 GROUP BY使用限制: 查询语句中没有GROUP BY,则SELECT子句中只能出现统计函数; ...
- 查找最大和次大元素(JAVA版)(分治法)
问题描述:对于给定的含有n个元素的无序序列,求这个序列中最大和次大的两个不同元素. 问题求解分析(分治法):先给出无序序列数组a[low...high].第一种情况为当数组中只有一个元素时,此时只存在 ...