kfifo是linux内核中非常经典的无锁环形队列,在内核中有很多优秀的设计,用匠心独具,巧夺天工来形容也不为过。kfifo的实现非常经典,代码如下
struct __kfifo{
unsigned int in; //入队索引
unsigned int out; //出队索引
unsigned int mask; //大小掩码
unsigned int esize; //元素大小
void *data; //缓冲区指针
};
在这个结构中,需要特别注意mask,mask的值被设置为size - 1,即队列大小减一,目的是为了后续进行逻辑与运算方便(后文详细介绍)。这个结构体是kfifo最主要的一部分,但是还不能称其为真正意义上的kfifo,它其实相当于面向对象思想中的基类。
再往下看
#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; \
}
这是整个kfifo设计中最为巧妙的一部分(个人认为),这个联合体包含了一个kfifo所需要的所有属性。首先分析一下为什么要用到联合,联合体(union)是C语言中非常特殊的存在,可以认为他是一个特殊的struct,但是与struct相比有很多的区别,其中最主要的区别就是联合体在编译器分配内存的时候只会根据其最大的元素分配内存,并且在赋值的时候只能给其中的某一个元素赋值,因此对于该联合体,编译器只会分配一块大小为sizeof(struct __kfifo)的内存,并不会为其他元素分配内存,这样将节省很多内存空间。其次,该联合体使用宏定义的方式实现,宏定义一直是C语言高手非常喜欢用的一个特性,在高手手中,宏定义往往可以模拟很多泛型编程,用上面的联合体举例说明:
#define __STRUCT_KFIFO_COMMON(int, 0, int) \
union { \
struct __kfifo kfifo; \
int *type; \
const int *const_type; \
char (*rectype)[recsize]; \
int *ptr; \
int const *ptr_const; \
}
上面的例子中我们用int, 0, int 分别代替了datatype, recsize, ptrtype,可以看到,联合体就会变成上面的形式,对于不同的datatype, recsize, ptrtype组合,都会有不同的联合体被灵活的定义,这是C语言函数所没有的灵活特性。并且除了kfifo这一个元素,其余元素都不会被赋值,所以并不用担心会造成数据的丢失。 rectype 为记录字段,对于记录类型的fifo来说,会将整个fifo划分成固定大小的几个块 每次入队的元素数量如果大于记录区域的长度,则会按照最大的记录区域截取 对于第一类记录类型,每块区域的长度为255,其中每块区域的第一个字节位置存储该区域存储的数据长度 对于第二类记录类型,每块区域的长度为65535,其中每块区域的第一个字节和第二个字节共同存储该区域存储的数据长度 。unsigned int max = (1 << (recsize << 3)) - 1 (这一行代码位于kfifo.c这个文件中__kfifo_max_r函数里面)这一行代码解释了为什么第一类和第二类记录类型的fifo的 区域长度分别为255和65535。
#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 __STRUCT_KFIFO_PTR(type, recsize, ptrtype) \
{ \
__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
type buf[0]; \
}
#define STRUCT_KFIFO_PTR(type) \
struct __STRUCT_KFIFO_PTR(type, 0, type)
接下来的四个宏定义分别实现了struct_kfifo和struct_kfifo_ptr两个结构,对于第一个结构,是将数据缓冲区包含在整个结构中,在定义结构的时候直接固定了大小,第二个结构只包含数据缓冲区的指针,真正的数据缓冲区在后面通过kfifo_alloc动态分配,至此,面向对象的继承和多态已经被完美体现了有木有 :)
struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);
#define STRUCT_KFIFO_REC_1(size) \
struct __STRUCT_KFIFO(unsigned char, size, 1, void)
#define STRUCT_KFIFO_REC_2(size) \
struct __STRUCT_KFIFO(unsigned char, size, 2, void)
struct kfifo_rec_ptr_1 __STRUCT_KFIFO_PTR(unsigned char, 1, void);
struct kfifo_rec_ptr_2 __STRUCT_KFIFO_PTR(unsigned char, 2, void);
这三个结构就不再赘述了,相信大家一看便懂,这三个结构也是面向对象的继承和多态的完美体现。
剩余的部分代码和注释放在下面供大家学习。
/**
* 辅助宏,用来判断队列是否是原地FIFO
* 原地FIFO就是指队列BUFFER存在于数据结构中,
* 如果不是原地FIFO,则返回 1, 否则返回 0
* */
#define __is_kfifo_ptr(fifo) \
(sizeof(*fifo) == sizeof(STRUCT_KFIFO_PTR(typeof(*(fifo)->type))))
/**
* DECLARE_KFIFO_PTR - 宏,用于声明 fifo 指针对象
* @fifo: 声明的 fifo 的名称
* @type: fifo 元素的类型
*/
#define DECLARE_KFIFO_PTR(fifo, type) STRUCT_KFIFO_PTR(type) fifo
/**
* DECLARE_KFIFO - 宏,用于声明一个 fifo 对象
* @fifo: 声明的 fifo 名称
* @type: fifo 元素的类型
* @size:fifo 中元素的个数,必须是 2 的幂次
*/
#define DECLARE_KFIFO(fifo, type, size) STRUCT_KFIFO(type, size) 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; \
})
/**
* DEFINE_KFIFO - 用于定义和初始化 fifo 的宏
* @fifo: 已声明的 fifo 数据类型名称
* @type:fifo 元素的类型
* @size:fifo 中元素的数量,必须是 2 的幂次
*
* 注意:该宏可用于全局和局部 fifo 数据类型变量。
*/
#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, \
} \
} \
}
(原地FIFO是我用翻译软件翻译的。。。。原文为real in place fifo。。。。。。)