kfifo是linux内核中的环形缓冲区,实现了先进先出的队列数据结构。以下为kfifo的数据结构定义(内核版本2.6.33.20):
struct kfifo {
unsigned char *buffer; // 环形缓冲区的大小
unsigned int size; // 环形缓冲区的大小,必须是2的冥
unsigned int in; /* data is added at offset (in % size) */
unsigned int out; /* data is extracted from off. (out % size) */
};
这个结构有以下特性:
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的这一点真是精妙,要是我来搞,肯定需要一大段的分支判断才能保证程序的正确性。