技术知识库

扩展:为 KFIFO 邮箱加入 Nonblocking I/O

扩展:为 KFIFO 邮箱加入 Nonblocking I/O

摘要

本扩展在现有 kfifo_mailbox 上检测 file->f_flags 中的 O_NONBLOCK。FIFO 满时,非阻塞写返回 -EAGAIN,用户空间显示 Resource temporarily unavailable;该方向已实际验证。空 FIFO 的非阻塞读使用相同语义,但尚缺最终运行输出,因此本笔记暂为 growing。

原理见 O_NONBLOCK 如何改变字符设备读写语义。

环境与路径

目标机: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
设备节点:/dev/kfifo_mailbox
KFIFO 容量:256 字节

前置主线:使用 KFIFO 实现可读写邮箱设备。

1. 修改驱动源码

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, nonblock=%d\n",
		__func__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev),
		!!(file->f_flags & O_NONBLOCK));
	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;

	if (kfifo_is_empty(&kfifo_mailbox_fifo) &&
	    (file->f_flags & O_NONBLOCK))
		return -EAGAIN;

	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;

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

	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)
		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("nonblocking KFIFO misc mailbox");

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)"

scp \
  -P 2222 \
  kfifo_mailbox.ko \
  martin@127.0.0.1:~/

Guest:

sudo rmmod kfifo_mailbox 2>/dev/null || true
sudo insmod /home/martin/kfifo_mailbox.ko
ls -l /dev/kfifo_mailbox

4. 验证非阻塞写

先清空,再用普通方式写满256字节:

cat /dev/kfifo_mailbox >/dev/null

dd \
  if=/dev/zero \
  of=/dev/kfifo_mailbox \
  bs=256 \
  count=1 \
  status=none

然后把输出设备以 O_NONBLOCK 打开,再写1字节:

printf X |
  dd \
    of=/dev/kfifo_mailbox \
    oflag=nonblock \
    bs=1 \
    count=1 \
    status=none

echo "status=$?"

实际结果:

dd: error writing '/dev/kfifo_mailbox': Resource temporarily unavailable
status=1

这证明满 FIFO 的非阻塞写进入 O_NONBLOCK 分支并返回了 -EAGAIN。

5. 验证非阻塞读

先清空 FIFO:

cat /dev/kfifo_mailbox >/dev/null

把输入设备以 O_NONBLOCK 打开:

dd \
  if=/dev/kfifo_mailbox \
  of=/dev/null \
  iflag=nonblock \
  bs=1 \
  count=1 \
  status=none

echo "status=$?"

预期:

dd: error reading '/dev/kfifo_mailbox': Resource temporarily unavailable
status=1

完成标准

  • 驱动能区分普通文件描述符和 O_NONBLOCK 文件描述符。
  • 满 FIFO 的非阻塞写返回 -EAGAIN。
  • 用户空间观察到 Resource temporarily unavailable 和退出状态1。
  • 空 FIFO 的非阻塞读返回 -EAGAIN,并取得实际输出证据。
  • 完成读方向验证后把本笔记状态更新为 evergreen。

关联

待验证

  • 运行空 FIFO 的 iflag=nonblock 读取测试。
  • 后续使用等待队列实现普通打开时真正的阻塞读写。