kfifo
1. kfifo (内核 FIFO)
kfifo
是 Linux 内核中一个通用、无锁(在单读者单写者场景下)的环形缓冲区实现。它设计精巧,提供了简洁而强大的 API。
主要特性:
- 无锁(特定条件下):当只有一个生产者和一个消费者时,
kfifo
是无锁的,性能极高。这是通过分离生产者和消费者的索引指针实现的。 - 内存高效:其内部缓冲区的大小通常要求是 2 的幂次方。这使得可以通过位运算(如
& (size - 1)
)来高效计算回绕索引,而不是昂贵的取模运算。 - 健壮的 API:提供了用于入队(Enqueue)和出队(Dequeue)的核心函数,这些函数会返回成功操作的数据量,便于处理。
- 动态和静态分配:支持在编译时静态分配或在运行时动态分配内存。
- 处理任意大小数据:可以处理任意字节序列,而不仅仅是固定大小的元素。
核心 API(位于 <linux/kfifo.h>
):
-
初始化和销毁
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
:动态分配并初始化一个 kfifo。void kfifo_free(struct kfifo *fifo)
:释放由kfifo_alloc
分配的 fifo。void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)
:使用用户提供的预分配缓冲区来初始化 kfifo。
-
入队(写操作)
unsigned int kfifo_in(struct kfifo *fifo, const void *buf, unsigned int len)
:将数据从用户缓冲区buf
写入fifo
,最多写入len
字节。返回实际写入的字节数。int kfifo_in_spinlocked(struct kfifo *fifo, const void *buf, unsigned int n, spinlock_t *lock)
:在需要自旋锁保护的情况下使用的变体。
-
出队(读操作)
unsigned int kfifo_out(struct kfifo *fifo, void *buf, unsigned int len)
:从fifo
中读取数据到用户缓冲区buf
,最多读取len
字节。返回实际读取的字节数。数据会被从 fifo 中移除。unsigned int kfifo_out_peek(struct kfifo *fifo, void *buf, unsigned int len, unsigned offset)
:窥视 fifo 中的数据而不将其移除。int kfifo_out_spinlocked(struct kfifo *fifo, void *buf, unsigned int n, spinlock_t *lock)
:在需要自旋锁保护的情况下使用的变体。
-
状态查询
unsigned int kfifo_size(struct kfifo *fifo)
:返回 fifo 的总容量。unsigned int kfifo_len(struct kfifo *fifo)
:返回 fifo 中当前已用的字节数。unsigned int kfifo_avail(struct kfifo *fifo)
:返回 fifo 中剩余的可用空间字节数。bool kfifo_is_empty(struct kfifo *fifo)
/bool kfifo_is_full(struct kfifo *fifo)
:检查 fifo 是否为空或满。
使用示例:
#include <linux/kfifo.h>#define MY_FIFO_SIZE 1024 // 必须是 2 的幂struct my_device_data {struct kfifo data_fifo;spinlock_t fifo_lock; // 如果有多生产者/多消费者则需要
};static int my_probe_function(struct device *dev) {struct my_device_data *data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);int ret;// 动态分配并初始化 kfiforet = kfifo_alloc(&data->data_fifo, MY_FIFO_SIZE, GFP_KERNEL);if (ret) {return ret;}spin_lock_init(&data->fifo_lock);// ... 其他初始化return 0;
}// 生产者端
ssize_t my_write_function(struct my_device_data *data, const char __user *user_buf, size_t count) {unsigned int copied;unsigned long flags;spin_lock_irqsave(&data->fifo_lock, flags);copied = kfifo_in(&data->data_fifo, user_buf, count);spin_unlock_irqrestore(&data->fifo_lock, flags);// 可能唤醒等待数据的读者wake_up_interruptible(&my_read_queue);return copied;
}// 消费者端
ssize_t my_read_function(struct my_device_data *data, char __user *user_buf, size_t count) {unsigned int copied;unsigned long flags;spin_lock_irqsave(&data->fifo_lock, flags);copied = kfifo_out(&data->data_fifo, user_buf, count);spin_unlock_irqrestore(&data->fifo_lock, flags);return copied;
}
2. 其他环形缓冲区实现
除了通用的 kfifo
,内核中还有其他一些特定用途的 Ring Buffer:
- perf 子系统:Linux 的性能分析(perf)子系统使用了一个高度优化的环形缓冲区(
kernel/events/ring_buffer.c
)来将性能计数器数据从内核态高效地传递到用户态。 - Trace Ring Buffer:内核的 Ftrace 跟踪框架使用了自己实现的环形缓冲区来存储跟踪事件。这个实现非常复杂,考虑了每 CPU 缓冲区、时间戳、并发写入等。
- DMA 环形缓冲区:许多网络和存储设备驱动使用环形缓冲区来描述 DMA(直接内存访问)描述符环。例如,在网卡驱动中,经常可以看到
rx_ring
和tx_ring
,用于管理接收和发送的数据包缓冲区。
总结
实现 | 主要用途 | 特点 |
---|---|---|
kfifo | 通用环形缓冲区 | 无锁(SPSP场景)、API简洁、内存高效,是大多数情况下的首选 |
perf ring buffer | 性能事件数据 | 内核到用户空间的高性能数据传递,与 perf 工具紧密集成 |
trace ring buffer | 内核跟踪 | 每 CPU 缓冲区,用于系统调试和性能分析 |
DMA descriptor ring | 设备驱动(网络、存储) | 管理硬件 DMA 操作,与设备寄存器交互 |
因此,当你在 Linux 内核开发中需要一个环形缓冲区时,首先应该考虑 kfifo
。它经过了充分的测试和优化,能够满足绝大多数场景的需求,并且可以避免重复造轮子。只有在 kfifo
无法满足特定性能或功能要求时,才需要考虑实现自定义的环形缓冲区或使用其他特定子系统的实现。