GNU/Linux >> Linux の 問題 >  >> Linux

ioctl() を使用してカーネル モジュールを操作するにはどうすればよいですか?

最小限の実行可能な例

完全に再現可能な QEMU + Buildroot 環境でテストされているため、他のユーザーが ioctl を取得するのに役立つ可能性があります 働く。 GitHub アップストリーム:カーネル モジュール |共有ヘッダー |ユーザーランド。

最も厄介な部分は、一部の低 ID がハイジャックされていることを理解することでした:cmd =2 の場合、ioctl は呼び出されません。 _IOx を使用する必要があります。

カーネル モジュール:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */

#include "ioctl.h"

MODULE_LICENSE("GPL");

static struct dentry *dir;

static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
    void __user *arg_user;
    union {
        int i;
        lkmc_ioctl_struct s;
    } arg_kernel;

    arg_user = (void __user *)argp;
    pr_info("cmd = %x\n", cmd);
    switch (cmd) {
        case LKMC_IOCTL_INC:
            if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
            pr_info("0 arg = %d\n", arg_kernel.i);
            arg_kernel.i += 1;
            if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
        break;
        case LKMC_IOCTL_INC_DEC:
            if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
            pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
            arg_kernel.s.i += 1;
            arg_kernel.s.j -= 1;
            if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
        break;
        default:
            return -EINVAL;
        break;
    }
    return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = unlocked_ioctl
};

static int myinit(void)
{
    dir = debugfs_create_dir("lkmc_ioctl", 0);
    /* ioctl permissions are not automatically restricted by rwx as for read / write,
     * but we could of course implement that ourselves:
     * https://stackoverflow.com/questions/29891803/user-permission-check-on-ioctl-command */
    debugfs_create_file("f", 0, dir, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(dir);
}

module_init(myinit)
module_exit(myexit)

カーネル モジュールとユーザーランド間の共有ヘッダー:

ioctl.h

#ifndef IOCTL_H
#define IOCTL_H

#include <linux/ioctl.h>

typedef struct {
    int i;
    int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC     _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)

#endif

ユーザーランド:

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "../ioctl.h"

int main(int argc, char **argv)
{
    int fd, arg_int, ret;
    lkmc_ioctl_struct arg_struct;

    if (argc < 2) {
        puts("Usage: ./prog <ioctl-file>");
        return EXIT_FAILURE;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open");
        return EXIT_FAILURE;
    }
    /* 0 */
    {
        arg_int = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d\n", arg_int);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    puts("");
    /* 1 */
    {
        arg_struct.i = 1;
        arg_struct.j = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    close(fd);
    return EXIT_SUCCESS;
}

必要なサンプル コードは drivers/watchdog/softdog.c にあります。 (これが書かれた時点で Linux 2.6.33 から)、適切なファイル操作と、ユーザーランドが ioctl() で構造体を埋める方法を示しています。

これは、簡単なキャラクター デバイス ドライバーを作成する必要がある人にとって、実際に役立つ優れたチュートリアルです。

私自身の質問に答える際に、softdog の ioctl インターフェイスを分析しました。これはあなたの役に立つかもしれません。

その要点は次のとおりです (すべてを網羅しているわけではありませんが) ...

softdog_ioctl() で 機能、バージョン、およびデバイス情報をアドバタイズする struct watchdog_info の単純な初期化が表示されます:

    static const struct watchdog_info ident = {
            .options =              WDIOF_SETTIMEOUT |
                                    WDIOF_KEEPALIVEPING |
                                    WDIOF_MAGICCLOSE,
            .firmware_version =     0,
            .identity =             "Software Watchdog",
    };

次に、ユーザーがこれらの機能を取得したいだけの単純なケースを見ていきます:

    switch (cmd) {
    case WDIOC_GETSUPPORT:
            return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;

...もちろん、対応するユーザー空間のwatchdog_infoに上記の初期化された値が入力されます。 copy_to_user() が失敗すると、-EFAULT が返され、対応するユーザー空間 ioctl() 呼び出しが -1 を返し、意味のある errno が設定されます。

マジック リクエストは実際には linux/watchdog.h で定義されているため、カーネルとユーザー空間がそれらを共有することに注意してください。

#define WDIOC_GETSUPPORT        _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS         _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS     _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP           _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS        _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE         _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT        _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT        _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT     _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT     _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT       _IOR(WATCHDOG_IOCTL_BASE, 10, int)

WDIOC は明らかに「Watchdog ioctl」を意味します

ドライバーに何かを実行させ、その結果を構造体に配置し、それをユーザー空間にコピーすることで、簡単にさらに一歩進めることができます。たとえば、構造体 watchdog_info にもメンバー __u32 result_code があるとします。 .注意、__u32 uint32_t のカーネルのバージョンです .

ioctl() を使用して、ユーザーはオブジェクトのアドレスをカーネルに渡します。それが構造体であろうと、整数であろうと、カーネルがその応答を同一のオブジェクトに書き込み、結果を提供されたアドレスにコピーすることを期待しています。

2 番目に必要なことは、誰かが開いたり、読み込んだり、書き込んだり、ioctl() のようなフックを使用したりしたときに、何をすべきかをデバイスが認識していることを確認することです。 /P>

興味深いのは:

static const struct file_operations softdog_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .write          = softdog_write,
        .unlocked_ioctl = softdog_ioctl,
        .open           = softdog_open,
        .release        = softdog_release,
};

unlocked_ioctl ハンドラーがどこに行くのか... ご想像のとおり、softdog_ioctl() です。

ioctl() を扱うときに実際には存在しない複雑なレイヤーを並置している可能性があると思いますが、それは本当に単純です。同じ理由で、ほとんどのカーネル開発者は、絶対に必要でない限り、新しい ioctl インターフェイスが追加されることに眉をひそめます。 ioctl() が埋めるタイプとそれを行うために使用する魔法のタイプを見失うのはあまりにも簡単です。これが、copy_to_user() が失敗する主な理由であり、多くのユーザー空間プロセスがスタックしてカーネルが腐敗します。ディスクスリープ。

タイマーについては、ioctl() が正気を保つための最短経路であることに同意します。


.open がありません file_operations の関数ポインタ プロセスがデバイス ファイルを開こうとしたときに呼び出される関数を指定する構造体。 .ioctl を指定する必要があります ioctl 関数の関数ポインタも同様です。

The Linux Kernel Module Programming Guide、特に第 4 章 (Character Device Files) と第 7 章 (Talking to Device Files) を読んでみてください。

第 4 章では file_operations を紹介します open などのさまざまな操作を実行するモジュール/ドライバーによって定義された関数へのポインターを保持する構造体 または ioctl .

第 7 章では、ioctl を介したモジュール/ドライブとの通信に関する情報を提供します。

Linux Device Drivers, Third Edition も優れたリソースです。


Linux
  1. LinuxでSuコマンドを使用する方法

  2. 特定のデバイスのカーネルモジュールを見つける方法は?

  3. Linux –どのモジュールがカーネルを汚染しているかを判断する方法は?

  1. カーネルモジュールパラメータを追加する方法は?

  2. コンパイルされたカーネルモジュールのバージョンを見つける方法は?

  3. Linux カーネルモジュールのコーディング方法は?

  1. LinuxでBusyBoxを使用する方法

  2. イーサネット (kgdboe) 経由で kgdb を使用するには?

  3. カーネルはどれくらいの RAM を使用しますか?