前言
kfifo巧妙的运用了无符号变量和位运算的特点。使得代码巧妙。
数据结构
struct kfifo {
unsigned char *buffer; /* 存放数据的buff */
unsigned int size; /* 分配到的buff的大小 */
unsigned int in; /* data写入时的偏移量 (in % size) */
unsigned int out; /* data is extracted from off. (out % size) */
};
初始化
对于kfifo的初始化分为两种,静态初始化和动态初始化。
在初始化中,我们会发现,它分配的buffer大小一定是2的幂次数,如果参数不是2的幂次数,会进行向上取舍。为什么这么设计呢?先放个悬念。其中_kfifo_init(fifo, buffer, size)是分配内存和设置参数的函数调用。
/** 静态初始化:使用一个预分配好的buffer进行初始化一个kfifo */
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)
{
/* size must be a power of 2 */
BUG_ON(!is_power_of_2(size));
_kfifo_init(fifo, buffer, size);
}
/* 分配一个size大小的fifo **/
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
{
unsigned char *buffer;
/*
* round up to the next power of 2, since our 'let the indices
* wrap' technique works only in this case.
*/
if (!is_power_of_2(size)) { // 必须是2的幂次,向上舍入
BUG_ON(size > 0x80000000);
size = roundup_pow_of_two(size);
}
buffer = kmalloc(size, gfp_mask);
if (!buffer) {
_kfifo_init(fifo, NULL, 0);
return -ENOMEM;
}
_kfifo_init(fifo, buffer, size);
return 0;
}
读写操作,以写为例
对于一个队列来说,最重要的是读取和写入数据。我们知道队列是按照先进先出来读写数据的。
另外,buffer是一个循环索引的内存。所以会出现写入的时候,写到了buffer物理内存的结尾数据还没有全部写完。这时候我们可以从起始位进行写入。
/* 写数据入队列 **/
unsigned int kfifo_in(struct kfifo *fifo, const void *from,
unsigned int len)
{
len = min(kfifo_avail(fifo), len); // 判断队列是否能容纳下。
__kfifo_in_data(fifo, from, len, 0); // 写数据
__kfifo_add_in(fifo, len); // 修改in的off
return len;
}
static inline void __kfifo_in_data(struct kfifo *fifo,
const void *from, unsigned int len, unsigned int off)
{
unsigned int l;
/*
* Ensure that we sample the fifo->out index -before- we
* start putting bytes into the kfifo.
*/
smp_mb();
off = __kfifo_off(fifo, fifo->in + off);
/* first put the data starting from fifo->in to buffer end */
l = min(len, fifo->size - off);
memcpy(fifo->buffer + off, from, l);
/* 如果写入的数据不够,从起始位继续读取,buffer是循环的 */
memcpy(fifo->buffer, from + l, len - l);
}
必须分配2的幂次的原因
这里有一个kfifo的一个精妙之处。就是off = __kfifo_off(fifo, fifo->in + off);
该函数的语句只有一句,但是作用很重要,是通过偏移值off得到索引位置。
使用位运算&,得到off % size相同的结果。
位运算更加高效。这也是为什么分配时必须是2的幂次方的原因。
static inline unsigned int __kfifo_off(struct kfifo *fifo, unsigned int off)
{
return off & (fifo->size - 1); /* &并操作。只保留size-1以下的数字。等同于off % size **/
}
至于读取操作和写入操作一样。就不多重复。
unsigned在循环索引中的妙用
至此,还有一个问题没有解决。那就是kfifo的状态问题,也就是:
buffer中数据的长度,以及kfifo为空还是为满。
我们都知道,在循环队列中,buffer是空和满的情况下,in和out的索引值都是相等的。那么kfifo是如何解决的呢?!
它巧妙的使用了无符号数的特点。
我们先看,如何计算kfifo中数据的长度len。可以看出是使用in - out。为什么这么做,我们看完判断为空为满之后一起解释。
static inline unsigned int kfifo_len(struct kfifo *fifo)
{
register unsigned int out;
out = fifo->out;
smp_rmb();
return fifo->in - out;
}
我们看一下in==out时候为空。len==size时为满。
static inline __must_check int kfifo_is_empty(struct kfifo *fifo)
{
return fifo->in == fifo->out;
}
static inline __must_check int kfifo_is_full(struct kfifo *fifo)
{
return kfifo_len(fifo) == kfifo_size(fifo);
}
这时候,大神们是不会有什么疑惑的,他们一眼就能看出其中的精妙。
像我们一样的小白,就会有一个疑惑,不是循环空间吗??in + size == out 的情况咋办?
代码肯定没问题咯,这可是linux内核代码。
仔细看看,发现,使用的unsigned int。无符号数自己溢出会自动归零!!!!
较小的数a-较大的数b,也不会出现负数,而是等于a-b+max!!!
所以使用unsigned int就不需要单独对溢出做操作了。