kfifo 无锁循环队列

一、提供的接口 #

1. 定义相关 #

  • 声明和初始化
 1// include/linux/kfifo.h
 2/**
 3 * DECLARE_KFIFO - macro to declare a fifo object
 4 * @fifo: name of the declared fifo
 5 * @type: type of the fifo elements
 6 * @size: the number of elements in the fifo, this must be a power of 2
 7 */
 8#define DECLARE_KFIFO(fifo, type, size) STRUCT_KFIFO(type, size) fifo
 9
10/**
11 * INIT_KFIFO - Initialize a fifo declared by DECLARE_KFIFO
12 * @fifo: name of the declared fifo datatype
13 */
14#define INIT_KFIFO(fifo) \
15(void)({ \
16    typeof(&(fifo)) __tmp = &(fifo); \
17    struct __kfifo *__kfifo = &__tmp->kfifo; \
18    __kfifo->in = 0; \
19    __kfifo->out = 0; \
20    __kfifo->mask = __is_kfifo_ptr(__tmp) ? 0 : ARRAY_SIZE(__tmp->buf) - 1;\
21    __kfifo->esize = sizeof(*__tmp->buf); \
22    __kfifo->data = __is_kfifo_ptr(__tmp) ?  NULL : __tmp->buf; \
23})
  • 声明和初始化一起可以用DEFINE_KFIFO
 1// include/linux/kfifo.h
 2/**
 3 * DEFINE_KFIFO - macro to define and initialize a fifo
 4 * @fifo: name of the declared fifo datatype
 5 * @type: type of the fifo elements
 6 * @size: the number of elements in the fifo, this must be a power of 2
 7 *
 8 * Note: the macro can be used for global and local fifo data type variables.
 9 */
10#define DEFINE_KFIFO(fifo, type, size) \
11    DECLARE_KFIFO(fifo, type, size) = \
12    (typeof(fifo)) { \
13        { \
14            { \
15            .in = 0, \
16            .out    = 0, \
17            .mask   = __is_kfifo_ptr(&(fifo)) ? \
18                  0 : \
19                  ARRAY_SIZE((fifo).buf) - 1, \
20            .esize  = sizeof(*(fifo).buf), \
21            .data   = __is_kfifo_ptr(&(fifo)) ? \
22                NULL : \
23                (fifo).buf, \
24            } \
25        } \
26    }

2. 操作相关 #

  • 单个元素操作
 1// include/linux/kfifo.h
 2/**
 3 * kfifo_put - put data into the fifo
 4 * @fifo: address of the fifo to be used
 5 * @val: the data to be added
 6 *
 7 * This macro copies the given value into the fifo.
 8 * It returns 0 if the fifo was full. Otherwise it returns the number
 9 * processed elements.
10 *
11 * Note that with only one concurrent reader and one concurrent
12 * writer, you don't need extra locking to use these macro.
13 */
14#define kfifo_put(fifo, val)
15...
16
17/**
18 * kfifo_get - get data from the fifo
19 * @fifo: address of the fifo to be used
20 * @val: address where to store the data
21 *
22 * This macro reads the data from the fifo.
23 * It returns 0 if the fifo was empty. Otherwise it returns the number
24 * processed elements.
25 *
26 * Note that with only one concurrent reader and one concurrent
27 * writer, you don't need extra locking to use these macro.
28 */
29#define kfifo_get(fifo, val) \
30__kfifo_uint_must_check_helper( \
31({ \
32    typeof((fifo) + 1) __tmp = (fifo); \
33    typeof(__tmp->ptr) __val = (val); \
34    unsigned int __ret; \
35    const size_t __recsize = sizeof(*__tmp->rectype); \
36    struct __kfifo *__kfifo = &__tmp->kfifo; \
37    if (__recsize) \
38        __ret = __kfifo_out_r(__kfifo, __val, sizeof(*__val), \
39            __recsize); \
40    else { \
41        __ret = !kfifo_is_empty(__tmp); \
42        if (__ret) { \
43            *(typeof(__tmp->type))__val = \
44                (__is_kfifo_ptr(__tmp) ? \
45                ((typeof(__tmp->type))__kfifo->data) : \
46                (__tmp->buf) \
47                )[__kfifo->out & __tmp->kfifo.mask]; \
48            smp_wmb(); \
49            __kfifo->out++; \
50        } \
51    } \
52    __ret; \
53}) \
54)

3. 获取属性 #

 1// include/linux/kfifo.h
 2/**
 3 * kfifo_is_empty - returns true if the fifo is empty
 4 * @fifo: address of the fifo to be used
 5 */
 6#define kfifo_is_empty(fifo) \
 7({ \
 8    typeof((fifo) + 1) __tmpq = (fifo); \
 9    __tmpq->kfifo.in == __tmpq->kfifo.out; \
10})
11
12/**
13 * kfifo_is_full - returns true if the fifo is full
14 * @fifo: address of the fifo to be used
15 */
16#define kfifo_is_full(fifo)

示例用法 #

 1/* keybuf.h */
 2#include <linux/kfifo.h>
 3#define KEYBUF_KFIFO_SIZE 32
 4// 这里将类型单独定义出来
 5typedef STRUCT_KFIFO(unsigned char, 32) KeyBufType;
 6// 按键结构体
 7extern KeyBufType g_keybuf;
 8
 9/* keybuf.c */
10#include <linux/kfifo.h>
11KeyBufType g_keybuf = (typeof(g_keybuf)){{{
12    .in = 0,
13    .out = 0,
14    .mask = __is_kfifo_ptr(&(g_keybuf)) ? 0 : ARRAY_SIZE((g_keybuf).buf) - 1,
15    .esize = sizeof(*(g_keybuf).buf),
16    .data = __is_kfifo_ptr(&(g_keybuf)) ? NULL : (g_keybuf).buf,
17}}};
18
19void intHandler() {
20    ...
21    // 插入一个数据
22    kfifo_put(&g_keybuf, data);
23}
24
25/* main.c */
26#include "keybuf.h"
27
28int main() {
29    for (;;) {
30        io_cli();
31        // 判断是否为空
32        if (kfifo_is_empty(&g_keybuf)) {
33            io_stihlt();
34        } else {
35            // 取一个数据出来
36            unsigned char i;
37            kfifo_get(&g_keybuf, &i);
38            ...
39        }
40    }
41}

二、源码分析 #

1. 定义 #

  • 主要结构体__kfifo
1// include/linux/kfifo.h
2struct __kfifo {
3    unsigned int    in;     // 写索引
4    unsigned int    out;    // 读索引
5    unsigned int    mask;   // 读写的mask,循环的高效写法的关键
6    unsigned int    esize;  // 数据的长度
7    void        *data;      // 数据段起始地址
8};
  • 联合声明
  • typeconst_typeptrptr_const的值没有实际含义,只是在后续实现的时候可以使用typeof()来找到类型,可以理解成是模板的C语言版本实现
  • rectype是作为一个占位符存在的,看清楚是一个指针数组,相当于可以指定头部大小,recsize指定即可,默认的是0,也就是按照__kfifo的大小最小占位
1#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \
2    union { \
3        struct __kfifo  kfifo; \
4        datatype    *type; \
5        const datatype  *const_type; \
6        char        (*rectype)[recsize]; \
7        ptrtype     *ptr; \
8        ptrtype const   *ptr_const; \
9    }

2. 操作 #

2.1. kfifo_put #

  • 定义
 1// include/linux/kfifo.h
 2#define kfifo_put(fifo, val) \
 3({ \
 4    typeof((fifo) + 1) __tmp = (fifo); \
 5    typeof(*__tmp->const_type) __val = (val); \
 6    unsigned int __ret; \
 7    size_t __recsize = sizeof(*__tmp->rectype); \
 8    struct __kfifo *__kfifo = &__tmp->kfifo; \
 9    if (__recsize) \
10        __ret = __kfifo_in_r(__kfifo, &__val, sizeof(__val), \
11            __recsize); \
12    else { \
13        __ret = !kfifo_is_full(__tmp); \
14        if (__ret) { \
15            (__is_kfifo_ptr(__tmp) ? \
16            ((typeof(__tmp->type))__kfifo->data) : \
17            (__tmp->buf) \
18            )[__kfifo->in & __tmp->kfifo.mask] = \
19                *(typeof(__tmp->type))&__val; \
20            smp_wmb(); \
21            __kfifo->in++; \
22        } \
23    } \
24    __ret; \
25})
  • 宏展开为
 1({
 2    typeof((&fifo) + 1) __tmp = (&fifo);
 3    typeof(*__tmp->const_type) __val = (data);
 4    unsigned int __ret;
 5    size_t __recsize = sizeof(*__tmp->rectype);
 6    struct __kfifo *__kfifo = &__tmp->kfifo;
 7    if (__recsize)
 8        __ret = __kfifo_in_r(__kfifo, &__val, sizeof(__val), __recsize);
 9    else {
10        __ret = !kfifo_is_full(__tmp);
11        if (__ret) {
12            (__is_kfifo_ptr(__tmp) ? ((typeof(__tmp->type))__kfifo->data)
13                                    : (__tmp->buf))[__kfifo->in & __tmp->kfifo.mask] = *(typeof(__tmp->type))&__val;
14            smp_wmb();
15            __kfifo->in++;
16        }
17    }
18    __ret;
19})
  • 上面关键点在于[__kfifo->in & __tmp->kfifo.mask],里面的in可以任意溢出,只保留mask的值
  • 也就是假设size为32,mask为0b11111,in随便加,溢出后被mask与就只剩0-31,也就是从0开始,形成循环

3. 属性 #

3.1. kfifo_is_full #

1// include/linux/kfifo.h
2#define kfifo_is_full(fifo)                     \
3    ({                                          \
4        typeof((fifo) + 1) __tmpq = (fifo);     \
5        kfifo_len(__tmpq) > __tmpq->kfifo.mask; \
6    })
  • 宏展开
1({
2    typeof((&fifo) + 1) __tmpq = (&fifo);
3    ({
4        typeof((__tmpq) + 1) __tmpl = (__tmpq);
5        __tmpl->kfifo.in - __tmpl->kfifo.out;
6    }) > __tmpq->kfifo.mask;
7})
  • 直接用in减去out,肯定会想如果in溢出从0开始怎么办,举个例子就知道了。假设in和out都是8位,那么0x00 - 0xff = 0x01,可以看到,溢出不影响计算两个的差值,所以整个kfifo的操作过程中,in和out只管尽情加就好了,不用考虑溢出的问题
  • 两个差值大于mask就说明确实满了,假设容量2个,差值为2,mask为1,大于正好满了