技术知识库

KFIFO 写满后 echo 持续重试

KFIFO 写满后 echo 持续重试

摘要

连续向 256 字节 KFIFO 写入且不读取后,FIFO 最终没有剩余空间。早期驱动让非零长度的 write() 返回 0,导致 echo 持续重试。修复后,最后一次写入先接收剩余的36字节,再次提交未写完的数据时返回 -ENOSPC;命令明确报告 No space left on device 并正常结束,故障闭环已验证。

问题/背景

基本的 test3 写入和读取已经成功。随后连续执行:

echo "tst" >> /dev/kfifo_mailbox

并写入较长字符串,超过 FIFO 剩余容量后,当前命令无法正常结束,虚拟机看起来像卡住。

环境

目标机:QEMU ARM64
内核:Linux 5.0.0+
设备:/dev/kfifo_mailbox
KFIFO 容量:256 字节

根因

当前写路径的核心逻辑是:

ret = kfifo_from_user(&misc_kfifo, buf, count, &write_len);
if (ret)
	return -EIO;

return write_len;

FIFO 满时可能出现:

ret = 0
write_len = 0
count > 0

ret = 0 只说明用户空间复制过程没有发生 fault,不代表一定写入了数据。驱动向非零写请求返回 0 会产生“零进展”语义;用户程序可能继续提交同一批剩余数据。若每次调用还打印日志,就会形成高速重试和日志洪泛。

紧急停止方法

先在 QEMU 串口控制台按:

Ctrl+C

若完全无法响应,-nographic 模式可按 Ctrl+A、松开后再按 X 退出 QEMU。后者相当于直接断电,只作为最后手段,Guest 中尚未同步的数据可能丢失。

当前阶段的安全修复

在 kfifo_from_user() 之后禁止对非零请求返回 0:

ret = kfifo_from_user(&misc_kfifo, buf, count, &write_len);
if (ret)
	return ret;

if (count != 0 && write_len == 0)
	return -ENOSPC;

return write_len;

若只写入了一部分,则先返回实际长度;用户空间再次提交剩余数据时,FIFO 已满会得到 -ENOSPC,从而终止重试。

下一实验中的正式策略

实验 6-5 将区分非阻塞访问:

if (kfifo_is_full(&misc_kfifo) &&
    (file->f_flags & O_NONBLOCK))
	return -EAGAIN;

真正的阻塞模式则需要等待队列:FIFO 满时写者睡眠,读取释放空间后再唤醒;这不是 KFIFO 自己完成的功能。

验证结果

修复并重新加载模块后,连续写入较长字符串。FIFO 接近满时出现部分写:

martin_mailbox: received 55
martin_mailbox: received 55
martin_mailbox: received 55
martin_mailbox: received 36
bash: echo: write error: No space left on device

这里的36字节是当时剩余空间。echo 随后重试未写完的数据,驱动发现 FIFO 已满并返回 -ENOSPC。之后的写入都会立即结束:

bash: echo: write error: No space left on device

没有再次发生无限重试,验证通过。

证据与来源

  • 本轮实际连续写入导致 QEMU 控制台无法正常停止。
  • 修复后实际观察到部分写入36字节,随后稳定返回 No space left on device。
  • 实验手册 v2.0.7,第 6.4 节“进阶思考”记录了向已满 KFIFO 持续写入时的重复输出问题。
  • 相关源码:include/linux/kfifo.h 与本地 kfifo_mailbox.c。

关联

待验证

  • 实验 6-5 待验证 O_NONBLOCK 和 -EAGAIN。
  • 后续等待队列实验实现真正的阻塞写。