技术知识库
使用 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 读写验证,只影响边界语义、错误信息和代码整洁度。
关联
- 字符设备驱动 MOC
- misc 设备为什么称为杂项设备
- 字符设备如何把设备节点连接到 file_operations
- 实现可读写的 martin_mailbox 字符设备
- 使用 KFIFO 实现可读写邮箱设备
待验证
- 第 6 章完成后阅读
misc_open()、misc_register()和misc_deregister()源码调用路径。 - 后续实验补充并发保护和更完整的设备语义。