Saltar al contenido

¿Cómo uso ioctl () para manipular mi módulo de kernel?

Solución:

El código de ejemplo que necesita se puede encontrar en drivers/watchdog/softdog.c (de Linux 2.6.33 en el momento en que se escribió esto), que ilustra las operaciones de archivo adecuadas, así como cómo permitir que el espacio de usuario complete una estructura con ioctl ().

En realidad, es un tutorial excelente y funcional para cualquiera que necesite escribir controladores de dispositivos de caracteres triviales.

Analicé la interfaz ioctl de softdog cuando respondí mi propia pregunta, lo que puede ser útil para usted.

Aquí está la esencia (aunque lejos de ser exhaustiva) …

En softdog_ioctl() verá una inicialización simple de struct watchdog_info que anuncia la funcionalidad, la versión y la información del dispositivo:

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

Luego miramos un caso simple en el que el usuario solo desea obtener estas capacidades:

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

… que, por supuesto, llenará el espacio de usuario correspondiente watchdog_info con los valores inicializados arriba. Si copy_to_user () falla, se devuelve -EFAULT, lo que hace que la llamada al espacio de usuario correspondiente a ioctl () devuelva -1 con un errno significativo establecido.

Tenga en cuenta que las solicitudes mágicas están realmente definidas en linux / watchdog.h, de modo que el kernel y el espacio de usuario las comparten:

#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 obviamente significa “Watchdog ioctl”

Puede llevarlo un paso más allá fácilmente, haciendo que su controlador haga algo y coloque el resultado de ese algo en la estructura y lo copie en el espacio de usuario. Por ejemplo, si struct watchdog_info también tuviera un miembro __u32 result_code. Nota, __u32 es solo la versión del kernel de uint32_t.

Con ioctl (), el usuario pasa la dirección de un objeto, ya sea una estructura, un entero, lo que sea, al kernel esperando que el kernel escriba su respuesta en un objeto idéntico y copie los resultados a la dirección que se proporcionó.

Lo segundo que tendrá que hacer es asegurarse de que su dispositivo sepa qué hacer cuando alguien lo abre, lee, escribe en él o usa un gancho como ioctl (), que puede ver fácilmente al estudiar softdog.

De interés es:

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

Donde ves que el controlador unlocked_ioctl va a … lo has adivinado, softdog_ioctl ().

Creo que podría estar yuxtaponiendo una capa de complejidad que realmente no existe cuando se trata de ioctl (), realmente es así de simple. Por esa misma razón, la mayoría de los desarrolladores de kernel desaprueban la adición de nuevas interfaces ioctl a menos que sean absolutamente necesarias. Es demasiado fácil perder de vista el tipo que ioctl () va a llenar frente a la magia que usa para hacerlo, que es la razón principal por la que copy_to_user () falla a menudo, lo que hace que el kernel se pudra con hordas de procesos de espacio de usuario atascados sueño del disco.

Para un temporizador, estoy de acuerdo, ioctl () es el camino más corto hacia la cordura.

Te falta un .open puntero de función en su file_operations estructura para especificar la función que se llamará cuando un proceso intente abrir el archivo del dispositivo. Deberá especificar un .ioctl puntero de función para su función ioctl también.

Intente leer la Guía de programación del módulo del kernel de Linux, específicamente los capítulos 4 (Archivos de dispositivo de caracteres) y 7 (Hablar con archivos de dispositivo).

El capítulo 4 presenta el file_operations estructura, que contiene punteros a funciones definidas por el módulo / controlador que realizan varias operaciones como open o ioctl.

El Capítulo 7 proporciona información sobre la comunicación con un módulo / unidad a través de ioctls.

Controladores de dispositivos Linux, tercera edición es otro buen recurso.

Ejemplo mínimo ejecutable

Probado en un entorno QEMU + Buildroot completamente reproducible, por lo que podría ayudar a otros a obtener su ioctl laboral. GitHub upstream: módulo de kernel | encabezado compartido | userland.

La parte más molesta fue comprender que se secuestraron algunos identificadores bajos: ioctl no se llama si cmd = 2, debe usar _IOx macros.

Módulo de kernel:

#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 = %xn", 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 = %dn", 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 %dn", 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)

Encabezado compartido entre el módulo del kernel y el área de usuario:

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

Userland:

#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 = %dn", arg_int);
        printf("ret = %dn", ret);
        printf("errno = %dn", 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 %dn", arg_struct.i, arg_struct.j);
        printf("ret = %dn", ret);
        printf("errno = %dn", errno);
    }
    close(fd);
    return EXIT_SUCCESS;
}
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *