kfifo概述
据wiki介绍,环形缓冲区(ring buffer),也称作圆形队列(circular queue),圆形缓冲区(circular buffer),循环缓冲区(cyclic buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。
圆形缓冲区的一个有用特性是:当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。相反,一个非圆形缓冲区(例如一个普通的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。因此,圆形缓冲区适合实现先进先出缓冲区,而非圆形缓冲区适合后进先出缓冲区。扩展一个圆形缓冲区的容量,需要搬移其中的数据。因此一个缓冲区如果需要经常调整其容量,用链表实现更为合适。
kfifo是内核里面的一个First In First Out数据结构,它采用环形循环队列的数据结构来实现;它提供一个无边界的字节流服务,最重要的一点是,它使用并行无锁编程技术,即当它用于只有一个入队线程和一个出队线程的场情时,两个线程可以并发操作,而不需要任何加锁行为,就可以保证kfifo的线程安全。
本文分析的原代码版本:3.5 kfifo的定义文件:kernel/kfifo.c kfifo的头文件: include/linux/kfifo.h
struct __kfifo {
unsigned int in; // 和buffer一起构成一个循环队列。 in指向buffer中队头,out指向buffer中的队尾
unsigned int out;
unsigned int mask; // 代表index掩码,FIFO块个数减1,FIFO块个数需要为2的整数次方。
unsigned int esize; // 块(element)大小,FIFO按整块入队列和出队列,esize可以为1。
void *data; // 数据指针,可动态分配、静态分配、或用户传递数据地址。
};
struct kfifo 的定义,实际上它是由一个宏来定义的,如下。 kfifo 分为两种,record 型和非 record 型。
非 record 型分为静态和指针两种;
- STRUCT_KFIFO(type, size)
- STRUCT_KFIFO_PTR(type)
指针类型的buf为buf[0],数据动态分配。静态类型的,buf大小是宏传递的size,size必须为常亮。recsize为0。record 型的 recsize 的值可为1或2。(以下只关注非record型)
#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \
union { \
struct __kfifo kfifo; \
datatype *type; \
char (*rectype)[recsize]; \
ptrtype *ptr; \
const ptrtype *ptr_const; \
}
#define __STRUCT_KFIFO(type, size, recsize, ptrtype) \
{ \
__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
type buf[((size < 2) || (size & (size - 1))) ? -1 : size]; \
}
kfifo怎么和其它字段是联合呢?其它字段读写岂不是会覆盖kfifo的内容。其实这又是内核的一个技巧,其它字段不会读写数据,只是编译器用来获取相关信息 。比如:
获取recsize:
#define kfifo_recsize(fifo) (sizeof(*(fifo)->rectype))
再比如通过kfifo_alloc分配buf存储空间时,获取块的大小
__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \
上述通过宏定义的struct kfifo结构体展开之后的通用格式如下:
struct
{
union
{
struct __kfifo kfifo;
datatype *type;
const datatype *const_type;
char (*rectype)[recsize];
ptrtype *ptr;
ptrtype const *ptr_const;
};
datatype buf[size];
}
关于锁定的注意事项:在不会调用kfifo_reset()的情况下,只有一个读者和一个写者使用FIFO时不需要锁定,在读者线程中可以安全地使用kfifo_reset_out()。对于多个写者和一个读者,只需要锁定写者。反之亦然,只有一个写者和多个读者只需要锁定读者。
定义及初始化
DEFINE_KFIFO(fifo, type, size) :
/*
* 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))
/**
* DEFINE_KFIFO - 定义并且初始化一个 fifo 结构
* @fifo: name of the declared fifo datatype
* @type: type of the fifo elements
* @size: the number of elements in the fifo, 在初化时,将它向上扩展成2的幂
*/
#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, \
} \
} \
}
// 0 肯定是异常情况, 则 .mask = ARRAY_SIZE((fifo).buf) – 1
DEFINE_KFIFO 定义并初始化一个fifo,定义的时候调用的 DECLARE_KFIFO ,所以也是静态分配 buf 。
DECLARE_KFIFO(fifo, type, size) :
DECLARE_KFIFO_PTR(fifo, type):
#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \
union { \
struct __kfifo kfifo; \
datatype *type; \
char (*rectype)[recsize]; \
ptrtype *ptr; \
const ptrtype *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)
/**
* DECLARE_KFIFO - 定义一个 fifo object
* @fifo: name of the declared fifo
* @type: type of the fifo elements
* @size: the number of elements in the fifo, this must be a power of 2
*/
#define DECLARE_KFIFO(fifo, type, size) STRUCT_KFIFO(type, size) fifo
/* ------------------------ END DECLARE_KFIFO -------------------------- */
#define __STRUCT_KFIFO_PTR(type, recsize, ptrtype) \
{ \
__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
type buf[0]; \
}
struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void); // kfifo以unsigned char作为元素类型
#define STRUCT_KFIFO_PTR(type) \
struct __STRUCT_KFIFO_PTR(type, 0, type)
#define DECLARE_KFIFO_PTR(fifo, type) STRUCT_KFIFO_PTR(type) fifo
DECLARE_KFIFO 该宏用于静态声明一个kfifo对象, fifo 要定义的kfifo的名字, type 元素的类型, size kfifo可容纳的元素个数,必须是2的幂 。这个接口静态分配buf的存储空间,分配了一个大小为size、类型为 type 的数组。
DECLARE_KFIFO_PTR 这个接口与 DECLARE_KFIFO 的不同在于定义的buf数组的大小为0,即type buf[ 0 ],然后配合 kfifo_alloc 动态分配buf的存储空间。
INIT_KFIFO(fifo) :
/**
* INIT_KFIFO - 初始化一个 DECLARE_KFIFO 声明的 fifo 结构
* @fifo: fifo结构体变量
*/
#define INIT_KFIFO(fifo) \
(void)({ \
typeof(&(fifo)) __tmp = &(fifo); \
struct __kfifo *__kfifo = &__tmp->kfifo; \
__kfifo->in = 0; \
__kfifo->out = 0; \
__kfifo->mask = __is_kfifo_ptr(__tmp) ? 0 : ARRAY_SIZE(__tmp->buf) - 1;\
__kfifo->esize = sizeof(*__tmp->buf); \
__kfifo->data = __is_kfifo_ptr(__tmp) ? NULL : __tmp->buf; \
})
kfifo_init(fifo, buffer, size) :
int __kfifo_init(struct __kfifo *fifo, void *buffer,
unsigned int size, size_t esize)
{
size /= esize; // size 为 buf 中元素个数,必须为2的幂
if (!is_power_of_2(size))
size = rounddown_pow_of_two(size);
fifo->in = 0;
fifo->out = 0;
fifo->esize = esize;
fifo->data = buffer;
if (size < 2) {
fifo->mask = 0;
return -EINVAL;
}
fifo->mask = size - 1;
return 0;
}
#define kfifo_init(fifo, buffer, size) \
({ \
typeof((fifo) + 1) __tmp = (fifo); \
struct __kfifo *__kfifo = &__tmp->kfifo; \
__is_kfifo_ptr(__tmp) ? \
__kfifo_init(__kfifo, buffer, size, sizeof(*__tmp->type)) : \
-EINVAL; \
})
kfifo_init 接口使用一个预分配好的buffer初始化fifo,size 为buffer大小,单位是字节。
kfifo_alloc
在kfifo_alloc函数中,使用size & (size – 1)来判断size 是否为2幂,如果条件为真,则表示size不是2的幂
#define is_power_of_2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0))
然后调用roundup_pow_of_two将之向上扩展为2的幂
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.
*/
if (!is_power_of_2(size))
size = rounddown_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; // 即 ARRAY_SIZE((fifo).buf) – 1
return 0;
}
/* __kfifo_int_must_check_helper是一个辅助函数, 主要是为了帮助警告用户忘记检查返回值 */
#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; \
}) \
)
kfifo_alloc动态分配kfifo的buf成员变量的存储空间,调用的kmalloc函数。typeof((fifo) + 1)为什么要加1呢?主要的好处是帮助确定传递的参数类型是否正确, 如果传递的是结构体会产生编译错误,如果传递的是数组名, 如 int fifo[4] ,typeof(fifo)的结果为 int [4] , 而typeof(fifo + 1)的结果为 int *。
巧妙的入队和出队操作,无锁并发
kfifo_put是入队操作,它先将数据放入buffer里面,最后才修改in参数;kfifo_get是出队操作,它先将数据从buffer中移走,最后才修改out。你会发现in和out两者各司其职。计算机科学家已经证明,当只有一个读经程和一个写线程并发操作时,不需要任何额外的锁,就可以确保是线程安全的,也即kfifo使用了无锁编程技术,以提高kernel的并发。
/**
* kfifo_put - put data into the fifo
* @fifo: address of the fifo to be used
* @val: the data to be added
*
* This macro copies the given value into the fifo.
* It returns 0 if the fifo was full. Otherwise it returns the number
* processed elements.
*
* Note that with only one concurrent reader and one concurrent
* writer, you don't need extra locking to use these macro.
*/
#define kfifo_put(fifo, val) \
({ \
typeof((fifo) + 1) __tmp = (fifo); \
typeof((val) + 1) __val = (val); \
unsigned int __ret; \
const size_t __recsize = sizeof(*__tmp->rectype); \
struct __kfifo *__kfifo = &__tmp->kfifo; \
if (0) { \
typeof(__tmp->ptr_const) __dummy __attribute__ ((unused)); \
__dummy = (typeof(__val))NULL; \
} \
if (__recsize) \ // 非record类型,此处为0 \
__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) : \ // 强转data指针类型为数据块的类型
(__tmp->buf) \
)[__kfifo->in & __tmp->kfifo.mask] = \ // 方括号中内容为计算写指针索引
*(typeof(__tmp->type))__val; \ // 赋值了
smp_wmb(); \ // 考虑到处理器的优化和cpu核的跳转执行情况,添加内存屏障指令,保证了in更新前,数据已写入存储区
__kfifo->in++; \
} \
} \
__ret; \
})
kfifo要求缓冲区长度必须为2的幂。读、写指针分别是无符号整型变量。把读写指针变换为缓冲区内的索引值,仅需要“按位与”操作:(指针值 按位与 (缓冲区长度-1))。这避免了计算代价高昂的“求余”操作。且下述关系总是成立:
读指针 + 缓冲区存储的数据长度 == 写指针
即使在写指针达到了无符号整型的上界,上溢出后写指针的值小于读指针的值,上述关系仍然保持成立(这是因为无符号整型加法的性质)。 kfifo的写操作,首先计算缓冲区中当前可写入存储空间的数据长度:
len = min{待写入数据长度, 缓冲区长度 - (写指针 - 读指针)}
然后,分两段写入数据。第一段是从写指针开始向缓冲区末尾方向;第二段是从缓冲区起始处写入余下的可写入数据,这部分可能数据长度为0即并无实际数据写入。
实现技巧:
- size & (size - 1) 用于检测大小是否为2的幂
- typeof(*fifo->type) 用于获取数据类型
- in−out 表示队列长度 ,始终成立
- in & mask 读/写指针转化为数组索引
- typeof((fifo) + 1) 确保传递的是fifo指针,而不是结构体或数组名
- size - in + out 为缓冲区中空闲空间
- in == out时缓冲区为空
- size == (in - out)时缓冲区满了
接口
DECLARE_KFIFO_PTR(fifo, type) | 定义一个非record FIFO,名字为fifo,element类型为type,其数据需要动态分配。 |
DECLARE_KFIFO(fifo, type, size) | 定义一个非record FIFO,名字为fifo,element类型为type,element个数为size,其数据静态存储在结构体中,size需为常数且为2的整数次方 |
INIT_KFIFO(fifo) | 初始化DECLARE_KFIFO接口定义的fifo |
DEFINE_KFIFO(fifo, type, size) | 定义并初始化fifo |
kfifo_initialized(fifo) | fifo是否初始化 |
kfifo_esize(fifo) | 返回fifo的esize |
kfifo_recsize(fifo) | 返回fifo的recsize |
kfifo_size(fifo) | 返回fifo的size |
kfifo_reset(fifo) | 将in和out置0,注意:需要上锁。 |
kfifo_reset_out(fifo) | 将out设置为in,由于只修改out,因此在读者上下文,且只有一个读者时,是安全的。否则需要上锁。 |
kfifo_len(fifo) | 返回fifo中element的个数 |
kfifo_is_empty(fifo) | fifo是否为空 (in == out) |
kfifo_is_full(fifo) | fifo是否满 |
kfifo_avail(fifo) | 非record FIFO,返回可容纳的element个数 record FIFO,返回除去record头能容纳的字节数,最多不超过record头能表示的字节数,如recsize为1,最多返回255。 |
kfifo_skip(fifo) | 跳过一个element或record |
kfifo_peek_len(fifo) | 获取下一个element或者record的字节长度。 |
kfifo_alloc(fifo, size, gfp_mask) | 为指针式FIFO分配空间并初始化,成功返回0,错误则返回负数错误码 |
kfifo_free(fifo) | 释放kfifo_alloc分配的内存 |
kfifo_init(fifo, buffer, size) | 使用预分配的缓存初始化fifo,成功返回0,错误则返回负数错误码 |
kfifo_put(fifo, val) | 这是一个宏,将val赋值给一个FIFO type类型的临时变量,然后将临时变量入队。存放一个element,如果成功返回入队的elements个数。如果FIFO满,则返回0。 |
kfifo_get(fifo, val) | val是一个指针,内部将val赋值给一个ptr指针类型的临时变量,并拷贝sizeof(*ptr)长度到val的地址。对于record FIFO,其是void类型,sizeof(void)为1,所以拷贝1个字节。非record类型,拷贝一个element。 如果FIFO为空,返回0,否则返回拷贝的element数。 |
kfifo_peek(fifo, val) | 和kfifo_get相同,除了不更新out外。 |
kfifo_in(fifo, but, n) | 入队n个elemnts。返回工程入队的elements数。 |
kfifo_in(fifo, buf, n, lock) | 加锁入队。加锁方式为spin_lock_irqsave |
kfifo_out(fifo, buf, n) | 出队n个elements,返回成功拷贝的elements数 |
kfifo_out_spinlocked(fifo, buf, n, lock) | 加锁出队。加锁方式位spin_lock_irqsave |
kfifo_from_user(fifo, from, len, copied) | 最多拷贝len个字节,参考record FIFO和非record FIFO的对应底层接口。 |
kfifo_to_user(fifo, to, len, copied) | 最多拷贝len个字节到用户空间,参考record FIFO和非record FIFO的对应底层接口。 |
kfifo_dma_in_prepare kfifo_dma_in_finish kfifo_dma_out_prepare kfifo_dma_out_finish | DMA接口,略 |
kfifo_out_peek(fifo, buf, n) | peek n个elements的数据,但是内部out不动,返回拷贝的elements个数 |