Linux kfifo 源码分析

文章目录

  • 说明
  • kfifo 特性
  • kfifo 结构体
  • kfifo 初始化
    • kfifo_alloc(fifo, size, gfp_mask)
    • kfifo_init(fifo, buffer, size)
    • kfifo 初始化相关函数/宏说明
  • kfifo 入队
    • 入队相关函数/宏说明
  • 出队
    • 出队相关函数/宏说明
  • 其他函数/宏说明
  • 移植 kfifo 到应用层

说明

本文章分析基于内核版本: 4.9.65

kfifo 特性

kfifo是内核里面的一个First In First Out数据结构
源码位于:

linux\lib\kfifo.c
linux\include\linux\kfifo.h

kfifo 实现了使用简单/性能高效的队列操作

只要满足以下要求, kfifo 操作便可以实现不加锁, 从而提高性能

  • 只有一个 reader 和 一个 writer
  • 不调用 kfifo_reset()
  • 如果有调用 kfifo_reset_out(), 只在出队线程中调用

而对于多个 writer 对应一个 reader 的情况, 只需要在 writer (入队线程)加锁即可
而对于多个 reader 对应一个 writer 的情况, 只需要在 reader (出队线程)加锁即可
源码中关于锁的相关介绍如下:

/*
 * Note about locking : There is no locking required until only * one reader
 * and one writer is using the fifo and no kfifo_reset() will be * called
 *  kfifo_reset_out() can be safely used, until it will be only called
 * in the reader thread.
 *  For multiple writer and one reader there is only a need to lock the writer.
 * And vice versa for only one writer and multiple reader there is only a need
 * to lock the reader.
 */

kfifo 分为两种, 一种是普通的 kfifo, 一种是带长度记录的 kfiro_rec
对于 kfifo,内存 buffer 全部用来存放数据,如下图:

在这里插入图片描述

而对于 kfiro_rec,内存 buffer 在每次入队时都会用一个字节或两个字节存放数据的长度,如下图:
Linux kfifo 源码分析_第1张图片

  • kfifo 适用于流数据的数据缓存, 一个线程进行数据入队, 另一个线程进行数据出队处理
  • kfifo_rec 适用于块数据的数据缓存, 一个线程进行一块块的数据入队, 另一个线程进行每次取一块数据进行处理

具体选用哪种类型的 kfifo 取决于具体的场景
kfifo 与 kfifo_rec 在初始化、入队、出队时的操作过程不太一样
以下只针对 kfifo 类型进行分析

kfifo 结构体

将 kfifo 对应的宏展开后如下

struct kfifo {
    union {
        struct __kfifo kfifo;
        unsigned char *type;
        const unsigned char *const_type;
        char (*rectype)[0]; /* 普通的 kfifo 该字段大小为0, kfiro_rec 该字段大小为1或2 */
        void *ptr;
        void const *ptr_const;
    }
    unsigned char buf[0];
}

struct __kfifo {
    unsigned int in;
    unsigned int out;
    unsigned int mask;
    unsigned int esize;
    void         *data;
};

kfifo 初始化

kfifo_alloc(fifo, size, gfp_mask)

kfifo_alloc 相关的宏如下

#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;                                                       \
})                                                                 \
)

static inline int __must_check __kfifo_int_must_check_helper(int val)
{
	return val;
}

fifo 是传进来的一个kfifo指针 struct kfifo *, fifo + 1根据类型转换可知道还是一个指针类型(例如 char + int 类型, 得到的结果是 int 类型), 所以 typeof((fifo) + 1) 就是 struct kfifo *
typeof((fifo) + 1) 这里如果不加1也是可以的, 加1的原因应该是为了在误操作能出现编译错误提示(例如 fifo 要求是struct kfifo *类型, 如果传入struct kfifo类型会出现编译错误提示)

将宏展开后如下

#define kfifo_alloc(fifo, size, gfp_mask)
{
	struct kfifo *__tmp = (fifo);
	struct __kfifo *__kfifo = &__tmp->kfifo;
	return __is_kfifo_ptr(__tmp) ? __kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : -EINVAL;
}

struct kfifo 结构体的 type 字段为 unsigned char * 类型, 这里进行取址后则为unsigned char, 所以 sizeof(*__tmp->type)大小为1

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.
	 */
	/*
	* 修改size大小为2的幂,即 2^n, 例如传进size=7, 这一步将size修改为2的3次方, 即size=8
	*/
	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;
	}

    /*
	 *这里要注意的是 size 为2的幂, mask = size - 1
	 *所以 mask 在二进制角度看是一个全为1的值
	 *这在为后面使用 mask 进行'与'计算求模做准备
     */
	fifo->mask = size - 1;

	return 0;
}

初始化后的 kfifo 状态如下图:
Linux kfifo 源码分析_第2张图片

kfifo_init(fifo, buffer, size)

由于 kfifo_alloc() 申请内存用的是 kmalloc(), 其申请的内存大小最小为32或64字节, 不能超过128KB
所以当需要 kfifo 大小大于128KB时, 最好就是通过 vmalloc() 申请内存, 然后通过 kfifo_init() 来初始化 kfifo
相当于驱动自身管理 kfifo 的内存 buffer 的申请与释放
需要注意的是, kfifo_init() 要求 buffer 大小为2的幂(即2^n), 所以使用 vmlloc() 申请内存时要注意这点

kfifo_init() 的操作过程跟 kfifo_alloc() 类似, 只是少了申请内存的步骤, 可看代码理解.

kfifo 初始化相关函数/宏说明

函数/宏 功能
kfifo_alloc(fifo, size, gfp_mask) 为 kfifo 申请内存 buffer 并初始化 kfifo
kfifo_free(fifo) 释放 kfifo 对应的内存 buffer
kfifo_init(fifo, buffer, size) 初始化 kfifo

kfifo 入队

kfifo_in() 宏如下

#define	kfifo_in(fifo, buf, n)                        \
({                                                    \
	typeof((fifo) + 1) __tmp = (fifo);                \
	typeof(__tmp->ptr_const) __buf = (buf);           \
	unsigned long __n = (n);                          \
	const size_t __recsize = sizeof(*__tmp->rectype); \
	struct __kfifo *__kfifo = &__tmp->kfifo;          \
	(__recsize) ?                                     \
	__kfifo_in_r(__kfifo, __buf, __n, __recsize) :    \
	__kfifo_in(__kfifo, __buf, __n);                  \
})

fifo 传进的是 struct kfifo * 类型
将宏展开后如下

#define	kfifo_in(fifo, buf, n)                                                                    \
({                                                                                                \
	struct __kfifo *__tmp = (fifo);                                                               \
	void const * __buf = (buf);                                                                   \
	unsigned long __n = (n);                                                                      \
	const size_t __recsize = sizeof(*__tmp->rectype);                                             \
	struct __kfifo *__kfifo = &__tmp->kfifo;                                                      \
	(__recsize) ? __kfifo_in_r(__kfifo, __buf, __n, __recsize) : __kfifo_in(__kfifo, __buf, __n); \
})

kfifo_in() 调用 __kfifo_in()

unsigned int __kfifo_in(struct __kfifo *fifo, const void *buf, unsigned int len)
{
	unsigned int l;

    /*
     *获得未使用的 data 内存长度
     */
	l = kfifo_unused(fifo);
	if (len > l)
		len = l;

	kfifo_copy_in(fifo, buf, len, fifo->in);
    /*
     *in += len, in往前移动
     */
	fifo->in += len;
	return len;
}

static inline unsigned int kfifo_unused(struct __kfifo *fifo)
{
	return (fifo->mask + 1) - (fifo->in - fifo->out);
}


static void kfifo_copy_in(struct __kfifo *fifo, const void *src,
		unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    /*
     *'与'运行获得求模
     */
    off &= fifo->mask;

    if (esize != 1) {
        off *= esize;
        size *= esize;
        len *= esize;
    }

    /*
     *拷贝数据到 in 后面的内存
     *如果 in 后面的内存 buffer 段不够存放
     *则将剩余的数据拷贝到内存 buffer 的开头对应的位置
     */
    l = min(len, size - off);
    memcpy(fifo->data + off, src, l);
    memcpy(fifo->data, src + l, len - l);

    /*
     * make sure that the data in the fifo is up to date before
     * incrementing the fifo->in index counter
     */
    /*
     *内存屏障
     */
    smp_wmb();
}

入队步骤如下:

  1. 获取未使用的内存长度
  2. 拷贝数据到内存 buffer
  3. fifo->in 加上入队的长度
  4. 返回入队的长度

这里需要注意:

  • __kfifo_in() 函数在数据拷贝完成后, 直接对 fifo->in 的值加上入队长度, 而 fifo->in 是 unsigned int 类型的, 所以 fifo->in 超过 2^32 之后, 会从0开始计数
  • kfifo_copy_in() 函数进行数据拷贝时, 会用 fifo->in 与 fifo->mask 进行’与’运算求模, 从而获取到 fifo->in 在内存 buffer 中的对应位置, 使用’与’运算比直接使用 % 求模快, 提高了效率
  • kfifo_unused() 函数计算内存 buffer 未使用的内存大小时, 用 (fifo->in - fifo->out) 获取已使用内存的长度, 在 fifo->in 超过 2^32 溢出后能否成立? 答案是肯定的, 因为 (fifo->out + len = fifo->in), 所以使用 (fifo->in - fifo->out = len) 获取已使用内存的长度是成立的

入队时的状态可用下图表示:

Linux kfifo 源码分析_第3张图片

Linux kfifo 源码分析_第4张图片

入队相关函数/宏说明

函数/宏 功能
kfifo_put(fifo, val) 将 val 入队, 该宏用于一个字节的入队
kfifo_in(fifo, buf, n) 将 buf 的 n 个字节入队
kfifo_in_spinlocked(fifo, buf, n, lock) 将 buf 的 n 个字节入队, 该入队过程加锁

出队

#define	kfifo_out(fifo, buf, n)                       \
__kfifo_uint_must_check_helper(                       \
({                                                    \
	typeof((fifo) + 1) __tmp = (fifo);                \
	typeof(__tmp->ptr) __buf = (buf);                 \
	unsigned long __n = (n);                          \
	const size_t __recsize = sizeof(*__tmp->rectype); \
	struct __kfifo *__kfifo = &__tmp->kfifo;          \
	(__recsize) ?                                     \
	__kfifo_out_r(__kfifo, __buf, __n, __recsize) :   \
	__kfifo_out(__kfifo, __buf, __n);                 \
})                                                    \
)

static inline unsigned int __must_check __kfifo_uint_must_check_helper(unsigned int val)
{
	return val;
}

fifo 传进的是 struct kfifo * 类型
将宏展开后如下

#define	kfifo_out(fifo, buf, n)                                                                            \
({                                                                                                         \
	struct kfifo *__tmp = (fifo);                                                                          \
	void *__buf = (buf);                                                                                   \
	unsigned long __n = (n);                                                                               \
	const size_t __recsize = sizeof(*__tmp->rectype);                                                      \
	struct __kfifo *__kfifo = &__tmp->kfifo;                                                               \
	return (__recsize) ? __kfifo_out_r(__kfifo, __buf, __n, __recsize) : __kfifo_out(__kfifo, __buf, __n); \
})

kfifo_out() 调用 __kfifo_out()

unsigned int __kfifo_out(struct __kfifo *fifo, void *buf, unsigned int len)
{
    len = __kfifo_out_peek(fifo, buf, len);
    /*
     *out += len, out往前移动
     */
    fifo->out += len;
    return len;
}
unsigned int __kfifo_out_peek(struct __kfifo *fifo, void *buf, unsigned int len)
{
    unsigned int l;

    /*
     *获得已使用的内存 buffer 长度
     */
    l = fifo->in - fifo->out;
    if (len > l)
        len = l;

    kfifo_copy_out(fifo, buf, len, fifo->out);
    return len;
}
static void kfifo_copy_out(struct __kfifo *fifo, void *dst, unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    /*
     *'与'运行获得求模
     */
    off &= fifo->mask;

    if (esize != 1) {
        off *= esize;
        size *= esize;
        len *= esize;
    }
    l = min(len, size - off);

    /*
     *将 out 后面的内存拷贝出来
     *如果 out 后面的内存 buffer 段长度比需要拷出来的数据长度小
     *则剩余的数据将从内存 buffer 的开头对应的位置拷贝
     */
    memcpy(dst, fifo->data + off, l);
    memcpy(dst + l, fifo->data, len - l);
    /*
     * make sure that the data is copied before
     * incrementing the fifo->out index counter
     */
    /*
     *内存屏障
     */
    smp_wmb();
}

出队步骤如下:

  1. 获取已使用的内存长度
  2. 拷贝内存 buffer 到目标地址
  3. fifo->out 加上出队的长度
  4. 返回出队的长度

需要注意与入队时的一样.

出队时的状态可用下图表示:
Linux kfifo 源码分析_第5张图片

Linux kfifo 源码分析_第6张图片

出队相关函数/宏说明

函数/宏 功能
kfifo_get(fifo, val) 获取 fifo 中地址 val 处的数据
kfifo_peek(fifo, val) 获取 fifo 中地址 val 出的数据, 但是没有将其从 fifo 中移除(没有出队操作)
kfifo_out(fifo, buf, n) 将 fifo 中的 n 个字节出队, 拷贝到 buf 中
kfifo_out_spinlocked(fifo, buf, n) 将 fifo 中的 n 个字节出队, 拷贝到 buf 中, 该出队过程加锁
kfifo_out_peek(fifo, buf, n) 将 fifo 中的 n 个字节出队, 拷贝到 buf 中, 但是没有将其从 fifo 中移除(没有出队操作)

其他函数/宏说明

函数/宏 功能
kfifo_size(fifo) 获取 kfifo 对应内存 buffer 的大小
kfifo_len(fifo) 获取 kfifo 对应内存 buffer 中已经使用了的大小
kfifo_is_empty(fifo) fifo为空则返回1, 否则返回0
kfifo_is_full(fifo) fifo为满则返回1, 否则返回0
kfifo_reset(fifo) 重新初始化 kfifo, in 和 out 重新指向内存首地址, 要在读写线程都不使用的情况下用, 否则不安全
kfifo_reset_out(fifo) 重新初始化 kfifo, 在只有一个 reader 的读线程中使用是安全的, 其他情况下使用不安全
kfifo_from_user(fifo, from, len, copied) 与驱动 struct file_operations 结构体的 write 回调函数配合使用
kfifo_to_user(fifo, to, len, copied) 与驱动 struct file_operations 结构体的 read 回调函数配合使用

移植 kfifo 到应用层

已经将 kfifo 移植到应用层, 有需要的可通过以下链接下载源码使用:
linux下移植到应用层的 kfifo
包含 kfifo 源码与测试代码, 编译后运行结果如下所示:
Linux kfifo 源码分析_第7张图片

你可能感兴趣的:(Linux,驱动,Linux)