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就不需要单独对溢出做操作了。

 

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