GNU/Linux >> Tutoriels Linux >  >> Linux

Comment utiliser ioctl() pour manipuler mon module noyau ?

Exemple exécutable minimal

Testé dans un environnement QEMU + Buildroot entièrement reproductible, il pourrait donc aider les autres à obtenir leur ioctl travail. GitHub en amont :module du noyau |en-tête partagé |espace utilisateur.

Le plus ennuyeux était de comprendre que certains identifiants bas étaient piratés :ioctl n'est pas appelé si cmd =2 , vous devez utiliser _IOx macros.

Module noyau :

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

En-tête partagé entre le module du noyau et l'espace utilisateur :

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

Espace utilisateur :

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

L'exemple de code dont vous avez besoin se trouve dans drivers/watchdog/softdog.c (à partir de Linux 2.6.33 au moment où cela a été écrit), qui illustre les opérations de fichier appropriées ainsi que la façon de permettre à userland de remplir une structure avec ioctl().

C'est en fait un excellent didacticiel fonctionnel pour tous ceux qui ont besoin d'écrire des pilotes de périphériques de caractères triviaux.

J'ai disséqué l'interface ioctl de softdog en répondant à ma propre question, ce qui peut vous être utile.

En voici l'essentiel (bien que loin d'être exhaustif)...

En softdog_ioctl() vous voyez une simple initialisation de struct watchdog_info qui annonce la fonctionnalité, la version et les informations sur l'appareil :

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

Nous examinons ensuite un cas simple où l'utilisateur souhaite simplement obtenir ces fonctionnalités :

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

... qui, bien sûr, remplira l'espace utilisateur watchdog_info correspondant avec les valeurs initialisées ci-dessus. Si copy_to_user() échoue, -EFAULT est renvoyé, ce qui fait que l'appel ioctl() de l'espace utilisateur correspondant renvoie -1 avec un errno significatif défini.

Notez que les requêtes magiques sont en fait définies dans linux/watchdog.h , de sorte que le noyau et l'espace utilisateur les partagent :

#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 signifiant évidemment "Watchdog ioctl"

Vous pouvez facilement aller plus loin en demandant à votre pilote de faire quelque chose et de placer le résultat de ce quelque chose dans la structure et de le copier dans l'espace utilisateur. Par exemple, si la structure watchdog_info avait également un membre __u32 result_code . Remarque, __u32 est juste la version du noyau de uint32_t .

Avec ioctl(), l'utilisateur passe l'adresse d'un objet, qu'il s'agisse d'une structure, d'un entier ou autre, au noyau en s'attendant à ce que le noyau écrive sa réponse dans un objet identique et copie les résultats à l'adresse fournie.

La deuxième chose que vous devrez faire est de vous assurer que votre appareil sait quoi faire lorsque quelqu'un l'ouvre, le lit, y écrit ou utilise un hook comme ioctl(), que vous pouvez facilement voir en étudiant softdog.

D'intérêt est :

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

Où vous voyez le gestionnaire unlocked_ioctl aller vers ... vous l'avez deviné, softdog_ioctl().

Je pense que vous pourriez juxtaposer une couche de complexité qui n'existe vraiment pas avec ioctl(), c'est vraiment aussi simple que cela. Pour cette même raison, la plupart des développeurs du noyau désapprouvent l'ajout de nouvelles interfaces ioctl à moins qu'elles ne soient absolument nécessaires. Il est tout simplement trop facile de perdre la trace du type que ioctl() va remplir par rapport à la magie que vous utilisez pour le faire, ce qui est la principale raison pour laquelle copy_to_user() échoue, ce qui entraîne souvent la pourriture du noyau avec des hordes de processus de l'espace utilisateur bloqués dans veille du disque.

Pour une minuterie, je suis d'accord, ioctl() est le chemin le plus court vers la raison.


Il vous manque un .open pointeur de fonction dans votre file_operations structure pour spécifier la fonction à appeler lorsqu'un processus tente d'ouvrir le fichier de périphérique. Vous devrez spécifier un .ioctl pointeur de fonction pour votre fonction ioctl également.

Essayez de lire le Guide de programmation du module du noyau Linux, en particulier les chapitres 4 (Fichiers de périphérique de caractères) et 7 (Parler aux fichiers de périphérique).

Le chapitre 4 présente le file_operations structure, qui contient des pointeurs vers des fonctions définies par le module/pilote qui effectuent diverses opérations telles que open ou ioctl .

Le chapitre 7 fournit des informations sur la communication avec un module/lecteur via ioctls.

Linux Device Drivers, Third Edition est une autre bonne ressource.


Linux
  1. Linux - Comment recharger correctement un module du noyau ?

  2. Comment trouver la version d'un module du noyau compilé ?

  3. modifications des paramètres du module du noyau (à l'aide de /sys/module)

  4. Comment utiliser kgdb sur Ethernet (kgdboe) ?

  5. Combien de RAM le noyau utilise-t-il ?

Comment charger ou décharger un module du noyau Linux

Comment utiliser la commande Modprobe sous Linux

Comment charger et décharger les modules du noyau sous Linux

Comment créer un module Terraform

Comment utiliser la commande Su sous Linux

Comment utiliser Instagram dans le terminal