Linux中kfifo数据结构的精妙之处:即使溢出仍然正确

kfifo是linux内核中的环形缓冲区,实现了先进先出的队列数据结构。以下为kfifo的数据结构定义(内核版本2.6.33.20):

  1. struct kfifo {  
  2.         unsigned char *buffer;  // 环形缓冲区的大小  
  3.         unsigned int size;      // 环形缓冲区的大小,必须是2的冥  
  4.         unsigned int in;        /* data is added at offset (in % size) */  
  5.         unsigned int out;       /* data is extracted from off. (out % size) */  
  6. };  
这个结构有以下特性:

1. 初始状态下,in == out == 0。

2. 入队导致in递增,出队导致out递增。注意in和out总是递增的,从来不会减少。

3. 计算in对应的缓冲区索引的公式是fifo.in& (fifo.size - 1),见__kfifo_off函数

4. 计算缓冲区中元素数量的公式是fifo.in - fifo.out,见kfifo_size函数。

根据以上特性,在运行的时候in和out迟早会溢出。然而即使溢出,kfifo_size函数仍然是对的,我们来分析一下。

预备知识:在32位机器上,假设N为正整数,static_cast<unsigned int>(-N) == 2**32 – N。也就是说,-N的补码的比特位,如果强制解释成正整数,其数值为2**32-N。

分情况证明:

1.      如果in和out都没有溢出,或者in和out都溢出了,kfifo_size是对的。

2.      如果in溢出了,out没有溢出。记in在溢出之前的数值为in_nofl,则in_nofl == 2**32+in。由于fifo.in < fifo.out,fifo.in – fifo.out为负数,因此static_cast<unsigned int>(fifo.in-fifo.out) == 2**32 -(fifo.out-fifo.in) == (2**32+fifo.in)-fifo.out == in_nofl – fifo.out。因此kfifo_size是正确的。

3.      不可能in没有溢出同时out溢出。

 

kfifo的这一点真是精妙,要是我来搞,肯定需要一大段的分支判断才能保证程序的正确性。





kfifo,在Linux内核文件kfifo.h和kfifo.c中,定义了一个先进先出圆形缓冲区实现。如果只有一个读线程、一个写线程,二者没有共享的被修改的控制变量,那么可以证明这种情况下不需要并发控制。kfifo就满足上述条件。kfifo要求缓冲区长度必须为2的幂。读、写指针分别是无符号整型变量。把读写指针变换为缓冲区内的索引值,仅需要“按位与”操作:(指针值 按位与 (缓冲区长度-1))。这避免了计算代价高昂的“求余”操作。且下述关系总是成立:
读指针 + 缓冲区存储的数据长度 == 写指针
即使在读指针达到了无符号整型的上界,上溢出后读指针的值小于写指针的值,上述关系仍然保持成立(这是因为无符号整型加法的性质)。 kfifo的写操作,首先计算缓冲区中当前可写入存储空间的数据长度:
len = min{待写入数据长度, 缓冲区长度 - (写指针 - 读指针)}
然后,分两段写入数据。第一段是从写指针开始向缓冲区末尾方向;第二段是从缓冲区起始处写入余下的可写入数据,这部分可能数据长度为0即并无实际数据写入。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值