1.介绍
kfifo是内核里的一个First In First Out数据结构,它采用环形循环队列的数据结构来实现,提供无边界的字节流服务(kfifo_rec 适用于块数据的数据缓存, 一个线程进行一块块的数据入队, 另一个线程进行每次取一块数据进行处理),并且当且仅当只有一个生产者(入队线程)和一个消费者(出队线程)的场情时,两个线程可以并发操作,而不需要加锁,就可以保证kfifo的线程安全。
2.实现
首先看一下kfifo的结构的示意图:
|<---------------------------size---------------------------->|
+--------------------------------------------------------------+
| |<----------data---------->| |
+--------------------------------------------------------------+
^ ^ ^
| | |
in out mask
struct __kfifo {
unsigned int in;
unsigned int out;
unsigned int mask;
unsigned int esize;
void *data;
};
/*
* 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.
*/
in, out, 和data一起构成一个循环队列。 in指向data头,而且out指向data尾
mask, 指向最后一个元素
esize, 元数据的大小
data, 用于存放数据的缓存
3.内存分配/初始化
int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
size_t esize, gfp_t gfp_mask)
{
/*
* round up to the next power of 2, since our 'let the indices
* wrap' technique works only in this case.
*/
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_array(esize, size, gfp_mask);
if (!fifo->data) {
fifo->mask = 0;
return -ENOMEM;
}
fifo->mask = size - 1;
return 0;
}
EXPORT_SYMBOL(__kfifo_alloc);
void __kfifo_free(struct __kfifo *fifo)
{
kfree(fifo->data);
fifo->in = 0;
fifo->out = 0;
fifo->esize = 0;
fifo->data = NULL;
fifo->mask = 0;
}
EXPORT_SYMBOL(__kfifo_free);
int __kfifo_init(struct __kfifo *fifo, void *buffer,
unsigned int size, size_t esize)
{
size /= esize;
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;
}
EXPORT_SYMBOL(__kfifo_init);
初始化后的结构示意图如下:
|<---------------------------size---------------------------->|
+--------------------------------------------------------------+
data=空
+--------------------------------------------------------------+
^
|
in、out
kfifo_alloc() 申请内存用的是 kmalloc(), 其申请的内存大小最小为32或64字节, 不能超过128KB,所以当需要 kfifo 大小大于128KB时, 最好就是通过 vmalloc() 申请内存, 然后通过 kfifo_init() 来初始化 kfifo,这相当于驱动自身管理 kfifo 的内存 buffer 的申请与释放,需要注意的是, kfifo_init() 要求 buffer 大小为2的幂(即2^n), 所以使用 vmlloc() 申请内存时要注意这点。
4.in/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;//*esize为了后续计算空闲位置的起始位置fifo->data + off
size *= esize;
len *= esize;
}
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();
}
unsigned int __kfifo_in(struct __kfifo *fifo,
const void *buf, unsigned int len)
{
unsigned int l;
l = kfifo_unused(fifo);
if (len > l)
len = l;
kfifo_copy_in(fifo, buf, len, fifo->in);
fifo->in += len;
return len;
}
EXPORT_SYMBOL(__kfifo_in);
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);
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_in() 函数在数据拷贝完成后, 直接对 fifo->in 的值加上入队长度, 而 fifo->in 是 unsigned int 类型的, 所以 fifo->in 超过 2^32 之后, 会从0开始计数
- kfifo_unused() 函数计算内存 buffer 未使用的内存大小时, 用 (fifo->in - fifo->out) 获取已使用内存的长度, 在 fifo->in 超过 2^32 溢出后能否成立? 答案是肯定的, 因为 (fifo->out + len = fifo->in), 所以使用 (fifo->in - fifo->out = len) 获取已使用内存的长度是成立的
- kfifo_copy_in() 函数进行数据拷贝时, 会用 fifo->in 与 fifo->mask 进行’与’运算求模, 从而获取到 fifo->in 在内存 buffer 中的对应位置, 使用’与’运算比直接使用 % 求模快, 提高了效率
5.参考
- https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/lib/kfifo.c
- https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/include/linux/kfifo.h
- https://blog.csdn.net/Bruno_Mars/article/details/100061793
- https://blog.csdn.net/hui_lin/article/details/43149377