前言
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就不需要單獨對溢出做操作了。