Linux kernel kfifo用法,10分钟讲清楚

在Linux内核中,kfifo (Kernel FIFO)是一个高效、通用的循环缓冲区(Circular Buffer)实现,专为生产者-消费者模型设计,广泛用于内核中的数据暂存与传输(如驱动中的中断上下文与进程上下文通信、模块间数据交换等)。

它支持动态/静态分配字节流/结构化数据 、**无锁单生产者单消费者(SPSC)**​ 等特性,是内核中最常用的FIFO组件之一。

一、kfifo的核心特点

  1. 循环缓冲:固定大小的缓冲区,写满后覆盖旧数据(或阻塞,取决于使用方式),读空后返回0。

  2. 高效操作 :入队(kfifo_in)和出队(kfifo_out)均为**O(1)**​ 时间复杂度,通过位运算实现快速索引。

  3. 线程安全

    • 单生产者单消费者(SPSC):无锁(依赖CPU原子操作),无需额外同步。

    • 多生产者/多消费者(MPMC):需用户自行加锁(如spinlockmutex)。

  4. 灵活分配 :支持静态数组 (编译时分配)和动态内存(运行时分配)。

  5. 数据无关性 :以字节流为单位操作,可存储任意类型数据(需用户自行解析)。

二、kfifo的使用步骤

1. 包含头文件
cpp 复制代码
#include <linux/kfifo.h>
2. 定义kfifo结构

kfifo的核心是struct kfifo结构体(内核隐藏实现,用户无需直接访问),通常通过以下方式定义:

  • 静态分配 (编译时分配缓冲区):用DEFINE_KFIFODECLARE_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");

四、注意事项

  1. 大小限制 :kfifo的元素个数 必须是2的幂(如8、16、32),否则会导致索引计算错误(内核不会自动调整,用户需确保)。

  2. 线程安全

    • 单生产者单消费者(SPSC):kfifo_in/kfifo_out无锁的(依赖CPU的原子操作),无需加锁。

    • 多生产者/多消费者(MPMC):必须用锁(如spinlockmutex)保护所有kfifo操作。

  3. 数据拷贝 :kfifo的操作都是深拷贝(从用户缓冲区拷贝到kfifo内部缓冲区,或反之),因此效率略低于指针传递,但更安全。

  4. 类型转换 :kfifo以字节流为单位,存储结构化数据时(如struct),需确保发送方和接收方的结构体布局一致(避免 padding 差异)。

  5. 旧API兼容 :早期内核(如2.6.x)使用kfifo_put/kfifo_get,现在推荐用kfifo_in/kfifo_out(更通用)。

参考资料

  • 内核文档:Documentation/core-api/kernel-api.rst(搜索"kfifo")。

  • 内核源码:include/linux/kfifo.hlib/kfifo.c

相关推荐
共享家95272 小时前
C++ 日志类设计
linux·c++·后端
Ops菜鸟(Xu JieHao)2 小时前
Linux快速生成测试日志flog
linux·运维·服务器·日志·log
云栖梦泽2 小时前
Linux内核与驱动:12.设备树实例分析
linux·c++·单片机
Edward111111112 小时前
TS安装
linux·运维·服务器
ZzzZZzzzZZZzzzz…2 小时前
Docker 数据持久化:4种挂载方式 + 备份还原实战
linux·运维·docker·云原生·容器·数据持久化
弹简特2 小时前
【Linux命令饲养指南】03-Linux文件操作与编辑:从“摸鱼”到“搬砖”,这篇让你把文件玩出花
linux
LSG_Dawn2 小时前
linux 开机黑屏,/dev/nvme1n1p4:clean, xxxxx/xxxxxxx files, xxxx/xxxx blocks
linux·运维·服务器
喜欢吃燃面2 小时前
Linux 进程间通信:命名管道与 System V 共享内存深度解析
linux·运维·服务器·学习
有谁看见我的剑了?2 小时前
关于linux namespace学习
linux·运维·docker