linux内核中的循环缓冲区

      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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/253740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js的规范写法ES5(自己以后按照这样写)

1、引号的使用&#xff0c;单引号 优先&#xff08;如果不是引号嵌套&#xff0c;不要使用双引号&#xff09; 正常情况&#xff1a;console.log(hello there) 双引号转码&#xff1a; $("<div classbox>") 2、空格的使用问题&#xff1a;&#xff08…

删除本地git的远程分支和远程删除git服务器的分支

在项目中使用git管理代码后&#xff0c;有些时候会创建很多不同名称的分支&#xff0c;以此区分各个分支代码功能。 而随着代码的合并&#xff0c;以前的分支就可能不再需要保存了&#xff0c;所以就要对没有用的分支进行删除&#xff0c;包括紧急回滚时从中抽取某一个版本记录…

数字图像处理——引导滤波

一、概述 引导滤波是由何恺明等人于2010年发表在ECCV的文章《Guided Image Filtering》中提出的&#xff0c;后续于2013年发表。引导过滤器根据局部线性模型原理&#xff0c;通过考虑引导图像的内容来计算过滤输出&#xff0c;引导图像可以是输入图像本身或另一个不同的图像。具…

Ubuntu 18.04换国内源

2019独角兽企业重金招聘Python工程师标准>>> 参考文档&#xff1a; https://blog.csdn.net/zhangjiahao14/article/details/80554616 https://blog.csdn.net/xiangxianghehe/article/details/80112149 1.复制源文件备份&#xff0c;以防万一 我们要修改的文件是sour…

video4linux简介

Video4linux&#xff08;简称V4L),是linux中关于视频设备的内核驱动,现在已有Video4linux2&#xff0c;还未加入linux内核&#xff0c;使用需自己下载补丁。在Linux中&#xff0c;视频设备是设备文件&#xff0c;可以像访问普通文件一样对其进行读写&#xff0c;摄像头在/dev/v…

动态DPC算法学习

造成坏点的原因 感光元件芯片自身工艺技术瑕疵造成;光线采集存在缺陷;制造商产品差异;坏点分类 hot pixel: 固定保持较高的像素值,一般呈现为画面高亮的点;dead pixel: 固定保持较低的像素值,一般在画面中呈现为暗点;noise pixel:信号强度随光照呈现的变化规律不符合正…

windows 邮槽mailslot 在服务程序内建立后客户端无权限访问(GetLastError() == 5)的问题...

邮槽创建在服务程序内&#xff0c;可以创建成功&#xff0c; 但外部客户端连接时 m_hMailslot CreateFile("\\\\.\\mailslot\\zdpMailslot",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);GetLastError返回错误 5 &#xff0c;无权…

递归下降分析

对于给定的文法G[E] : E→ET|E-T|TT→T*F| T/F|FF→(E)|i 消除左递归后的文法是&#xff1a;E→TE E→TE|-TE|∑ T→FT T→*FT|/FT|∑ F→(E)|i 是否是LL(1)文法&#xff1f; select(E→TE)first(TE){(,i}select(E→TE)first(TE){}select(E→-TE)first(-TE){-}select(E→∑)fol…

SYS简介

"sysfs is a ram-based filesystem initially based on ramfs. It provides a means to export kernel data structures, their attributes, and the linkages between them to userspace.” --- documentation/filesystems/sysfs.txt 可以先把documentation/filesystems/…

数字后端——布图规划

布图规划&#xff08;floorplan&#xff09;与布局&#xff08;place&#xff09;在芯片设计中占据着重要的地位&#xff0c;它的合理与否直接关系到芯片的时序收敛、布线通畅、电源稳定以及良品率。所以在整个芯片设计中&#xff0c;从布图规划到完成布局一般需要占据整个物理…

利用SSH传输文件

在linux下一般用scp这个命令来通过ssh传输文件。 1、从服务器上下载文件scp usernameservername:/path/filename /var/www/local_dir&#xff08;本地目录&#xff09; 2、上传本地文件到服务器scp /path/filename usernameservername:/path 例如scp /var/www/test.php root19…

App WebView实例化

a&#xff0c;高级设置里的环境变量 jdk的配置 b&#xff0c;下载Google的sdk&#xff0c;里面直接包含eclipse 1&#xff0c;新建一个项目 2&#xff0c;起个名字 3&#xff0c;设么走不做&#xff0c;next 4&#xff0c;只操作选择显示的三种方式 5&#xff0c;next什么都不做…

[动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现

前言 关于动态代理的系列文章&#xff0c;到此便进入了最后的“一出好戏”。前俩篇内容分别展开了&#xff1a;从源码上&#xff0c;了解JDK实现动态代理的原理&#xff1b;以及从动态代理切入&#xff0c;学会看class文件结构的含义。 如果还没有看过这俩篇文章的小伙伴&#…

Ti的DM368系列芯片的所有PDF资料汇总

http://www.ti.com/sc/docs/psheets/man_dsp.htm

刘浩(专业打劫三十年)20155307的预备作业02:

我的技能&#xff1f;比大多数人好&#xff1f;经验是什么&#xff1f;与老师的经验的共同之处&#xff1f; 我的技能之一就是单词翻译王——其实看了娄老师的学习经验之后便有些自惭形秽了&#xff0c;我目前的单词量是7300,扇贝上测的&#xff0c;而且测试时是严格的“不会就…

数字后端——电源规划

电源规划是给整个芯片的供电设计出一个均勻的网络&#xff0c;它是芯片物理设计中非常关键的一部分。电源规划在芯片布图规划后或在布图规划过程中交叉完成,它贯穿于整个设计中&#xff0c;需要在芯片设计的不同阶段对电源的供电网络进行分析并根据要求进行修改。&#xff0c;主…

逆向project实战--Acid burn

0x00 序言 这是第二次破解 crackme 小程序&#xff0c;感觉明显比第一次熟练。破解过程非常顺利&#xff0c;差点儿是分分钟就能够找到正确的 serial&#xff0c;可是我们的目标是破解计算过程。以下将具体介绍。 0x01 初次执行 刚開始拿到 crackme 先执行程序。看看有哪些明显…

PyCharm使用技巧(六):Regullar Expressions的使用

2019独角兽企业重金招聘Python工程师标准>>> PyCharm v2018.2最新版本下载 使用正则表达式查找和替换文件中的文本 示例代码 使用正则表达式查找和替换字符串 假设您想用扩展标记<title> </title>替换元素&#xff08;title&#xff09;中的属性&#x…

内核中_init,_exit中的作用

__init&#xff0c; __initdata等属性标志&#xff0c;是要把这种属性的代码放入目标文件的.init.text节&#xff0c;数据放入.init.data节──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本来指导ld完成的。 对编译成module的代码和数据来说&#xff0c;当模…

jQuery笔记总结

来源于&#xff1a;http://blog.poetries.top/2016/10/20/review-jQuery/ http://www.jianshu.com/p/f8e3936b34c9 首先&#xff0c;来了解一下jQuery学习的整体思路 第一节 jQuery初步认知 jQuery概述 JQuery概念 javascript概念 基于Js语言的API和语法组织逻辑&#xff0c;通…