技术知识库
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。 - 后续等待队列实验实现真正的阻塞写。