
在Linux内核中,kfifo (Kernel FIFO)是一个高效、通用的循环缓冲区(Circular Buffer)实现,专为生产者-消费者模型设计,广泛用于内核中的数据暂存与传输(如驱动中的中断上下文与进程上下文通信、模块间数据交换等)。
它支持动态/静态分配 、字节流/结构化数据 、**无锁单生产者单消费者(SPSC)** 等特性,是内核中最常用的FIFO组件之一。
一、kfifo的核心特点
-
循环缓冲:固定大小的缓冲区,写满后覆盖旧数据(或阻塞,取决于使用方式),读空后返回0。
-
高效操作 :入队(
kfifo_in)和出队(kfifo_out)均为**O(1)** 时间复杂度,通过位运算实现快速索引。 -
线程安全:
-
单生产者单消费者(SPSC):无锁(依赖CPU原子操作),无需额外同步。
-
多生产者/多消费者(MPMC):需用户自行加锁(如
spinlock、mutex)。
-
-
灵活分配 :支持静态数组 (编译时分配)和动态内存(运行时分配)。
-
数据无关性 :以字节流为单位操作,可存储任意类型数据(需用户自行解析)。
二、kfifo的使用步骤
1. 包含头文件
cpp
#include <linux/kfifo.h>
2. 定义kfifo结构
kfifo的核心是struct kfifo结构体(内核隐藏实现,用户无需直接访问),通常通过以下方式定义:
-
静态分配 (编译时分配缓冲区):用
DEFINE_KFIFO或DECLARE_KFIFO宏。 -
动态分配 (运行时分配缓冲区):用
kfifo_alloc函数。
(1)静态分配示例
cpp
// 定义一个存储int类型的kfifo,大小为8(必须是2的幂,内核会自动调整吗?不,用户需确保!)
#define FIFO_SIZE 8 // 必须是2的幂(如8=2^3、16=2^4),否则会导致索引错误
static DEFINE_KFIFO(my_fifo, int, FIFO_SIZE); // 类型:int,大小:FIFO_SIZE
// 或更灵活的声明(后续初始化):
static DECLARE_KFIFO_PTR(my_fifo_ptr, int); // 指针型声明,需用kfifo_init初始化
(2)动态分配示例
cpp
struct kfifo *dyn_fifo;
int ret;
// 分配一个存储char类型的kfifo,大小为16(必须是2的幂)
ret = kfifo_alloc(&dyn_fifo, 16, GFP_KERNEL); // GFP_KERNEL表示内核内存分配标志
if (ret) {
// 处理分配失败(如打印错误日志)
printk(KERN_ERR "kfifo_alloc failed\n");
return ret;
}
// 注意:动态分配的kfifo需用kfifo_free释放
// kfifo_free(&dyn_fifo);
3. 初始化kfifo
-
静态分配 :
DEFINE_KFIFO/DECLARE_KFIFO已自动初始化,无需额外操作。 -
动态分配 :用
kfifo_init(针对已有缓冲区)或kfifo_alloc(自动分配缓冲区)。
示例(动态初始化已有缓冲区):
cpp
char buffer[32]; // 缓冲区(大小必须是2的幂)
struct kfifo my_fifo;
// 初始化kfifo,关联buffer,大小为sizeof(buffer)
kfifo_init(&my_fifo, buffer, sizeof(buffer));
4. 入队操作(生产者)
核心函数:kfifo_in(struct kfifo *fifo, const void *buf, unsigned int len)
-
功能 :将
buf指向的len字节数据拷贝到kfifo中。 -
返回值:实际拷贝的字节数(若缓冲区剩余空间不足,返回实际能拷贝的字节数)。
示例(入队int数据):
cpp
int data[] = {1, 2, 3, 4};
unsigned int len = sizeof(data); // 16字节(4个int)
unsigned int copied;
copied = kfifo_in(&my_fifo, data, len); // 将数据拷贝到kfifo
if (copied != len) {
printk(KERN_INFO "Only copied %u bytes (expected %u)\n", copied, len);
}
5. 出队操作(消费者)
核心函数:kfifo_out(struct kfifo *fifo, void *buf, unsigned int len)
-
功能 :从kfifo中取出最多
len字节数据,拷贝到buf。 -
返回值:实际拷贝的字节数(若缓冲区数据不足,返回实际能取出的字节数)。
示例(出队int数据):
cpp
int recv_data[4];
unsigned int len = sizeof(recv_data); // 16字节
unsigned int copied;
copied = kfifo_out(&my_fifo, recv_data, len); // 从kfifo取出数据
if (copied > 0) {
printk(KERN_INFO "Received %u bytes: %d, %d, %d, %d\n",
copied, recv_data[0], recv_data[1], recv_data[2], recv_data[3]);
} else {
printk(KERN_INFO "FIFO is empty\n");
}
6. 常用辅助函数
| 函数原型 | 功能描述 |
|---|---|
kfifo_size(struct kfifo *fifo) |
返回kfifo的总容量(字节) |
kfifo_len(struct kfifo *fifo) |
返回kfifo中当前数据长度(字节) |
kfifo_avail(struct kfifo *fifo) |
返回kfifo剩余可用空间(字节) |
kfifo_is_empty(struct kfifo *fifo) |
判断kfifo是否为空(返回布尔值) |
kfifo_is_full(struct kfifo *fifo) |
判断kfifo是否已满(返回布尔值) |
kfifo_reset(struct kfifo *fifo) |
重置kfifo(清空数据,不改变容量) |
kfifo_free(struct kfifo *fifo) |
释放动态分配的kfifo缓冲区 |
kfifo_peek(struct kfifo *fifo, void *buf, unsigned int len) |
查看kfifo头部数据(不取出),返回实际查看的字节数 |
kfifo_skip(struct kfifo *fifo, unsigned int len) |
跳过kfifo头部len字节数据(相当于丢弃) |
kfifo_from_user(struct kfifo *fifo, const void __user *from, unsigned int len) |
从用户空间拷贝数据到kfifo(返回0成功,负错误码失败) |
kfifo_to_user(struct kfifo *fifo, void __user *to, unsigned int len) |
从kfifo拷贝数据到用户空间(返回0成功,负错误码失败) |
三、完整示例
示例1:静态分配的kfifo(单生产者单消费者)
cpp
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#define FIFO_SIZE 8 // 必须是2的幂(8=2^3)
// 静态定义kfifo(存储int类型,大小FIFO_SIZE)
static DEFINE_KFIFO(my_fifo, int, FIFO_SIZE);
static int __init kfifo_example_init(void) {
int data[] = {10, 20, 30, 40};
int recv_data[4];
unsigned int copied;
printk(KERN_INFO "KFIFO example init\n");
// 入队数据(4个int,共16字节?不,FIFO_SIZE是元素个数还是字节?注意:DEFINE_KFIFO的第三个参数是**元素个数**,不是字节!哦,这里之前犯了一个错误!)
// 修正:DEFINE_KFIFO的语法是 DEFINE_KFIFO(name, type, size),其中size是**元素个数**,不是字节。例如,存储int类型,size=8表示最多存8个int(32字节)。
// 所以上面的例子中,FIFO_SIZE=8表示最多存8个int,data数组有4个int,没问题。
copied = kfifo_in(&my_fifo, data, ARRAY_SIZE(data)); // ARRAY_SIZE(data)=4(元素个数)
printk(KERN_INFO "Enqueued %u elements\n", copied); // 输出4
// 出队数据
copied = kfifo_out(&my_fifo, recv_data, ARRAY_SIZE(recv_data)); // 尝试取4个
printk(KERN_INFO "Dequeued %u elements: %d, %d, %d, %d\n",
copied, recv_data[0], recv_data[1], recv_data[2], recv_data[3]);
return 0;
}
static void __exit kfifo_example_exit(void) {
printk(KERN_INFO "KFIFO example exit\n");
}
module_init(kfifo_example_init);
module_exit(kfifo_example_exit);
MODULE_LICENSE("GPL");
示例2:动态分配的kfifo(多生产者多消费者,加锁)
cpp
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#include <linux/spinlock.h>
#define FIFO_SIZE 16 // 元素个数(假设存储char,总容量16字节)
struct kfifo *dyn_fifo;
spinlock_t fifo_lock; // 用于多生产者/消费者的锁
static int __init kfifo_dynamic_init(void) {
char data[] = "hello, kfifo!";
char recv_buf[20];
unsigned int copied;
int ret;
spin_lock_init(&fifo_lock);
// 动态分配kfifo(存储char,元素个数FIFO_SIZE)
ret = kfifo_alloc(&dyn_fifo, FIFO_SIZE, GFP_KERNEL);
if (ret) {
printk(KERN_ERR "kfifo_alloc failed: %d\n", ret);
return ret;
}
// 入队(加锁)
spin_lock(&fifo_lock);
copied = kfifo_in(dyn_fifo, data, strlen(data));
spin_unlock(&fifo_lock);
printk(KERN_INFO "Enqueued %u bytes: %s\n", copied, data);
// 出队(加锁)
spin_lock(&fifo_lock);
copied = kfifo_out(dyn_fifo, recv_buf, sizeof(recv_buf)-1); // 留1字节给'\0'
spin_unlock(&fifo_lock);
if (copied > 0) {
recv_buf[copied] = '\0';
printk(KERN_INFO "Dequeued %u bytes: %s\n", copied, recv_buf);
}
return 0;
}
static void __exit kfifo_dynamic_exit(void) {
kfifo_free(dyn_fifo); // 释放动态分配的kfifo
printk(KERN_INFO "KFIFO dynamic example exit\n");
}
module_init(kfifo_dynamic_init);
module_exit(kfifo_dynamic_exit);
MODULE_LICENSE("GPL");
四、注意事项
-
大小限制 :kfifo的元素个数 必须是2的幂(如8、16、32),否则会导致索引计算错误(内核不会自动调整,用户需确保)。
-
线程安全:
-
单生产者单消费者(SPSC):
kfifo_in/kfifo_out是无锁的(依赖CPU的原子操作),无需加锁。 -
多生产者/多消费者(MPMC):必须用锁(如
spinlock、mutex)保护所有kfifo操作。
-
-
数据拷贝 :kfifo的操作都是深拷贝(从用户缓冲区拷贝到kfifo内部缓冲区,或反之),因此效率略低于指针传递,但更安全。
-
类型转换 :kfifo以字节流为单位,存储结构化数据时(如
struct),需确保发送方和接收方的结构体布局一致(避免 padding 差异)。 -
旧API兼容 :早期内核(如2.6.x)使用
kfifo_put/kfifo_get,现在推荐用kfifo_in/kfifo_out(更通用)。
参考资料
-
内核文档:
Documentation/core-api/kernel-api.rst(搜索"kfifo")。 -
内核源码:
include/linux/kfifo.h、lib/kfifo.c。
