Подтвердить что ты не робот

Как использовать ioctl() для управления моим модулем ядра?

Итак, я пытаюсь написать модуль ядра, который использует файл linux/timer.h. Я получил его для работы внутри модуля, и теперь я пытаюсь заставить его работать из пользовательской программы.

Вот мой модуль ядра:

//Necessary Includes For Device Drivers.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/ioctl.h>

#define DEVICE_NAME "mytimer"
#define DEVICE_FILE_NAME "mytimer"
#define MAJOR_NUM 61
#define MINOR_NUM 0

MODULE_LICENSE("Dual BSD/GPL");

static struct timer_list my_timer;

struct file_operations FileOps = 
{
    //No File Operations for this timer.
};

//Function to perform when timer expires.
void TimerExpire(int data)
{
    printk("Timer Data: %d\n", data);
}

//Function to set up timers.
void TimerSetup(void)
{
    setup_timer(&my_timer, TimerExpire, 5678);
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
}

//Module Init and Exit Functions.
int init_module(void)
{
    int initResult = register_chrdev(MAJOR_NUM, "mytimer", &FileOps);

    if (initResult < 0)
    {
        printk("Cannot obtain major number %d\n", MAJOR_NUM);

        return initResult;
    }

printk("Loading MyTimer Kernel Module...\n");


return 0;
}
void cleanup_module(void)
{
    unregister_chrdev(MAJOR_NUM, "mytimer");
    printk("Unloading MyTimer Kernel Module...\n");
}

В частности, я хочу, чтобы моя пользовательская программа вызывала функцию TimerSetup(). Я знаю, что мне нужно будет использовать ioctl(), но я не уверен, как указать в моем файле MODULE FILE, что TimerSetup() должен быть вызван через ioctl().

Кроме того, мой второй вопрос: мне удалось включить мой модуль, а также mknod в /dev/mytimer с правильным основным номером. Но когда я попытался открыть(), чтобы я мог получить дескриптор файла из него, он продолжал возвращать -1, что я предполагаю, это неправильно. Я удостоверился, что разрешения были прекрасны (на самом деле, я сделал это 777, чтобы быть уверенным)... Это все еще не работает... Есть ли что-то, что мне не хватает?

Вот программа пользователя на всякий случай:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int fd = open("/dev/mytimer", "r");
    printf("fd: %d\n", fd);

    return 0;
}
4b9b3361

Ответ 1

Пример кода, который вам нужен, можно найти в drivers/watchdog/softdog.c (из Linux 2.6.33 на момент написания), который иллюстрирует правильные операции с файлами, а также как разрешить пользовательской области заполнять структуру с помощью ioctl().

На самом деле это отличный, рабочий учебник для тех, кому нужно писать тривиальные драйверы символов.

Я рассмотрел интерфейс softdog ioctl, когда ответил на мой вопрос, который может быть вам полезен.

Вот его суть (хотя и далеко не исчерпывающая)...

В softdog_ioctl() вы видите простую инициализацию struct watchdog_info, которая рекламирует функциональность, версию и информацию об устройстве:

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

Затем мы рассмотрим простой случай, когда пользователь просто хочет получить эти возможности:

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

... который, конечно же, заполнит соответствующее пользовательское пространство watchdog_info с инициализированными значениями выше. Если copy_to_user() терпит неудачу, возвращается -EFAULT, что вызывает вызов ioctl() соответствующего пользовательского пространства для возврата -1 с установленным значением errno.

Обратите внимание, что магические запросы на самом деле определены в linux/watchdog.h, так что ядро ​​и пользовательское пространство делятся ими:

#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, очевидно, означает "Watchdog ioctl"

Вы можете легко сделать этот шаг дальше, указав, что ваш драйвер что-то сделал, и поместите результат этого что-то в структуру и скопируйте его в пользовательское пространство. Например, если у struct watchdog_info также был член __u32 result_code. Примечание. __u32 - это только версия ядра uint32_t.

С помощью ioctl() пользователь передает адрес объекта, будь то структура, целое число, независимо от того, что ядро ​​ожидает, чтобы ядро ​​записало свой ответ в идентичном объекте и скопировал результаты по адресу, который был предоставлен.

Второе, что вам нужно сделать, это убедиться, что ваше устройство знает, что делать, когда кто-то открывает, читает его, записывает на него или использует крючок вроде ioctl(), который вы можете легко увидеть, изучая softdog.

Интересно:

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

Где вы видите обработчик unlocked_ioctl, который... вы догадались, softdog_ioctl().

Я думаю, вы могли бы сопоставить уровень сложности, который на самом деле не существует при работе с ioctl(), это действительно так просто. По этой же причине большинство разработчиков ядра недовольны новыми добавляемыми интерфейсами ioctl, если они не являются абсолютно необходимыми. Слишком легко потерять информацию о типе, который ioctl() собирается заполнить, и магии, которую вы используете для этого, что является основной причиной того, что copy_to_user() часто не получается, что приводит к гниению ядра с ордами процессов пользовательского пространства, застрявшими в дисковый сон.

Для таймера, я согласен, ioctl() - это самый короткий путь к здравомыслию.

Ответ 2

В структуре file_operations отсутствует указатель функции .open, чтобы указать функцию, вызываемую при попытке процесса открыть файл устройства. Вам также нужно указать указатель функции .ioctl для вашей функции ioctl.

Попробуйте прочитать Руководство по программированию модулей ядра Linux, в частности главы 4 (Файлы персональных устройств) и 7 (Разговор с файлами устройств).

В главе 4 представлена ​​структура file_operations, которая содержит указатели на функции, определенные модулем/драйвером, которые выполняют различные операции, такие как open или ioctl.

В главе 7 содержится информация о связи с модулем/диском через ioctls.

Драйверы устройств Linux, третье издание - еще один хороший ресурс.

Ответ 3

Минимальный пример runnable

Протестировано в полностью воспроизводимой среде QEMU + Buildroot, поэтому может помочь другим получить работу ioctl. GitHub вверх по течению: модуль ядра | общий заголовок | userland.

Самая раздражающая часть заключалась в понимании того, что некоторые низкие идентификаторы захвачены: ioctl не вызывается, если cmd = 2, вы должны использовать макросы _IOx.

Модуль ядра:

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

Общий заголовок:

#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

Самодельная:

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