linux: カーネルからioctl(2)可能なfdをユーザ空間に返す

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ステップでできます。

  1. fdを新たに割り当て
  2. file構造体を作成
  3. 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$ 

( ´ー`)フゥー...