Linux內核數據結構隊列-kfifo

前言

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章