技术知识库
KFIFO 如何用环形缓冲区管理字符设备数据
KFIFO 如何用环形缓冲区管理字符设备数据
摘要
KFIFO 是 Linux 内核提供的先进先出环形缓冲区。写入的数据追加到队尾,读取从队头取出并消费;读写位置由 KFIFO 内部维护,因此字符设备不再需要用 *ppos 和额外长度变量手工管理普通数组。它解决了排队、边界计算和已读空间复用,但不会自动决定 FIFO 为空或已满时应该等待还是报错。
问题/背景
普通 misc_mailbox 使用数组、有效长度和文件偏移:
char mailbox_buf[256];
size_t mailbox_len;
驱动必须自行计算剩余数据、限制复制长度并推进 *ppos。同一文件描述符先写后读时,写操作推进的文件偏移还可能使后续读取直接得到 EOF。实验 6-4 用 KFIFO 替换这种线性缓冲区管理。
核心机制
先进先出与环形空间
写入 A、B、C: 队头 → A B C ← 队尾
读取一次: 返回 A,队头移动到 B
继续写入 D: 队头 → B C D ← 队尾
KFIFO 内部维护读索引和写索引。读出的空间可以在环形数组中重新利用,不需要移动尚未读取的数据。
定义一个静态 KFIFO
#include <linux/kfifo.h>
DEFINE_KFIFO(kfifo_mailbox_fifo, char, 256);
kfifo_mailbox_fifo是对象名称。char是元素类型。256是元素数量;静态 KFIFO 的容量应为 2 的整数次幂。
从用户空间写入
unsigned int copied;
int ret;
ret = kfifo_from_user(&kfifo_mailbox_fifo,
buf, count, &copied);
if (ret)
return ret;
return copied;
KFIFO 会根据剩余空间决定实际接收长度,把数据复制到队尾,并推进内部写索引。驱动必须返回 copied,因为 FIFO 空间不足时它可能小于 count。
复制到用户空间
unsigned int copied;
int ret;
ret = kfifo_to_user(&kfifo_mailbox_fifo,
buf, count, &copied);
if (ret)
return ret;
return copied;
KFIFO 只返回当前已有的数据,并推进内部读索引。已经读出的内容不再出现在下一次读取中。loff_t *ppos 仍保留在 file_operations 规定的函数签名里,但这个 FIFO 设备不再依靠它定位数据。
与普通缓冲区的区别
| 行为 | 普通线性缓冲区 | KFIFO 环形缓冲区 |
|---|---|---|
| 写入 | 当前实现会覆盖旧内容 | 追加到队尾 |
| 读取 | 依赖 *ppos 和有效长度 | 从队头取出并消费 |
| 位置管理 | 驱动手工维护 | KFIFO 内部维护 |
| 已读空间 | 需要自行回收或重置 | 环形复用 |
| 空/满策略 | 驱动决定 | 仍由驱动决定 |
KFIFO 不等于阻塞 I/O
当 FIFO 为空时,kfifo_to_user() 可以成功但复制 0 字节;简单 read() 随后返回 0,用户空间会把它视作 EOF。当 FIFO 已满时,写入也可能复制 0 字节。
KFIFO 只负责保存和取出数据。下面这些策略需要驱动在其外层实现:
O_NONBLOCK下返回-EAGAIN。- 阻塞模式下使用等待队列睡眠。
- 数据到来或空间释放后唤醒等待者。
- 多生产者或多消费者场景下增加锁。
并发边界
KFIFO 在“单一生产者 + 单一消费者”场景可以不额外加锁。若存在多个写者或多个读者,仍需要由驱动使用自旋锁、互斥锁等方式保护相应操作。
意义
这个实验的重点不是少写几行 copy_to_user(),而是把设备数据从“一个可覆盖的内存块”升级成“有顺序、有容量、有消费语义的数据通道”。这正是串口接收、日志、传感器数据流和许多生产者—消费者设备常见的基础模型。
证据与来源
- Running Linux Kernel 5.0:
include/linux/kfifo.h。 - 实验手册 v2.0.7,第 6.4 节“使用 KFIFO 改进设备驱动”。
- 实验源码:
/home/martin-zou/workspace/gcc-lab/char_mod/kfifo_mailbox/kfifo_mailbox.c。 - Linux 5.0 ARM64 Guest 实际结果:写入
test3后,从/dev/kfifo_mailbox读回test3。
关联
- 字符设备驱动 MOC
- misc 设备为什么称为杂项设备
- 使用 KFIFO 实现可读写邮箱设备
- 使用 misc 机制实现可读写邮箱设备
- KFIFO 写满后 echo 持续重试
- O_NONBLOCK 如何改变字符设备读写语义
- 扩展:为 KFIFO 邮箱加入 Nonblocking I/O
待验证
- 实验 6-5 验证 FIFO 为空或已满时的非阻塞
-EAGAIN语义。 - 后续实验用等待队列实现真正的阻塞读写。
- 第 6 章结束后阅读
include/linux/kfifo.h的索引和掩码实现。