本文章分析基于内核版本: 4.9.65
kfifo是内核里面的一个First In First Out数据结构
源码位于:
linux\lib\kfifo.c
linux\include\linux\kfifo.h
kfifo 实现了使用简单/性能高效的队列操作
只要满足以下要求, kfifo 操作便可以实现不加锁, 从而提高性能
而对于多个 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 在每次入队时都会用一个字节或两个字节存放数据的长度,如下图:
具体选用哪种类型的 kfifo 取决于具体的场景
kfifo 与 kfifo_rec 在初始化、入队、出队时的操作过程不太一样
以下只针对 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_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_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_alloc(fifo, size, gfp_mask) | 为 kfifo 申请内存 buffer 并初始化 kfifo |
kfifo_free(fifo) | 释放 kfifo 对应的内存 buffer |
kfifo_init(fifo, buffer, size) | 初始化 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();
}
入队步骤如下:
这里需要注意:
入队时的状态可用下图表示:
函数/宏 | 功能 |
---|---|
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();
}
出队步骤如下:
需要注意与入队时的一样.
函数/宏 | 功能 |
---|---|
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 移植到应用层, 有需要的可通过以下链接下载源码使用:
linux下移植到应用层的 kfifo
包含 kfifo 源码与测试代码, 编译后运行结果如下所示: