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