技术知识库

使用 misc 机制实现可读写邮箱设备

使用 misc 机制实现可读写邮箱设备

摘要

本实验把基础字符设备邮箱改写为 misc 设备:保留原有 open/read/write 和 256 字节内核缓冲区,用一个 struct miscdevice、misc_register() 和 misc_deregister() 代替手工设备号、cdev、class 与 device 注册。模块加载后自动生成 /dev/misc_mailbox,实际写入 martin_test 后可由 cat 正确读回,实验 6-2 已完成。

原理见 misc 设备为什么称为杂项设备。

环境与路径

宿主机:Ubuntu 26.04,x86-64
目标机:QEMU ARM64,Linux 5.0.0+,Debian 12
源码目录:/home/martin-zou/workspace/gcc-lab/char_mod/misc_mailbox
内核构建目录:/home/martin-zou/workspace/runninglinuxkernel_5.0/build/arm64
交叉编译器前缀:aarch64-linux-gnu-
SSH 转发:127.0.0.1:2222 → ARM64 Guest:22

前置实验:实现可读写的 martin_mailbox 字符设备。

1. 创建 misc 邮箱驱动

mkdir -p /home/martin-zou/workspace/gcc-lab/char_mod/misc_mailbox
cd /home/martin-zou/workspace/gcc-lab/char_mod/misc_mailbox
vim misc_mailbox.c

写入经过本轮代码检查整理的版本:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>

#define MISC_MAILBOX_NAME "misc_mailbox"
#define MISC_MAILBOX_SIZE 256

static char misc_mailbox_buf[MISC_MAILBOX_SIZE];
static size_t misc_mailbox_len;

static int misc_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 misc_mailbox_read(struct file *file,
				 char __user *buf,
				 size_t count,
				 loff_t *ppos)
{
	size_t remaining;
	size_t read_len;

	if (*ppos >= misc_mailbox_len)
		return 0;

	remaining = misc_mailbox_len - *ppos;
	read_len = count;
	if (read_len > remaining)
		read_len = remaining;

	if (copy_to_user(buf, misc_mailbox_buf + *ppos, read_len))
		return -EFAULT;

	*ppos += read_len;
	pr_info("misc_mailbox: read %zu bytes\n", read_len);
	return read_len;
}

static ssize_t misc_mailbox_write(struct file *file,
				  const char __user *buf,
				  size_t count,
				  loff_t *ppos)
{
	size_t write_len = count;

	if (write_len > sizeof(misc_mailbox_buf) - 1)
		write_len = sizeof(misc_mailbox_buf) - 1;

	if (copy_from_user(misc_mailbox_buf, buf, write_len))
		return -EFAULT;

	misc_mailbox_buf[write_len] = '\0';
	misc_mailbox_len = write_len;
	*ppos += write_len;

	pr_info("misc_mailbox: received %zu bytes: %s\n",
		write_len, misc_mailbox_buf);
	return write_len;
}

static const struct file_operations misc_mailbox_ops = {
	.owner = THIS_MODULE,
	.open = misc_mailbox_open,
	.read = misc_mailbox_read,
	.write = misc_mailbox_write,
};

static struct miscdevice misc_mailbox_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = MISC_MAILBOX_NAME,
	.fops = &misc_mailbox_ops,
};

static int __init misc_mailbox_init(void)
{
	int ret;

	ret = misc_register(&misc_mailbox_device);
	if (ret) {
		pr_err("misc_mailbox: misc_register failed: %d\n", ret);
		return ret;
	}

	pr_info("misc_mailbox: registered, major=%d, minor=%d\n",
		MISC_MAJOR, misc_mailbox_device.minor);
	return 0;
}

static void __exit misc_mailbox_exit(void)
{
	misc_deregister(&misc_mailbox_device);
	pr_info("misc_mailbox: unregistered\n");
}

module_init(misc_mailbox_init);
module_exit(misc_mailbox_exit);

MODULE_AUTHOR("martin.zou");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("misc test mailbox");

与实际验证版本相比,整理版只做了不改变正常读写流程的修正:缓冲区改为模块私有、长输入返回实际接收长度、注册失败返回真实错误码,并删除当前没有使用的 this_device 保存变量。

2. 创建 Makefile

vim Makefile

写入:

KERNEL_DIR ?= /home/martin-zou/workspace/runninglinuxkernel_5.0/build/arm64
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-

obj-m += misc_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 misc_mailbox.ko
modinfo misc_mailbox.ko |
  grep -E '^(license|description|author|vermagic):'

预期模块架构为 ARM AArch64,vermagic 与 Guest 的 5.0.0+ 一致。

4. 复制并加载模块

宿主机执行:

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

登录 ARM64 Guest:

ssh -p 2222 martin@127.0.0.1

加载并检查:

sudo insmod /home/martin/misc_mailbox.ko

lsmod |
  grep '^misc_mailbox'

grep -E '^ *10 +misc$' /proc/devices
grep misc_mailbox /proc/misc
ls -l /dev/misc_mailbox

sudo dmesg |
  tail -n 20

/proc/devices 中的主设备号应为 10;/proc/misc 和 /dev/misc_mailbox 会显示实际动态分配的次设备号。

5. 验证邮箱读写

普通用户可使用:

printf 'martin_test\n' |
  sudo tee /dev/misc_mailbox >/dev/null

sudo cat /dev/misc_mailbox

本轮在 root shell 中的实际结果:

root@rlk-arm64:/home/martin# echo "martin_test" > /dev/misc_mailbox
root@rlk-arm64:/home/martin# cat /dev/misc_mailbox
martin_test

这证明设备节点、misc 分发、copy_from_user()、内核缓冲区和 copy_to_user() 已全部接通。

6. 卸载并验证清理

sudo rmmod misc_mailbox

lsmod |
  grep misc_mailbox ||
  echo 'module unloaded'

test ! -e /dev/misc_mailbox &&
  echo 'device node removed'

sudo dmesg |
  tail -n 20

完成标准

  • misc_mailbox.ko 成功交叉编译并加载到 Linux 5.0 ARM64。
  • misc_register() 自动创建 /dev/misc_mailbox。
  • 不再手工管理设备号、cdev、class 和 device。
  • echo 写入 martin_test 后,cat 能完整读回。
  • 能解释 misc 设备仍是字符设备,并共用主设备号 10。
  • 能说明 MISC_DYNAMIC_MINOR 是动态分配标记。

代码检查结论

本轮实际代码的核心注册和读写结构正确,实验完成。仍有以下非阻塞整理项:

  • write() 当前返回 count;输入超过 255 字节时实际只接收 255 字节,应返回 write_len。
  • misc_register() 失败时应返回原始 ret,不要固定返回 -1。
  • mymailbox_device = misc_mailbox_device.this_device 当前没有后续用途,可以删除;需要操作设备模型对象时再保留。
  • 缓冲区和长度只供本模块使用,建议声明为 static。
  • #include <linux/cdev.h> 在 misc 版本中已不需要。
  • 日志中的 successded 和 martin_mailbox_exit 可以改成对应的 misc 名称。

这些项目不否定本轮 martin_test 读写验证,只影响边界语义、错误信息和代码整洁度。

关联

待验证

  • 第 6 章完成后阅读 misc_open()、misc_register() 和 misc_deregister() 源码调用路径。
  • 后续实验补充并发保护和更完整的设备语义。