Linux字符设备驱动实现与验证
本文总结 simple_demo 这个最小 Linux 字符设备驱动 demo 的功能,并给出完整代码与最小编译、加载、验证方法。
假定内核已经提前编译完成,且可直接使用:
KDIR=~/workspace/Linux/linux
1. Demo 功能概述
simple_demo 是一个基于 miscdevice 的最小字符设备驱动。
加载模块后,内核会创建设备节点:
sh
/dev/simple_demo
这个 demo 支持最基本的两类操作:
read():读取驱动内部保存的字符串write():把用户写入的字符串保存到驱动内部缓冲区
默认行为
- 模块加载后,默认内容为:
text
hello from simple_demo
- 用户向
/dev/simple_demo写入新内容后,后续再读时就会返回最新写入的数据
2. 代码实现思路
这个 demo 的实现非常简单,核心只有四部分:
- 一个全局缓冲区:保存当前字符串内容
- 一个
read():把内核缓冲区内容拷给用户态 - 一个
write():把用户态内容写入内核缓冲区
设备注册方式使用 miscdevice,这样不需要自己手动分配主设备号。
3. 完整代码
C
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#define SIMPLE_DEMO_MAX_MSG_LEN 256
#define SIMPLE_DEMO_DEFAULT_MSG "hello from simple_demo\n"
static DEFINE_MUTEX(simple_demo_lock);
static char simple_demo_msg[SIMPLE_DEMO_MAX_MSG_LEN] = SIMPLE_DEMO_DEFAULT_MSG;
static size_t simple_demo_msg_len = sizeof(SIMPLE_DEMO_DEFAULT_MSG) - 1;
static ssize_t simple_demo_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret;
mutex_lock(&simple_demo_lock);
ret = simple_read_from_buffer(buf, count, ppos, simple_demo_msg, simple_demo_msg_len);
mutex_unlock(&simple_demo_lock);
return ret;
}
static ssize_t simple_demo_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
size_t copy_len;
if (!count)
return 0;
copy_len = min(count, (size_t)(SIMPLE_DEMO_MAX_MSG_LEN - 1));
mutex_lock(&simple_demo_lock);
if (copy_from_user(simple_demo_msg, buf, copy_len)) {
mutex_unlock(&simple_demo_lock);
return -EFAULT;
}
simple_demo_msg[copy_len] = '\0';
simple_demo_msg_len = copy_len;
mutex_unlock(&simple_demo_lock);
return count;
}
static const struct file_operations simple_demo_fops = {
.owner = THIS_MODULE,
.read = simple_demo_read,
.write = simple_demo_write,
.llseek = noop_llseek,
};
static struct miscdevice simple_demo_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "simple_demo",
.fops = &simple_demo_fops,
.mode = 0666,
};
static int __init simple_demo_init(void)
{
int ret;
ret = misc_register(&simple_demo_miscdev);
if (ret)
return ret;
pr_info("simple_demo: loaded (/dev/%s)\n", simple_demo_miscdev.name);
return 0;
}
static void __exit simple_demo_exit(void)
{
misc_deregister(&simple_demo_miscdev);
pr_info("simple_demo: unloaded\n");
}
module_init(simple_demo_init);
module_exit(simple_demo_exit);
MODULE_AUTHOR("demo");
MODULE_DESCRIPTION("Simple misc char device driver demo");
MODULE_LICENSE("GPL");
4. 编译方法
假定内核已经编译好,内核代码位于~/workspace/Linux/linux,直接编这个驱动模块即可。
进入驱动Demo目录:
bash
cd ~/workspace/Lab/drivers
编译命令:
bash
make clean KDIR=~/workspace/Linux/linux
make KDIR=~/workspace/Linux/linux
编译成功后会生成:
text
simple_demo.ko
可以再检查一下模块版本信息:
bash
modinfo -F vermagic simple_demo.ko
输出中应包含:
text
7.1.0-rc2+
输出必须等于 QEMU (我们用QEMU启动的内核)里的 uname -r
5. 加载与验证
宿主机与qemu之间传递文件可以参考《使用额外ext4磁盘镜像在QEMU中传递与加载内核模块》。
假设 simple_demo.ko 已经放到 QEMU 可访问目录 /mnt/host,在 QEMU 中执行:
sh
insmod /mnt/host/simple_demo.ko
ls -l /dev/simple_demo
读取默认内容:
sh
cat /dev/simple_demo
写入新内容并再次读取:
sh
echo -n "from_user\n" > /dev/simple_demo
cat /dev/simple_demo
查看日志:
sh
dmesg | tail -n 20
卸载模块:
sh
rmmod simple_demo
6. 上层应用如何使用
上层应用把它当成普通字符设备文件即可,直接对 /dev/simple_demo 做文件操作:
open("/dev/simple_demo", O_RDWR)read()write()close()
最简单的 shell 使用方式:
sh
cat /dev/simple_demo
echo -n "hello driver\n" > /dev/simple_demo
cat /dev/simple_demo