GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie verwende ich ioctl(), um mein Kernel-Modul zu manipulieren?

Minimales lauffähiges Beispiel

Getestet in einer vollständig reproduzierbaren QEMU + Buildroot-Umgebung, könnte also anderen helfen, ihren ioctl zu bekommen Arbeiten. GitHub-Upstream:Kernelmodul |gemeinsamer Header |userland.

Der ärgerlichste Teil war zu verstehen, dass einige niedrige IDs entführt werden:ioctl wird nicht aufgerufen, wenn cmd =2 , Sie müssen _IOx verwenden Makros.

Kernelmodul:

#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)

Gemeinsamer Header zwischen Kernelmodul und Userland:

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

Benutzerland:

#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;
}

Den benötigten Beispielcode finden Sie in drivers/watchdog/softdog.c (von Linux 2.6.33 zu der Zeit, als dies geschrieben wurde), die die ordnungsgemäßen Dateioperationen veranschaulicht und wie man Userland erlaubt, eine Struktur mit ioctl() zu füllen.

Es ist tatsächlich ein großartiges, funktionierendes Tutorial für jeden, der triviale Zeichengerätetreiber schreiben muss.

Ich habe die ioctl-Schnittstelle von softdog seziert, als ich meine eigene Frage beantwortete, was für Sie hilfreich sein könnte.

Hier ist das Wesentliche (wenn auch bei weitem nicht erschöpfend) ...

In softdog_ioctl() Sie sehen eine einfache Initialisierung von struct watchdog_info, die Funktionalität, Version und Geräteinformationen ankündigt:

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

Wir betrachten dann einen einfachen Fall, in dem der Benutzer nur diese Fähigkeiten erhalten möchte:

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

... was natürlich den entsprechenden Userspace watchdog_info mit den oben initialisierten Werten füllt. Wenn copy_to_user() fehlschlägt, wird -EFAULT zurückgegeben, was dazu führt, dass der entsprechende Userspace-ioctl()-Aufruf -1 zurückgibt, wobei eine aussagekräftige Fehlernummer gesetzt wird.

Beachten Sie, dass die magischen Anforderungen tatsächlich in linux/watchdog.h definiert sind, sodass der Kernel und der Benutzerbereich sie gemeinsam nutzen:

#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 bedeutet offensichtlich "Watchdog ioctl"

Sie können leicht einen Schritt weiter gehen, indem Sie Ihren Treiber etwas tun lassen und das Ergebnis dieses Etwas in die Struktur einfügen und es in den Benutzerbereich kopieren. Zum Beispiel, wenn struct watchdog_info auch ein Mitglied __u32 result_code hatte . Beachten Sie, __u32 ist nur die Kernel-Version von uint32_t .

Mit ioctl() übergibt der Benutzer die Adresse eines Objekts, sei es eine Struktur, eine Ganzzahl oder was auch immer, an den Kernel und erwartet, dass der Kernel seine Antwort in ein identisches Objekt schreibt und die Ergebnisse an die angegebene Adresse kopiert.

Als Zweites müssen Sie sicherstellen, dass Ihr Gerät weiß, was zu tun ist, wenn jemand es öffnet, daraus liest, darauf schreibt oder einen Hook wie ioctl() verwendet, was Sie leicht erkennen können, indem Sie softdog.

Interessant ist:

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,
};

Wo Sie sehen, dass der unlocked_ioctl-Handler zu ... Sie haben es erraten, softdog_ioctl().

Ich denke, Sie stellen vielleicht eine Ebene der Komplexität gegenüber, die wirklich nicht existiert, wenn Sie sich mit ioctl() befassen, es ist wirklich so einfach. Aus dem gleichen Grund missbilligen die meisten Kernel-Entwickler das Hinzufügen neuer ioctl-Schnittstellen, es sei denn, sie sind absolut notwendig. Es ist einfach zu leicht, den Überblick über den Typ zu verlieren, den ioctl() füllen wird, im Vergleich zu der Magie, die Sie dafür verwenden, was der Hauptgrund dafür ist, dass copy_to_user() oft fehlschlägt, was dazu führt, dass der Kernel mit Horden von Userspace-Prozessen verrottet Festplattenruhezustand.

Für einen Timer, da stimme ich zu, ist ioctl() der kürzeste Weg zur Vernunft.


Ihnen fehlt ein .open Funktionszeiger in Ihrem file_operations -Struktur, um die Funktion anzugeben, die aufgerufen werden soll, wenn ein Prozess versucht, die Gerätedatei zu öffnen. Sie müssen einen .ioctl angeben Funktionszeiger auch für Ihre ioctl-Funktion.

Versuchen Sie, den Linux Kernel Module Programming Guide durchzulesen, insbesondere die Kapitel 4 (Character Device Files) und 7 (Talking to Device Files).

Kapitel 4 stellt den file_operations vor Struktur, die Zeiger auf vom Modul/Treiber definierte Funktionen enthält, die verschiedene Operationen wie open ausführen oder ioctl .

Kapitel 7 enthält Informationen zur Kommunikation mit einem Modul/Laufwerk über ioctls.

Linux Device Drivers, Third Edition ist eine weitere gute Ressource.


Linux
  1. So verwenden Sie den Rmmod-Befehl unter Linux mit Beispielen

  2. Wie finde ich das Kernel-Modul für ein bestimmtes Gerät?

  3. Linux – Wie kann man feststellen, welches Modul den Kernel verschmutzt?

  4. Wie finde ich die Version eines kompilierten Kernelmoduls?

  5. Wie kodiere ich ein Linux-Kernel-Modul?

So laden oder entladen Sie ein Linux-Kernel-Modul

So verwenden Sie den Modprobe-Befehl unter Linux

So laden und entladen Sie Kernel-Module in Linux

So erstellen Sie ein Terraform-Modul

So verwenden Sie den Su-Befehl unter Linux

So verwenden Sie Instagram im Terminal