GNU/Linux >> Tutoriales Linux >  >> Linux

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

Ejemplo ejecutable mínimo

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

La parte más molesta fue entender que algunas identificaciones bajas están secuestradas:no se llama a ioctl si cmd =2, debe usar _IOx macros.

Módulo del núcleo:

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

Encabezado compartido entre el módulo del kernel y el espacio del 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

Zona de usuario:

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

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 archivos adecuadas y cómo permitir que el espacio de usuario llene una estructura con ioctl().

En realidad, es un gran tutorial funcional para cualquier persona que necesite escribir controladores de dispositivos de caracteres triviales.

Analicé la interfaz ioctl de softdog al responder mi propia pregunta, lo que puede resultarle útil.

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 observamos 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 ioctl() del espacio de usuario correspondiente devuelva -1 con un errno significativo configurado.

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 llevar eso 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 número entero, lo que sea, al núcleo esperando que el núcleo escriba su respuesta en un objeto idéntico y copie los resultados en la dirección proporcionada.

Lo segundo que deberá hacer es asegurarse de que su dispositivo sepa qué hacer cuando alguien lo abra, lea, escriba o use un enlace 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 del kernel fruncen el ceño ante la adición de nuevas interfaces ioctl a menos que sean absolutamente necesarias. Es demasiado fácil perder la noción del 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 suspensión 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 kernel de Linux, específicamente los capítulos 4 (Archivos de dispositivo de caracteres) y 7 (Hablando 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.


Linux
  1. Cómo usar BusyBox en Linux

  2. Cómo uso cron en Linux

  3. Cómo usar Nginx para redirigir

  4. Linux:¿cómo determinar qué módulo contamina el kernel?

  5. ¿Cómo codificar un módulo del kernel de Linux?

Cómo cargar o descargar un módulo del kernel de Linux

Cómo usar el comando Modprobe en Linux

Cómo cargar y descargar módulos del kernel en Linux

Cómo crear un módulo de Terraform

Cómo usar el comando Su en Linux

Cómo usar Instagram en la terminal