读Linux内核(4.9.9)之环形缓冲区实现kfifo

kfifo实现了环形缓冲区(RingBuffer),提供了无锁单生产/单消费模式的共享队列;也就是如果只有一个写入者,一个读取者,是不需要锁的。 对于多个写入者,一个读取者,只需要对写入者上锁。 反之,如果有多个读取者,一个写入者,只需要对读取者上锁。在老的内核中直接定义了kfifo结构体,在新的内核中通过了一些稍显复杂的宏进行间接定义。我们先看下kfifo的原型__kfifo :

struct __kfifo {
	//data is added at offset (in % size)  --> 指向队列头
	unsigned int	in;

	//data is extracted from off. (out % size)  --> 指向队列尾
	unsigned int	out;

	//用于表示FIFO的总大小,也就是data的长度减1  --> 在初化时,将它向上扩展成2的幂
	unsigned int	mask;

	//the size of the allocated buffer --> 元素大小
	unsigned int	esize;
	
	//the buffer holding the data --> 存储数据的缓冲区
	void		*data;
}; 

接着看我们如何静态定义一个kfifo对象:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \
	union { \
		struct __kfifo	kfifo; \
		datatype	*type; \
		const datatype	*const_type; \
		char		(*rectype)[recsize]; \
		ptrtype		*ptr; \
		ptrtype const	*ptr_const; \
	}


#define __STRUCT_KFIFO(type, size, recsize, ptrtype) \
{ \
	__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
	type		buf[((size < 2) || (size & (size - 1))) ? -1 : size]; \
}

#define STRUCT_KFIFO(type, size) struct __STRUCT_KFIFO(type, size, 0, type)


/*****************************************************************************
 * 函 数 名  : DEFINE_KFIFO
 * 函数功能  : 静态声明一个kfifo对象
 * 输入参数  : fifo  要定义的kfifo的名字
               type  元素类型
               size  可容纳元素的个数,必须是2的幂
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年03月04日
 *   修改内容: 新生成函数
*****************************************************************************/	
#define DECLARE_KFIFO(fifo, type, size)	STRUCT_KFIFO(type, size) fifo


/*****************************************************************************
 * 函 数 名  : DEFINE_KFIFO
 * 函数功能  : 静态声明并初始化一个kfifo对象
 * 输入参数  : fifo  要定义的kfifo的名字
               type  元素类型
               size  可容纳元素的个数,必须是2的幂
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年03月04日
 *   修改内容: 新生成函数
*****************************************************************************/	
#define DEFINE_KFIFO(fifo, type, size) \
	DECLARE_KFIFO(fifo, type, size) = \
	(typeof(fifo)) { \
		{ \
			{ \
			.in	= 0, \
			.out	= 0, \
			.mask	= __is_kfifo_ptr(&(fifo)) ? \
				  0 : \
				  ARRAY_SIZE((fifo).buf) - 1, \
			.esize	= sizeof(*(fifo).buf), \
			.data	= __is_kfifo_ptr(&(fifo)) ? \
				NULL : \
				(fifo).buf, \
			} \
		} \
	}

看起来稍显复杂,单其实就是通过__STRUCT_KFIFO(type, size, recsize, ptrtype)定义了两部分一部分是环形缓冲区描述符——struct __kfifo结构对象,一部分是缓冲区本身——数组buf,然后初始化。宏展开之后是:

#define __STRUCT_KFIFO(type, size, recsize, ptrtype) \
	union { \
		struct __kfifo	kfifo; \
		datatype	*type; \
		const datatype	*const_type; \
		char		(*rectype)[recsize]; \
		ptrtype		*ptr; \
		ptrtype const	*ptr_const; \
	}
	type		buf[((size < 2) || (size & (size - 1))) ? -1 : size]; 

这里有一个联合体union ,除了描述符struct __kfifo kfifo,其他都是指针,这是为了方便获取元素类型(通过typeof);size & (size - 1)是为了确定缓冲区大小是2的幂,因为如果是2的幂,则相与结果为0。一般情况下我们不会静态声明,而是动态声明;我们看下如何动态声明:

/*****************************************************************************
 * 函 数 名  : __STRUCT_KFIFO_PTR
 * 函数功能  : kfifo结构体定义辅助宏
 * 输入参数  : type     元素类型
               recsize  ?
               ptrtype  指针类型
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年03月05日
 *   作    者:  
 *   修改内容: 新生成函数
*****************************************************************************/
#define __STRUCT_KFIFO_PTR(type, recsize, ptrtype) \
{ \
	__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
	type		buf[0]; \
}

//kfifo以unsigned char作为元素类型
//struct kfifo的定义,实际上它是由一个宏来定义的。
struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);

可以看到也借助到了__STRUCT_KFIFO_COMMON宏,但是元素类型直接被定义unsigned char,指针类型被指定为void,还看到缓冲区描述符中常见0长度数组buf(以便可以将方便缓冲区的分配和释放)。看下如何分配缓冲区实体:


static inline unsigned int __must_check
__kfifo_uint_must_check_helper(unsigned int val)
{
	return val;
}

static inline int __must_check
__kfifo_int_must_check_helper(int val)
{
	return val;
}

/*****************************************************************************
 * 函 数 名  : kfifo_alloc
 * 函数功能  : 动态分配一个环形缓冲区实体
 * 输入参数  : fifo      kfifo指针
               size      缓冲区容量,必须是2的幂
               gfp_mask  内存分配掩码
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年03月05日
 *   作    者:  
 *   修改内容: 新生成函数
*****************************************************************************/
#define kfifo_alloc(fifo, size, gfp_mask) \
__kfifo_int_must_check_helper( \
({ \
	typeof((fifo) + 1) __tmp = (fifo); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	__is_kfifo_ptr(__tmp) ? \
	__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \
	-EINVAL; \
}) \
)

1 __kfifo_int_must_check_helper是一个辅助函数, 主要是为了帮助警告用户忘记检查返回值 

2 fifo是一个指针,就是__STRUCT_KFIFO中定义的联合体指针

3 typeof((fifo) + 1)这里为什么要加1呢, 主要的好处是帮助确定传递的参数类型是否正确(确保是指针), 如果传递的是结构体会产生编译错误,如果传递的是数组名, 如 int fifo[4] ,typeof(fifo)的结果为 int [4] , 而typeof(fifo + 1)的结果为 int * 

4 实质借助的是__kfifo_alloc

我们看下__kfifo_alloc函数:

int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
		size_t esize, gfp_t gfp_mask)
{
	/*
	 * round down to the next power of 2, since our 'let the indices
	 * wrap' technique works only in this case.
	 */

	//将之向上扩展为2的幂
	size = roundup_pow_of_two(size);

	fifo->in = 0;
	fifo->out = 0;
	fifo->esize = esize;

	if (size < 2) {
		fifo->data = NULL;
		fifo->mask = 0;
		return -EINVAL;
	}

	fifo->data = kmalloc(size * esize, gfp_mask);

	if (!fifo->data) {
		fifo->mask = 0;
		return -ENOMEM;
	}
	fifo->mask = size - 1;

	return 0;
}

很简单,主要是分配一块缓冲区,然后将地址复制给struct __kfifo的data成员。还有一个要留意的是size被向上扩展为2的幂。这里要注意一点:

fifo->mask = size - 1;
描述符结构里的 mask并不是缓冲区的大小,而是缓冲区大小减一,也就是指示缓冲区满了,其实是有一个数据空间的。

 再看下释放缓冲区:

/*****************************************************************************
 * 函 数 名  : kfifo_free
 * 函数功能  : 释放缓冲区内存
 * 输入参数  : fifo  kfifo指针
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年03月05日
 *   作    者:  
 *   修改内容: 新生成函数
*****************************************************************************/
#define kfifo_free(fifo) \
({ \
	typeof((fifo) + 1) __tmp = (fifo); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	if (__is_kfifo_ptr(__tmp)) \
		__kfifo_free(__kfifo); \
})
void __kfifo_free(struct __kfifo *fifo)
{
	kfree(fifo->data);
	fifo->in = 0;
	fifo->out = 0;
	fifo->esize = 0;
	fifo->data = NULL;
	fifo->mask = 0;
}

可以看到并没有释放缓冲区描述符,而只是释放了缓冲区。


下面我们看下是怎么添加数据到缓冲区的:

/*****************************************************************************
 * 函 数 名  : kfifo_put
 * 函数功能  : 添加数据到环形缓冲区
 * 输入参数  : fifo  环形缓冲区描述符指针
               val   待添加的数据
 * 输出参数  : 无
 * 返 回 值  : 
 * 调用关系  : 
 * 记    录
 * 1.日    期: 2018年05月06日
 *   作    者:  
 *   修改内容: 新生成函数
*****************************************************************************/
#define	kfifo_put(fifo, val) \
({ \
	typeof((fifo) + 1) __tmp = (fifo); \
	typeof(*__tmp->const_type) __val = (val); \
	unsigned int __ret; \
	size_t __recsize = sizeof(*__tmp->rectype); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	if (__recsize) \
		__ret = __kfifo_in_r(__kfifo, &__val, sizeof(__val), \
			__recsize); \
	else { \
		__ret = !kfifo_is_full(__tmp); \
		if (__ret) { \
			(__is_kfifo_ptr(__tmp) ? \
			((typeof(__tmp->type))__kfifo->data) : \
			(__tmp->buf) \
			)[__kfifo->in & __tmp->kfifo.mask] = \
				*(typeof(__tmp->type))&__val; \
			smp_wmb(); \
			__kfifo->in++; \
		} \
	} \
	__ret; \

__recsize不是很懂我们不看,我们直接看else部分。

首选判断缓冲区是否满了,方法就是插入的数据大小减去取出的数据大小是否大于缓冲区大小:

/**
 * kfifo_len - returns the number of used elements in the fifo
 * @fifo: address of the fifo to be used
 */
#define kfifo_len(fifo) \
({ \
	typeof((fifo) + 1) __tmpl = (fifo); \
	__tmpl->kfifo.in - __tmpl->kfifo.out; \
})

/**
 * kfifo_is_full - returns true if the fifo is full
 * @fifo: address of the fifo to be used
 */
#define	kfifo_is_full(fifo) \
({ \
	typeof((fifo) + 1) __tmpq = (fifo); \
	kfifo_len(__tmpq) > __tmpq->kfifo.mask; \
})


接着会判断是动态分配的,还是静态分配的;区别是动态分配的,描述符大小不包含缓冲区实体大小,静态分配的包含缓冲区实体大小(静态分配的描述符结构和数组作为一个整体结构体声明),因为动态分配的会将缓冲区实体地址赋值给描述符的data成员:

/*
 * helper macro to distinguish between real in place fifo where the fifo
 * array is a part of the structure and the fifo type where the array is
 * outside of the fifo structure.
 */
#define	__is_kfifo_ptr(fifo)	(sizeof(*fifo) == sizeof(struct __kfifo))

接着就是将数据插入缓冲区,指示插入数据大小的成员自加

__kfifo->in++; 


你可能感兴趣的:(Linux内核阅读)