linux内核kfifo

linux内核数据结构之kfifo

kfifo是一种"First In First Out “数据结构,它采用了前面提到的环形缓冲区来实现,提供一个无边界的字节流服务。采用环形缓冲区的好处为,当一个数据元素被用掉后,其余数据元素不需要移动其存储位置,从而减少拷贝提高效率。更重要的是,kfifo采用了并行无锁技术,kfifo实现的单生产/单消费模式的共享队列是不需要加锁同步的

kfifo的实现中使用如下几个小技巧:

保证缓冲区大小为2的次幂,不是的向上取整为2的次幂(取模运算转化成&运算)。

使用无符号整数保存输入(in)和输出(out)的位置,在输入输出时不对in和out的值进行模运算,而让其自然溢出,并能够保证in-out的结果为缓冲区中已存放的数据长度。

将需要取模的运算用 & 操作代替( a % size = (a & (size − 1)) ), 这需要size保证为2的次幂。

使用内存屏障(Memory Barrier)技术,实现单消费者和单生产者对kfifo的无锁并发访问,多个消费者、生产者的并发访问还是需要加锁的

 

linux内核的kfifo可以作为一个缓冲区。缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度。例如一个进程A产生数据发给另外一个进程B,进程B需要对进程A传的数据进行处理并写入文件,如果B没有处理完,则A要延迟发送。为了保证进程A减少等待时间,可以在A和B之间采用一个缓冲区,A每次将数据存放在缓冲区中,B每次冲缓冲区中取。这是典型的生产者和消费者模型,缓冲区中数据满足FIFO特性,因此可以采用队列进行实现。Linux内核的kfifo正好是一个环形队列,可以用来当作环形缓冲区

 

2、linux 内核kfifo

kfifo设计的非常巧妙,代码很精简,对于入队和出对处理的出人意料。首先看一下kfifo的数据结构:

struct __kfifo {

    unsigned int    in;

    unsigned int    out;

    unsigned int    mask;

    unsigned int    esize;

void        *data;   };

 

data:用于存放数据的缓存区

esize:缓存区每个元素的size(element size)

mask:缓冲区元素个数(size) - 1;使用&mask,替换%size,提升效率

in:队尾下标,入队列的offset为(in % size 或者 in & mask) 取模运算转化成&运算

out:队首下标,出队列的offset为(out % size 或者 out & mask) 取模运算转化成&运算

队尾下标in在有数据入队的时候,一直自增;队首下标out在有数据出队的时候,一直自增。内核使用了unsigned int溢出的特性,来实现循环队列,即in - out不管任何情况都为队列的长度,即使in < out

 

static inline unsigned int kfifo_unused(struct __kfifo *fifo)

{

return (fifo->mask + 1) - (fifo->in - fifo->out);

}

 

此处fifo->mask + 1 即为缓冲区长度,这个好理解。

fifo->in - fifo->out 为已经写入长度

 

 

 

 

 

 

当in > out 时很好理解。
 

还有一种情况,就是in写满缓冲区尾部,折回到开头并且unsigned int 溢出,out不溢出。此时 in < out , in - out 还为已经写入长度?无符号整形数据的溢出

实现环形缓冲队列的细节

 


 

 

 

判断一个数是否为2的次幂,按照一般的思路,求一个数n是否为2的次幂的方法为看 n % 2 是否等于0, 我们知道“取模运算”的效率并没有 “位运算” 的效率高,下面再验证一下这样取2的模的正确性,若n为2的次幂,则n和n-1的二进制各个位肯定不同 (如8(1000)和7(0111)),&出来的结果肯定是0;如果n不为2的次幂,则各个位肯定有相同的 (如7(0111) 和6(0110)),&出来结果肯定为0。是不是很巧妙?

   1: bool is_power_of_2(unsigned long n)

   2: {

   3:     return (n != 0 && ((n & (n - 1)) == 0));

   4: }

再看下kfifo内存分配和初始化的代码,前面提到kfifo总是对size进行2次幂的圆整,这样的好处不言而喻,可以将kfifo->size取模运算可以转化为与运算,如下:
          kfifo->in % kfifo->size 可以转化为 kfifo->in & (kfifo->size – 1)

 

 

数据溢出情况的赏析:

这里图解一下 in 先溢出的情况,size = 64, 写入前 in = 4294967291, out = 4294967279 ,数据 in – out = 12;

 

    写入 数据16个字节,则 in + 16 = 4294967307,溢出为 11,此时 in – out = –4294967268,溢出为28,数据长度仍然正确,由此可见,在这种特殊情况下,这种计算仍然正确,是不是让人叹为观止,妙不可言?

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值