👋

[WSL2] 自作Linuxデバイスドライバをユーザープログラムから動かしてみる

2024/01/03に公開

前回の記事では自作したデバイスドライバをシェルから動かしてみたが、今回はユーザープログラムから動かしてみる。

デバイスドライバのソース

今回はユーザープログラムからwriteでデータを書き込み、書き込んだデータをreadで読み込み動作を確認する。そのため以下のようにread・write関数の中身を実装したデバイスドライバを作成した。

MyDevice.c
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>

#define DRIVER_NAME "MyDevice"
#define DRIVER_MAJOR 63

MODULE_LICENSE("GPL");

static int MyDevice_open(struct inode *inode, struct file *file)
{
    printk("MyDevice open\n");
    return 0;
}

static int MyDevice_close(struct inode *inode, struct file *file)
{
    printk("MyDevice close\n");
    return 0;
}

static char my_dev_data[64];

static ssize_t MyDevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    printk("MyDevice read\n");

    // カーネル空間からユーザー空間へデータをコピー
    if (copy_to_user(&buf[0], &my_dev_data[0], count))
    {
        // 失敗した場合エラーコードを返す
        return -EFAULT;
    }

    return (ssize_t)count;
}

static ssize_t MyDevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    printk("MyDevice write\n");

    // ユーザー空間からカーネル空間へデータをコピー
    if (copy_from_user(&my_dev_data[0], &buf[0], count))
    {
        // 失敗した場合エラーコードを返す
        return -EFAULT;
    }

    return (ssize_t)count;
}

/* 各種システムコールに対応するハンドラテーブル */
struct file_operations s_myDevice_fops = {
    .open    = MyDevice_open,
    .release = MyDevice_close,
    .read    = MyDevice_read,
    .write   = MyDevice_write,
};

static int MyDevice_init(void)
{
    printk("MyDevice init\n");
    register_chrdev(DRIVER_MAJOR, DRIVER_NAME, &s_myDevice_fops);
    return 0;
}

static void MyDevice_exit(void)
{
    printk("MyDevice exit\n");
    unregister_chrdev(DRIVER_MAJOR, DRIVER_NAME);
}

module_init(MyDevice_init);
module_exit(MyDevice_exit);

writeで書き込んだデータを保持しておくためにmy_dev_dataの静的な変数バッファを用意した。
デバイスドライバのプログラムが動いているカーネル空間はユーザープログラムのメモリ空間と分離されているので直接データをコピーすることはできない。そのため、writeではcopy_from_userの関数を使いユーザーデータをバッファにコピーし、readではcopy_to_userの関数を使いバッファの中身をユーザーデータへコピーする。

それ以外のMakefileやデバイスのインストール手順は前回と同じ。

ユーザープログラムのソースと実行結果

デバイスドライバの動作を確認するユーザープログラムのソースは以下。
writeで4バイト分のデータを書き込み、readで書き込んだデータが読めるかを確認するだけのシンプルなもの。

test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define DEV_FILE "/dev/myDevice"

int main()
{
    ssize_t ret;
    unsigned char write_buf[4];
    unsigned char read_buf[4];
    int fd = open(DEV_FILE, O_RDWR);
    if (fd == -1)
    {
        perror("open");
    }

    // 1234のデータをwrite
    write_buf[0] = 1;
    write_buf[1] = 2;
    write_buf[2] = 3;
    write_buf[3] = 4;
    ret = write(fd, &write_buf, sizeof(write_buf));
    if (ret < 0)
    {
        perror("write");
    }

    // writeしたデータをread
    ret = read(fd, &read_buf, sizeof(read_buf));
    if (ret < 0)
    {
        perror("read");
    }
    printf("%d%d%d%d\n", read_buf[0], read_buf[1], read_buf[2], read_buf[3]);

    if (close(fd) != 0)
    {
        perror("close");
    }

    return 0;
}

ソースを作成したらgccでコンパイルし実行する。

$ gcc test.c
$ ./a.out
1234

実行するとwriteで書き込んだ1234のデータがreadで読み込まれて出力されていることが分かる。

Discussion