天天看點

讀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++;