GNU/Linux >> Znalost Linux >  >> Linux

Jak mohu použít ioctl() k manipulaci s mým modulem jádra?

Minimální spustitelný příklad

Testováno v plně reprodukovatelném prostředí QEMU + Buildroot, takže může pomoci ostatním získat jejich ioctl pracovní. GitHub upstream:modul jádra |sdílená hlavička |userland.

Nejotravnější na tom bylo pochopení, že některá nízká id jsou unesena:ioctl se nezavolá, pokud cmd =2 , musíte použít _IOx makra.

Modul jádra:

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

Sdílená hlavička mezi modulem jádra a uživatelskou zemí:

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

Uživatelská země:

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

Příklad kódu, který potřebujete, najdete v drivers/watchdog/softdog.c (z Linuxu 2.6.33 v době, kdy to bylo napsáno), což ilustruje správné operace se soubory a také to, jak povolit uživatelské zemi vyplnit strukturu pomocí ioctl().

Je to vlastně skvělý, fungující návod pro každého, kdo potřebuje napsat triviální ovladače znakových zařízení.

Rozhraní ioctl softdogu jsem rozebral, když jsem odpovídal na svou vlastní otázku, což by vám mohlo být užitečné.

Zde je jeho podstata (i když zdaleka ne vyčerpávající) ...

V softdog_ioctl() vidíte jednoduchou inicializaci struct watchdog_info, která inzeruje funkčnost, verzi a informace o zařízení:

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

Poté se podíváme na jednoduchý případ, kdy uživatel chce pouze získat tyto schopnosti:

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

... což samozřejmě vyplní odpovídající watchdog_info v uživatelském prostoru výše inicializovanými hodnotami. Pokud copy_to_user() selže, je vráceno -EFAULT, což způsobí, že odpovídající volání uživatelského prostoru ioctl() vrátí -1 a nastaví se smysluplné errno.

Všimněte si, že magické požadavky jsou ve skutečnosti definovány v linux/watchdog.h , takže je jádro a uživatelský prostor sdílí:

#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 zjevně znamená "Watchdog ioctl"

Můžete to snadno udělat o krok dále, nechat váš ovladač něco udělat a umístit výsledek toho něčeho do struktury a zkopírovat jej do uživatelského prostoru. Pokud má například struct watchdog_info také člena __u32 result_code . Poznámka:__u32 je pouze jaderná verze uint32_t .

Pomocí ioctl() uživatel předá adresu objektu, ať už je to struktura, celé číslo, cokoliv, jádru a očekává, že jádro zapíše svou odpověď do identického objektu a zkopíruje výsledky na poskytnutou adresu.

Druhá věc, kterou budete muset udělat, je ujistit se, že vaše zařízení ví, co má dělat, když jej někdo otevře, čte z něj, zapisuje do něj nebo používá háček jako ioctl(), což můžete snadno zjistit studiem softdog.

Zajímavostí je:

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

Kde vidíte, že handler unlocked_ioctl jde do ... uhodli jste, softdog_ioctl().

Myslím, že při práci s ioctl() možná stavíte vedle sebe vrstvu složitosti, která ve skutečnosti neexistuje, je to opravdu tak jednoduché. Ze stejného důvodu se většina vývojářů jádra mračí na přidávání nových rozhraní ioctl, pokud to není nezbytně nutné. Je příliš snadné ztratit přehled o typu, který ioctl() naplní, v porovnání s kouzlem, které k tomu použijete, což je hlavní důvod, proč copy_to_user() často selhává, což má za následek hnijící jádro s hordami procesů v uživatelském prostoru uvízlých v režim spánku disku.

Pro časovač, souhlasím, je ioctl() nejkratší cestou k příčetnosti.


Chybí vám .open ukazatel funkce ve vašem file_operations struktura k určení funkce, která má být volána, když se proces pokusí otevřít soubor zařízení. Budete muset zadat .ioctl ukazatel funkce také pro vaši funkci ioctl.

Zkuste si přečíst The Linux Kernel Module Programming Guide, konkrétně kapitoly 4 (Soubory znakových zařízení) a 7 (Talking to Device Files).

Kapitola 4 představuje file_operations struktura, která obsahuje ukazatele na funkce definované modulem/ovladačem, které provádějí různé operace, jako je open nebo ioctl .

Kapitola 7 poskytuje informace o komunikaci s modulem/jednotkou přes ioctls.

Dalším dobrým zdrojem jsou ovladače zařízení Linux, třetí vydání.


Linux
  1. Jak používat příkaz Rmmod v systému Linux s příklady

  2. Jak najít modul jádra pro dané zařízení?

  3. Jak přidat parametry modulu jádra?

  1. Jak používat BusyBox na Linuxu

  2. Jak kódovat modul jádra Linuxu?

  3. Jak používat kgdb přes ethernet (kgdboe)?

  1. Jak používám cron v Linuxu

  2. Jak používat Nginx k přesměrování

  3. Linux – Jak zjistit, který modul poškozuje jádro?