file_operations
最小可运行示例
此示例不与任何硬件交互,但它使用 debugfs 演示了更简单的 file_operations
内核 API。
内核模块 fops.c:
#include <asm/uaccess.h>
#include <linux/debugfs.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <uapi/linux/stat.h>
static struct dentry *debugfs_file;
static char data[] = {'a', 'b', 'c', 'd'};
static int open(struct inode *inode, struct file *filp)
{
pr_info("open\n");
return 0;
}
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
ssize_t ret;
pr_info("read\n");
pr_info("len = %zu\n", len);
pr_info("off = %lld\n", (long long)*off);
if (sizeof(data) <= *off) {
ret = 0;
} else {
ret = min(len, sizeof(data) - (size_t)*off);
if (copy_to_user(buf, data + *off, ret)) {
ret = -EFAULT;
} else {
*off += ret;
}
}
pr_info("buf = %.*s\n", (int)len, buf);
pr_info("ret = %lld\n", (long long)ret);
return ret;
}
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
ssize_t ret;
pr_info("write\n");
pr_info("len = %zu\n", len);
pr_info("off = %lld\n", (long long)*off);
if (sizeof(data) <= *off) {
ret = 0;
} else {
if (sizeof(data) - (size_t)*off < len) {
ret = -ENOSPC;
} else {
if (copy_from_user(data + *off, buf, len)) {
ret = -EFAULT;
} else {
ret = len;
pr_info("buf = %.*s\n", (int)len, data + *off);
*off += ret;
}
}
}
pr_info("ret = %lld\n", (long long)ret);
return ret;
}
static int release(struct inode *inode, struct file *filp)
{
pr_info("release\n");
return 0;
}
static loff_t llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
pr_info("llseek\n");
pr_info("off = %lld\n", (long long)off);
pr_info("whence = %lld\n", (long long)whence);
switch(whence) {
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = filp->f_pos + off;
break;
case SEEK_END:
newpos = sizeof(data) + off;
break;
default:
return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
pr_info("newpos = %lld\n", (long long)newpos);
return newpos;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.llseek = llseek,
.open = open,
.read = read,
.release = release,
.write = write,
};
static int myinit(void)
{
debugfs_file = debugfs_create_file("lkmc_fops", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
return 0;
}
static void myexit(void)
{
debugfs_remove_recursive(debugfs_file);
}
module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");
用户空间Shell测试程序:
#!/bin/sh
mount -t debugfs none /sys/kernel/debug
insmod /fops.ko
cd /sys/kernel/debug/lkmc_fops
cat f
printf '01' >f
cat f
printf '1234' >f
printf '12345' >f
echo "$?"
cat f
printf '1234' >f
printf 'z' | dd bs=1 of=f seek=2
cat f
如果你不清楚每个命令调用了哪些系统调用,你还应该编写一个C程序来运行这些测试。(或者你也可以使用
strace
并找出:-)。)
其他的
file_operations
涉及到的内容比较复杂,以下是一些进一步的例子:
-
ioctl
-
poll
-
mmap
从模拟器中开始软件模型化简化硬件
实际设备硬件开发很“难”,因为:
- 你不能总是轻松地获得所需的硬件。
- 硬件API可能很复杂。
- 很难看到硬件的内部状态。
像QEMU这样的模拟器通过在软件中模拟简化的硬件模拟来克服这些困难。
例如,QEMU有一个内置的教育PCI设备,称为
edu
,我在这里进一步解释了它:
如何在QEMU源代码中添加新设备?,是开始使用设备驱动程序的好方法。我在这里
提供了一个简单的驱动程序。
然后,你可以像任何其他程序一样在QEMU上放置printf或使用GDB,并精确地看到发生了什么。
对于你特定的用例,还有一个OPAM SPI模型:
https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c。
s/commands/functions/
- ILMostro_7