KVMはioctl(2)でVMを作ったりvcpuを作ったりします。この時に使うfdは/dev/kvmファイルに対してioctl(2)を実行した時の戻り値です。ioctl(2)の戻り値がioctl(2)可能なfdとなってます。ユーザーランドに返すfdはどうやって作るのかというのが今回のめもです。
早速ですが、出来たコードはこうなります。
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/anon_inodes.h>
#include <linux/string.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/file.h>
MODULE_DESCRIPTION("ioctl test");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");
struct fd_test_data {
unsigned long id;
};
static long fd_test_anon_fd_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
int r = -1;
struct fd_test_data *p = filp->private_data;
pr_info("%s: ioctl is %d\n", __func__, ioctl);
switch (ioctl) {
case 0:
r = p->id;
break;
default:
pr_warn("unkown ioctl %ud\n", ioctl);
}
return r;
}
static struct file_operations fd_test_anon_fd_fops = {
.unlocked_ioctl = fd_test_anon_fd_ioctl,
.compat_ioctl = fd_test_anon_fd_ioctl,
.llseek = noop_llseek,
};
static int create_file(unsigned long id)
{
struct fd_test_data *p = kmalloc(sizeof(*p), GFP_KERNEL);
int fd;
char name[256] = { 0x0 };
struct file *filp;
if (!p)
return -ENOMEM;
p->id = id;
snprintf(name, sizeof(name), "fd_test-%ld\n", id);
filp = anon_inode_getfile(name, &fd_test_anon_fd_fops, p, O_RDWR);
if (IS_ERR(filp))
return -1;
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
return -1;
fd_install(fd, filp);
return fd;
}
static long fd_test_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
long r = -EINVAL;
pr_info("%s: ioctl is %d\n", __func__, ioctl);
switch (ioctl) {
case 0:
r = create_file(arg);
break;
default:
pr_warn("unkown ioctl %ud\n", ioctl);
}
return r;
}
static struct file_operations fd_test_chardev_fops = {
.unlocked_ioctl = fd_test_ioctl,
.compat_ioctl = fd_test_ioctl,
.llseek = noop_llseek,
};
static struct miscdevice fd_test_chardev = {
.minor = 243,
.name = "fd_test",
.fops = &fd_test_chardev_fops,
};
static int fd_test_init(void)
{
int fd;
fd = misc_register(&fd_test_chardev);
if (fd < 0) {
pr_warn("failed to create miscdevice\n");
return fd;
}
pr_info("device file created\n");
return fd;
}
static void fd_test_exit(void)
{
misc_deregister(&fd_test_chardev);
pr_info("finish %s\n", __func__);
}
module_init(fd_test_init);
module_exit(fd_test_exit);
まずはmiscデバイスを作っていて、最初にこのファイルを開いてioctl(2)を実行します。そうするとioctl(2)を実行できるfdが返るという感じです。
このfdの作り方は以下の3ステップでできます。
- fdを新たに割り当て
- file構造体を作成
- fdとファイル構造体を関連付ける
あ、上記のコードだと1と2は逆ですが。
1のfdの割り当てはget_unused_fd_flags()で行います。
2はanon_inode_getfile()です。この関数の3番目の引数はfile構造体のprivate_data変数に設定するデータです。上記のコードだとfd_test_data構造体を設定してます。
最後の3はfd_install()で、見ての通りfdとfile構造体を渡してます。
そして、fdをユーザーランドに返せば完了です。
テストコードはこんな感じです。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd;
int anon_fd;
int r;
unsigned long id = argc == 2 ? strtoul(argv[1], NULL, 10) : 1234UL;
fd = open("/dev/fd_test", O_RDWR);
if (fd < 0) {
perror("open");
exit(-1);
}
anon_fd = ioctl(fd, 0, id);
if (anon_fd < 0) {
perror("ioctl");
exit(-1);
}
r = ioctl(anon_fd, 0, 0UL);
printf("id is %d\n", r);
close(fd);
}
これをコンパイルして動かすとこんなふうになります。
masami@kerntest:~/fd_test$ sudo ./a.out 1234
id is 1234
masami@kerntest:~/fd_test$ sudo ./a.out 12345678
id is 12345678
masami@kerntest:~/fd_test$
( ´ー`)フゥー...