任务
写一个杂项字符设备驱动程序。杂项接口是创建字符设备的一种非常简单的方式,不需要担心所有的sysfs和字符设备注册混乱,因此尽可能地使用简单的接口。
-
杂项设备应该使用动态的次设备号创建,不需要去尝试预留一个真实的次设备号来用于你的测试模块,那样做会很疯狂。
-
杂项设备应该实现读和写功能。
-
杂项设备节点应该在
/dev/eudyptula
中显示。 -
当从字符设备节点读取时,你的分配的ID应该返回给调用者。
-
当向字符设备节点写入时,发送到内核的数据需要被检查。如果它匹配你的分配ID,则返回正确的写入返回值。如果值不匹配你的分配ID,返回“无效值”错误值。
-
当你的模块加载时,杂项设备应该被注册,当它卸载时应该被注销。
思路
1. 创建带有动态次设备号的杂项设备:
- 使用Linux内核提供的misc_register函数注册一个杂项设备。struct miscdevice结构体中的minor成员应设置为MISC_DYNAMIC_MINOR,以使用动态分配的次设备号。
2. 实现读写函数:
- 定义read和write函数,并在struct file_operations结构体中引用它们。这些函数将处理对设备文件的读写操作。
- read函数应返回分配给您的ID。
- write函数应检查写入的数据是否与您的分配ID匹配。如果匹配,返回正确的写入字节数;如果不匹配,返回错误码(例如,-EINVAL)。
3. 设备节点应出现在/dev/eudyptula:
- 在struct miscdevice结构体中设置name字段为"eudyptula"。这将导致创建名为/dev/eudyptula的设备文件。
4. 模块加载与卸载时的注册和注销:
- 在模块的初始化函数中调用misc_register来注册杂项设备。
- 在模块的退出函数中调用misc_deregister来注销杂项设备。
5. 提供证明:
- 编写测试脚本或程序,以演示设备文件的创建、读写操作的正确性和错误处理。
- 使用dmesg日志来显示模块加载和卸载的消息,以及读写操作的日志输出。
代码
helloworld.c如下
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/errno.h>
#define MY_ID "7c1caf2f50d1"
#define MY_ID_LEN 13 /* ID length including the final NULL */
static ssize_t hello_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
char *hello_str = MY_ID;
if (*ppos != 0)
return 0;
if ((count < MY_ID_LEN) ||
(copy_to_user(buf, hello_str, MY_ID_LEN)))
return -EINVAL;
*ppos += count;
return count;
}
static ssize_t hello_write(struct file *file, char const __user *buf,
size_t count, loff_t *ppos)
{
char *hello_str = MY_ID;
char input[MY_ID_LEN];
if ((count != MY_ID_LEN) ||
(copy_from_user(input, buf, MY_ID_LEN)) ||
(strncmp(hello_str, input, MY_ID_LEN - 1)))
return -EINVAL;
else
return count;
}
static const struct file_operations hello_fops = {
.owner = THIS_MODULE,
.read = hello_read,
.write = hello_write
};
static struct miscdevice hello_dev = {
MISC_DYNAMIC_MINOR,
"eudyptula",
&hello_fops
};
static int __init hello_init(void)
{
int ret;
ret = misc_register(&hello_dev);
pr_debug("Hello World!\n");
return ret;
}
static void __exit hello_exit(void)
{
misc_deregister(&hello_dev);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("7c1caf2f50d1");
MODULE_DESCRIPTION("Misc char hello world module");
makefile如下:
CFLAGS_helloworld.o = -DDEBUG
obj-m += helloworld.o
KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
测试
执行如下命令,编译并插入内核模块
make clean
make
sudo insmod helloworld.ko
lsmod | head
模块成功加载,如图:
查看设备节点
测试设备的读写功能
尝试用脚本测试设备的读写功能
read.sh如下,并添加执行权限
#!/bin/bash
READ_DATA=$(sudo cat /dev/eudyptula)
echo "read result: $READ_DATA"
write.sh如下:
#!/bin/bash
DATA="7c1caf2f50d1"
echo "$DATA" | sudo tee /dev/eudyptula
if [ $? -eq 0 ]; then
echo "write success"
else
echo "write failed"
fi
最后记得rmmod模块
问题解决
1.为什么只有读的权限没有写的权限,加了sudo也不行
使用sudo echo "test" > /dev/eudyptula命令时,遇到权限问题的原因在于重定向操作(>)是由当前的shell执行的,而不是sudo。即使echo命令有超级用户权限,写入/dev/eudyptula的操作却是以普通用户的权限执行的。为了解决这个问题,可以使用以下几种方法之一:
使用tee命令:(亲测有效)
echo "test" | sudo tee /dev/eudyptula
这里,echo命令的输出被传递给tee,tee以超级用户权限写入到/dev/eudyptula。
2.如果make出错,需要重新编译内核,保证makefile的lib/modules/下存在与uname -r一致的编译目录
说明
本项目来源于github开源项目eudyptula