技术知识库
使用 KFIFO 实现可读写邮箱设备
使用 KFIFO 实现可读写邮箱设备
摘要
本实验保留 misc 设备注册框架,把普通字符数组、有效长度和 *ppos 管理替换为 Linux KFIFO。驱动通过 kfifo_from_user() 追加数据,通过 kfifo_to_user() 读取并消费数据。模块加载后生成 /dev/kfifo_mailbox,实际写入 test3 后成功读回,实验 6-4 的核心目标已完成。
环境与路径
宿主机:Ubuntu 26.04,x86-64
目标机:QEMU ARM64,Linux 5.0.0+,Debian 12
源码目录:/home/martin-zou/workspace/gcc-lab/char_mod/kfifo_mailbox
内核构建目录:/home/martin-zou/workspace/runninglinuxkernel_5.0/build/arm64
交叉编译器前缀:aarch64-linux-gnu-
SSH 转发:127.0.0.1:2222 → ARM64 Guest:22
前置实验:使用 misc 机制实现可读写邮箱设备。
1. 创建 KFIFO 邮箱驱动
mkdir -p /home/martin-zou/workspace/gcc-lab/char_mod/kfifo_mailbox
cd /home/martin-zou/workspace/gcc-lab/char_mod/kfifo_mailbox
vim kfifo_mailbox.c
写入经过本轮代码检查整理的版本:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#define KFIFO_MAILBOX_NAME "kfifo_mailbox"
#define KFIFO_MAILBOX_SIZE 256
DEFINE_KFIFO(kfifo_mailbox_fifo, char, KFIFO_MAILBOX_SIZE);
static int kfifo_mailbox_open(struct inode *inode, struct file *file)
{
pr_info("%s: major=%d, minor=%d\n",
__func__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
return 0;
}
static ssize_t kfifo_mailbox_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
unsigned int copied;
int ret;
ret = kfifo_to_user(&kfifo_mailbox_fifo, buf, count, &copied);
if (ret)
return ret;
pr_info("kfifo_mailbox: read %u bytes\n", copied);
return copied;
}
static ssize_t kfifo_mailbox_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
unsigned int copied;
int ret;
ret = kfifo_from_user(&kfifo_mailbox_fifo, buf, count, &copied);
if (ret)
return ret;
if (count != 0 && copied == 0)
return -ENOSPC;
pr_info("kfifo_mailbox: wrote %u bytes\n", copied);
return copied;
}
static const struct file_operations kfifo_mailbox_ops = {
.owner = THIS_MODULE,
.open = kfifo_mailbox_open,
.read = kfifo_mailbox_read,
.write = kfifo_mailbox_write,
};
static struct miscdevice kfifo_mailbox_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = KFIFO_MAILBOX_NAME,
.fops = &kfifo_mailbox_ops,
};
static int __init kfifo_mailbox_init(void)
{
int ret;
ret = misc_register(&kfifo_mailbox_device);
if (ret) {
pr_err("kfifo_mailbox: misc_register failed: %d\n", ret);
return ret;
}
pr_info("kfifo_mailbox: registered, major=%d, minor=%d\n",
MISC_MAJOR, kfifo_mailbox_device.minor);
return 0;
}
static void __exit kfifo_mailbox_exit(void)
{
misc_deregister(&kfifo_mailbox_device);
pr_info("kfifo_mailbox: unregistered\n");
}
module_init(kfifo_mailbox_init);
module_exit(kfifo_mailbox_exit);
MODULE_AUTHOR("martin.zou");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("KFIFO misc mailbox");
与实际验证版本相比,整理版删除了不再使用的普通缓冲区长度和 cdev 头文件,统一了 KFIFO 命名及日志,并让用户复制失败时返回原始错误码。它还临时用 -ENOSPC 阻止非零写请求在 FIFO 满时返回 0;正常 test3 读写路径没有改变。
2. 创建 Makefile
vim Makefile
写入:
KERNEL_DIR ?= /home/martin-zou/workspace/runninglinuxkernel_5.0/build/arm64
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
obj-m += kfifo_mailbox.o
.PHONY: all clean
all:
$(MAKE) -C $(KERNEL_DIR) \
M=$(CURDIR) \
ARCH=$(ARCH) \
CROSS_COMPILE=$(CROSS_COMPILE) \
modules
clean:
$(MAKE) -C $(KERNEL_DIR) \
M=$(CURDIR) \
ARCH=$(ARCH) \
CROSS_COMPILE=$(CROSS_COMPILE) \
clean
3. 交叉编译并检查模块
make clean
make -j"$(nproc)"
file kfifo_mailbox.ko
modinfo kfifo_mailbox.ko |
grep -E '^(license|description|author|vermagic):'
预期模块架构为 ARM AArch64,vermagic 与 Guest 的 5.0.0+ 一致。
4. 复制并加载模块
宿主机执行:
scp \
-P 2222 \
kfifo_mailbox.ko \
martin@127.0.0.1:~/
登录 ARM64 Guest:
ssh -p 2222 martin@127.0.0.1
加载并检查:
sudo insmod /home/martin/kfifo_mailbox.ko
lsmod |
grep '^kfifo_mailbox'
grep kfifo_mailbox /proc/misc
ls -l /dev/kfifo_mailbox
sudo dmesg |
tail -n 20
5. 验证 KFIFO 读写
普通用户可使用:
printf 'test3\n' |
sudo tee /dev/kfifo_mailbox >/dev/null
sudo cat /dev/kfifo_mailbox
本轮在 root shell 中的实际结果:
root@rlk-arm64:/home/martin# echo "test3" > /dev/kfifo_mailbox
root@rlk-arm64:/home/martin# cat /dev/kfifo_mailbox
test3
echo 写入的换行也进入 FIFO,所以 cat 输出后自然换行。读取完成后,该段数据已经从 FIFO 中消费。
6. 验证 FIFO 追加与消费语义
以下验证比单次 echo/cat 更能区分 KFIFO 与覆盖式普通缓冲区:
printf 'first-' |
sudo tee /dev/kfifo_mailbox >/dev/null
printf 'second' |
sudo tee /dev/kfifo_mailbox >/dev/null
sudo cat /dev/kfifo_mailbox
sudo cat /dev/kfifo_mailbox
预期第一次得到 first-second;第二次不再读到这批数据,因为第一次读取已经消费它们。
7. 已知边界:FIFO 写满
连续写入且不读取会填满 FIFO。早期版本在满时返回 0,导致 echo 持续重试并洪泛日志;修复后会返回 -ENOSPC,详见 KFIFO 写满后 echo 持续重试。
当前阶段至少保证非零写请求不返回 0:
if (count != 0 && copied == 0)
return -ENOSPC;
下一实验将把它扩展为正式的非阻塞 -EAGAIN 语义;真正的阻塞等待留给等待队列实验。
8. 卸载并验证清理
sudo rmmod kfifo_mailbox
lsmod |
grep kfifo_mailbox ||
echo 'module unloaded'
test ! -e /dev/kfifo_mailbox &&
echo 'device node removed'
完成标准
-
kfifo_mailbox.ko成功交叉编译并加载到 Linux 5.0 ARM64。 -
/dev/kfifo_mailbox自动生成。 -
kfifo_from_user()能接收用户空间数据。 -
kfifo_to_user()能读出并消费数据。 - 写入
test3后能够完整读回。 - 驱动不再用
*ppos和有效长度手工维护普通数组。 - 能说明 KFIFO 只管理数据,不自动提供阻塞或非阻塞策略。
- 已复现 FIFO 写满后零长度写导致用户程序持续重试的问题。
- 已重新构建并验证临时
-ENOSPC修复;部分写后能明确报错并结束。
代码检查结论
本轮实际代码的核心 KFIFO 路径正确,实验完成。仍有以下非阻塞整理项:
copy_len和write_len已正确使用unsigned int;日志格式建议对应使用%u。kfifo_to_user()、kfifo_from_user()的错误码可直接return ret,不必统一改成-EIO。martin_mailbox_len已无用途,可以删除。mymailbox_device当前没有被继续使用,可以删除。- misc + KFIFO 版本不再需要
#include <linux/cdev.h>。 - 注册失败应返回
ret,日志名称可以统一为kfifo_mailbox。
这些整理项不影响本轮 test3 的成功验证。
关联
- 字符设备驱动 MOC
- KFIFO 如何用环形缓冲区管理字符设备数据
- misc 设备为什么称为杂项设备
- 使用 misc 机制实现可读写邮箱设备
- KFIFO 写满后 echo 持续重试
- 扩展:为 KFIFO 邮箱加入 Nonblocking I/O
待验证
- 实验 6-5:为空或已满的 KFIFO 加入
O_NONBLOCK和-EAGAIN语义。 - 后续实验:等待队列、阻塞 I/O、
poll和并发保护。